减治法

概述

减治法把一个大问题划分为若干个子问题,但是只需求解其中的一个子问题,也无需对子问题的解进行合并。所以,严格地说,减治法应该是一种退化了的分治法

求解过程

  1. 将问题分解为若干个子问题
    • 原问题的解只存在于其中一个较小规模的子问题中;
    • 原问题的解与其中一个较小规模的解之间存在某种确定的对应关系

例题

折半查找

实现

int BinarySearch(int a[],int n,int key){
    int low=0,high=n-1,mid;
    while(low<=high){
        mid=(low+high)/2;
        if(a[mid]==key) return mid;
        else if(a[mid]>key) high=mid-1;
        else low=mid+1;
    }
    return -1;
}

选择问题

问题

给定一个无序的数组,求第k小的元素

分析

类似快速排序的分治思想,每次选取一个基准元素,将数组分为两部分,左边的元素都小于基准元素,右边的元素都大于基准元素,如果基准元素的下标为i,那么第k小的元素就是第k小的元素

实现

int Partition(int a[],int low,int high){
    int pivot=a[low];
    while(low<high){
        while(low<high&&a[high]>=pivot) high--;
        a[low]=a[high];
        while(low<high&&a[low]<=pivot) low++;
        a[high]=a[low];
    }
    a[low]=pivot;
    return low;
}
int Select(int a[],int low,int high,int k){
    int pivotpos=Partition(a,low,high);
    int m=pivotpos-low+1;
    if(m==k) return a[pivotpos];
    else if(m>k) return Select(a,low,pivotpos-1,k);
    else return Select(a,pivotpos+1,high,k-m);
}

堆排序

实现

void ShitHeap(int r[],int k,int n){
    int i = k;
    int j = 2*i+1;
    while(j<n){
        if(j+1<n && r[j+1]>r[j]){
            j++;//j指向左右孩子中较大的那个
        }
        if(r[j]>r[i]){
            int temp = r[j];
            r[j] = r[i];
            r[i] = temp;
            i = j;
            j = 2*i+1;//继续向下筛选
        }else{
            break;
        }
    }
}
void BuildHeap(int r[],int n){
    for(int i=n/2-1;i>=0;i--){
        ShitHeap(r,i,n);
    }
    //排序
    for(int i=n-1;i>=0;i--){
        int temp = r[0];
        r[0] = r[i];
        r[i] = temp;
        ShitHeap(r,0,i);
    }
}

假币问题

问题

在 n 枚外观相同的硬币中,有一枚是假币,并且已知假币较轻。通过一架来任意比较两组硬币,从而得知两组硬币的重量是否相同,或者哪一组更轻一些,假币问题要求设计一个高效的算法来检测出这枚假币

分析

将硬币分为三组,每组 n/3(向上取整) 枚,如果两组硬币的重量相同,那么假币就在第三组,否则假币就在重量较轻的那一组。然后再将这一组硬币分为三组,重复上述过程,直到找到假币

实现

int coin(int c[],int low,int high,int n){
    int num1=0,num2=0,num3=0;
    if (n==1) return low+1;
    if (n%3==0)
        num1=num2=n/3;
    else
        num1=num2=n/3+1;
    num3=n-num1-num2;
    //第一组重量
    int w1=0;
    for (int i=0;i<num1;i++)
        w1+=c[low+i];
    //第二组重量
    int w2=0;
    for (int i=0;i<num2;i++)
        w2+=c[low+num1+i];
    //第三组重量
    int w3=0;
    for (int i=0;i<num3;i++)
        w3+=c[low+num1+num2+i];
    if (w1<w2)
        return coin(c,low,num1,n/3);
    else if (w1>w2)
        return coin(c,low+num1,num2,n/3);
    else
        return coin(c,low+num1+num2,num3,n/3);
}

两个序列的中位数

问题

给定两个等长的有序数组 A 和 B,找到两个数组的中位数

分析

分别求两个数组中位数

  • 两个数组的中位数相等,既中位数
  • 中位数一定在两个数组的中位数的左边或者右边
  • 当两个数组均为1时,中位数为两个数组中位数的较小值

实现

int mid(int a[],int b[],int n){
    int s1=0,s2=0;
    int e1=n-1,e2=n-1;
    int m1,m2;
    while(s1<e1&&s2<e2){
        m1=(s1+e1)/2;
        m2=(s2+e2)/2;
        if(a[m1]==b[m2])
            return a[m1];
        if (a[m1]>b[m2]){
            //保留a中m1之前的元素和b中m2之后的元素
            if((s1+e1)%2==0){
                e1=m1;
                s2=m2+1;
            }
            else{
                e1=m1;
                s2=m2;
            }
        }
        else{
            if((s2+e2)%2==0){
                s1=m1+1;
                e2=m2;
            }
            else{
                s1=m1;
                e2=m2;
            }
        }
        return a[s1]<b[s2]?a[s1]:b[s2];
    }
}

topK问题

问题

从大批量数据中找出前K大的数据

分析

创建k个大小的小顶堆,遍历数据,如果数据大于堆顶元素,就替换堆顶元素,然后调整堆

实现

int maxTopK(int a[],int n,int k,int max[]){
    //用前k个值构建小根堆
    for(int i=0;i<k;i++){
        max[i]=a[i];
        for(int j=i;j>0;){
            if(max[j]<max[(j-1)/2]){
                int temp=max[j];
                max[j]=max[(j-1)/2];
                max[(j-1)/2]=temp;
                j=(j-1)/2;
            }
            else
                break;
        }
    }
    //遍历剩余元素,若大于堆顶元素,则替换堆顶元素
    for(int i=k;i<n;i++){
        if(a[i]>max[0]){
            max[0]=a[i];
            for(int j=0;j<k;){
                if(max[j]>max[2*j+1]||max[j]>max[2*j+2]){
                    if(max[2*j+1]<max[2*j+2]){
                        int temp=max[j];
                        max[j]=max[2*j+1];
                        max[2*j+1]=temp;
                        j=2*j+1;
                    }
                    else{
                        int temp=max[j];
                        max[j]=max[2*j+2];
                        max[2*j+2]=temp;
                        j=2*j+2;
                    }
                }
                else
                    break;
            }
        }
    }
}

练习

    • 问题:折半查找范围
    • 分析:折半查找上限索引和下限索引
    • 实现:
        void findBetween(int a[],int n,int x,int y,int &i,int &j){
            //找上限
            int mid,low=0,high=n-1;
            while(low<=high){
                    mid=(low+high)/2;
                    if(a[mid]>x) high=mid-1;
                    else if(a[mid]==x) {low=mid;break;}
                    else low=mid+1;
            }
            i=low;
            //找下限
            low=0,high=n-1;
            while(low<=high){
                    mid=(low+high)/2;
                    if(a[mid]<y) low=mid+1;
                    else if(a[mid]==y) {high=mid;break;}
                    else high=mid-1;
            }
            j=high;
    }
    
posted @ 2023-03-24 09:33  asdio  阅读(232)  评论(0)    收藏  举报