张三木教你理解分治法

分治策略(Divide and Conquer)

  1. 将原始问题划分或者归结为规模较小的子问题
  2. 递归或者迭代求解每个子问题
  3. 将子问题的解综合得到原问题的解

注意:

  1. 子问题和原始问题性质完全一样
  2. 子问题之间可以彼此独立的求解
  3. 递归停止时子问题可以直接求解

分治算法设计模式的一般描述

devide-and-conquer(P) {
    if( |p|<=n0 ){
        adhoc(P)
    };
    divide P into smaller subinstances  P1,P2,`````PK;
    for( i = 1;i <=k ;i++){
        yi = divide-and-conquer(Pi);
    };
    rerturn merge(y1,y2,.....,yk);
}

其中,|p|表示问题P的规模;n0为不需要再分解问题的阈值;adhoc是基本子算法,可以直接求解;merge用于将子问题的解合并为原问题的解。

分治策略的特点

  • 将原问题规约为规模小的子问题,子问题与原问题具有相同的性质
  • 子问题规模足够小时,可以直接求解
  • 子问题的解综合得到原问题的解
  • 算法可以递归,也可以迭代实现
  • 算法的分析方法:递推方程
  • 算法实现:递归或者迭代

分治算法的时间分析

用主定理求解型为T(n)=aT(n/b)+O(n^d)的递归方程

设a>=1和b>1是常数,f(n)是一个函数,T(n)是定义在非负整数集上的函数:T(n)=aT(n/b)+ O(n^d) 。
即有:

分治法改进的方法

  1. 减少子问题个数
  2. 增加预处理

例子

二分查找

二分查找的设计思想:

  • 通过x和a[mid]的比较。将原问题归结为规模减半的子问题。
  • 对子问题进行二分检索
  • 当子问题规模为1的时候,直接比较x与a[mid],若相等则返回m
  • 时间复杂度:O(logN)
/*
*二分查找(数组实现)
*@param {array} a 需要查找的已排好序的数组
*@param {int} low 数组的最低下标
*@param {int} high 数组的最高下标
*@param {int} x 被查找的数
*@return {array} a 排好序的数组
*/

void binarySearch(int a[] , int low , int high , int x ){
    if(low > high){
        return ;
    };
    int mid = (low+high)/2;
    if(a[mid] == x){
        return mid;
    };
    if(a[mid] > x){
        return binarySearch(a , low , mid , x );
    }else if( a[mid] < x ){
        return binarySearch(a , mid , high , x );
    };
}

二分归并排序

二分归并排序设计思想:

  • 划分将原问题归结为规模为n/2的2个子问题
  • 继续划分,将原问题归结为规模为n/4的4个子问题。继续....,当子问题的规模为1时,划分结束。
  • 从规模1到n/2,陆续归并被排好序的两个数组。每归并一次,数组规模扩大一倍,直到原始数组。
  • 时间复杂度分析:O(n)=nlogn
/*
*二分归并数组排序
*
*/

void merge ( int a[ ], int low , int mid , int high ){
    int b[1000];
    int i = low;
    int j = mid;
    int k = 0;
    
    while(i <= mid && j<=high ){
        if( a[i] > a[j]){
            b[k]=a[j];
            j++;
            k++;
        } else {
            b[k]=a[i];
            i++;
            k++;
       };
      if(i <= m){
        for( int p = i; p<=m ;p++){
            b[k++]=a[p];
        };
     } else {
        for(int p=j;p<=high;p++){
            b[k++]=a[p];
       }     
    }
}

for(int p=low;p<=high;p++){
    a[p]=b[p-low];
};

/*
*
*@param {array} a 乱序数组
*@param {int} low 数组的最低下标
*@param {int} high 数组的最高下标
*/
void MergeSort( int a[ ], int low , int high ) {
    if(low<high){
        int mid = (low+high)/2;    //对半划分
        MergeSort(a , low , mid);    //子问题一
        MergeSort(a, mid+1, high);    //子问题二
        Merge(a,low,mid,high);
   }     
}

快速排序

用首元素x作为划分标准,将输入数组A划分成不超过x的元素构成的数组Al,大于x的元素构成的数组Ar。其中Al和Ar从左到右存放在数组A的位置。

递归地对子问题Al和Ar进行排序,直到子问题规模为1。

int partition(int a[], int left, int right) {
    int x = a[left];
    while (left < right) {
        while (left < right&&a[right] >= x) {
            right--;
        };
        a[left] = a[right];
        while (left < right&&a[left] <= x) {
            left++;
        };
        a[right] = a[left];
    };
    a[left] = x ;
    return left ;
}

void QSort(int a[],int low,int high){
    if(low<high){
        pivotloc=partition(a,low,high);
        QSort(L,low,pivotloc-1);
        QSort(L,pivotloc+1,high);
    }
}

void QuickSort(int a,int n){
    QSort(a,0,n-1);
}

小总结

分治法(尤其是二分法)非常适合用于存在某一个数并且查找该目标数的题目,同时也注意几个点:

  • 边界值的等号是否需要考虑
  • 中间值是否需要去掉
  • 二分后的对象是否与原对象性质一样。

简单来说,分治法的设计思想就是:将一个大的问题分解为性质相同规模较小的小问题,逐个击破,分而治之。


参考:
《计算机算法设计与分析》(第五版)王晓东著
郑琪老师提供的PPT
如有错漏,恳请指出。


结对编程感想:
本次结对编程实验中,我们一开始就遇到了难题(没有理解题意导致A不过去),我们两个都写出了自己的实现方法,在这上面花了很多时间。
后来知道错误之处后,答题就顺利了很多。结对编程可以互相找出对方的不足之处,也可以齐心协力想出一个更好的算法。队友逻辑思维十分清晰,我也在此次上机中学到了很多东西。

posted @ 2019-10-12 14:29  MarcusJr19  阅读(436)  评论(0编辑  收藏  举报