分治算法思想

一、基本概念

   在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)……

    任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算。n=2时,只要作一次比较即可排好序。n=3时只要作3次比较即可,…。而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的.

二、基本思想及策略

   分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

   分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。

   如果原问题可分割成k个子问题,1<k≤n,且这些子问题都可解并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。这自然导致递归过程的产生。分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。

三、分治法适用的情况

    分治法所能解决的问题一般具有以下几个特征:

    1) 该问题的规模缩小到一定的程度就可以容易地解决

    2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。

    3) 利用该问题分解出的子问题的解可以合并为该问题的解;

    4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。

第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;

第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;、

第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。

第四条特征涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。

四、分治法的基本步骤

分治法在每一层递归上都有三个步骤:

    step1 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;

    step2 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题

    step3 合并:将各个子问题的解合并为原问题的解。

它的一般的算法设计模式如下:

    Divide-and-Conquer(P)

    1. if |P|≤n0

    2. then return(ADHOC(P))

    3. 将P分解为较小的子问题 P1 ,P2 ,...,Pk

    4. for i←1 to k

    5. do yi ← Divide-and-Conquer(Pi) △ 递归解决Pi

    6. T ← MERGE(y1,y2,...,yk) △ 合并子问题

    7. return(T)

    其中|P|表示问题P的规模;n0为一阈值,表示当问题P的规模不超过n0时,问题已容易直接解出,不必再继续分解。ADHOC(P)是该分治法中的基本子算法,用于直接解小规模的问题P。因此,当P的规模不超过n0时直接用算法ADHOC(P)求解。算法MERGE(y1,y2,...,yk)是该分治法中的合并子算法,用于将P的子问题P1 ,P2 ,...,Pk的相应的解y1,y2,...,yk合并为P的解。

五、分治法的复杂性分析

    一个分治法将规模为n的问题分成k个规模为n/m的子问题去解。设分解阀值n0=1,且adhoc解规模为1的问题耗费1个单位时间。再设将原问题分解为k个子问题以及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。用T(n)表示该分治法解规模为|P|=n的问题所需的计算时间,则有:

 T(n)= k T(n/m)+f(n)

    通过迭代法求得方程的解:

    递归方程及其解只给出n等于m的方幂时T(n)的值,但是如果认为T(n)足够平滑,那么由n等于m的方幂时T(n)的值可以估计T(n)的增长速度。通常假定T(n)是单调上升的,从而当                  mi≤n<mi+1时,T(mi)≤T(n)<T(mi+1)。 
 

六、可使用分治法求解的一些经典问题

 (1)二分搜索
(2)大整数乘法
 (3)Strassen矩阵乘法
(4)棋盘覆盖
(5)合并排序
(6)快速排序
(7)线性时间选择

 (8)最接近点对问题
(9)循环赛日程表
(10)汉诺塔
 
七、依据分治法设计程序时的思维过程

    实际上就是类似于数学归纳法,找到解决本问题的求解方程公式,然后根据方程公式设计递归程序。
1、一定是先找到最小问题规模时的求解方法
2、然后考虑随着问题规模增大时的求解方法
3、找到求解的递归函数式后(各种规模或因子),设计递归程序即可。

二分搜索

假设搜索范围为数组a中的n个元素,要搜索的元素为x。
将n个元素分成个数大致相同的两半,取a[n/2]与x进行比较。如果x=a[n/2],则找到x,算法终止。如果x<a[n/2],则只要在数组a的左半部继续搜索x。如果x>a[n/2],则只要在数组a的右半部继续搜索x。以此不断地将搜索范围折半,从而实现采用分治策略进行二分搜索。
代码实现:

1 #include <iostream>2 #include <fstream>
 3 #include <cstdlib>
 4 #include <ctime>
 5 #include <algorithm>
 6 using namespace std;
 7 #define limit 100000
 8 
 9 int Search(int R[],int low,int high,int k)    //low表示当前查找的范围下界、high表示当前查找范围的上界,k为所要查找的内容
10 {
11     int mid;
12 
13     if (low<=high){                           //查找区间存在一个及以上元素
14         mid=(low+high)/2;                     //求中间位置
15         if (R[mid]==k)                        //查找成功返回1
16             return 1;
17         if (R[mid]>k)                         //在R[low..mid-1]中递归查找
18             Search(R,low,mid-1,k);
19         else                                  //在R[mid+1..high]中递归查找
20             Search(R,mid+1,high,k);
21     }
22     else
23         return 0;
24 }

归并排序

将长度为n序列 递归拆解
假设有序列初始值为: {5,4,6,3,1,2,8,7,10}; int arr[] = {5,4,6,3,1,2,8,7,10};
取中间分解点 int mid = (height+low)/2 。其中:low 为序列的起始下标 hight 为结束下标 分解点取他们中间的点
递归排序 arr[low] 至 arr[mid] 区间的序列 (原序列的左侧部分)
递归排序 arr[mid+1] 至 arr[hight ] 区间的序列 (原序列的右侧部分)
将左边的有序的子序列 和 右边的有序子序列 排序后组合 逐个填写到原数组中(这个部分的详细内容看第二点)
直到被分解的子序列长度为1 的时候 自然有序 停止递归

快速排序

i =L; j = R; 将基准数挖出形成第一个坑a[i]。

2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。

3.

i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。

再重复执行2,3二步,直到i==j,将基准数填入a[i]

  1. #include<iostream>

  2. using namespace std;

  3. void quickSort(int a[],int,int);

  4. int main()

  5. {

  6. int array[]={34,65,12,43,67,5,78,10,3,70},k;

  7. int len=sizeof(array)/sizeof(int);

  8. cout<<"The orginal arrayare:"<<endl;

  9. for(k=0;k<len;k++)

  10. cout<<array[k]<<",";

  11. cout<<endl;

  12. quickSort(array,0,len-1);

  13. cout<<"The sorted arrayare:"<<endl;

  14. for(k=0;k<len;k++)

  15. cout<<array[k]<<",";

  16. cout<<endl;

  17. system("pause");

  18. return 0;

  19. }

  20. void quickSort(int s[], int l, int r)

  21. {

  22. if (l< r)

  23. {

  24. int i = l, j = r, x = s[l];

  25. while (i < j)

  26. {

  27. while(i < j && s[j]>= x) // 从右向左找第一个小于x的数

  28. j--;

  29. if(i < j)

  30. s[i++] = s[j];

  31. while(i < j && s[i]< x) // 从左向右找第一个大于等于x的数

  32. i++;

  33. if(i < j)

  34. s[j--] = s[i];

  35. }

  36. s[i] = x;

  37. quickSort(s, l, i - 1); // 递归调用

  38. quickSort(s, i + 1, r);

  39. }

  40. }

  41. 棋盘覆盖

  42. #include<iostream>  
    using namespace std;  
    int tile=1;                   //L型骨牌的编号(递增)  
    int board[100][100];  //棋盘  
    /***************************************************** 
    * 递归方式实现棋盘覆盖算法 
    * 输入参数: 
    * tr--当前棋盘左上角的行号 
    * tc--当前棋盘左上角的列号 
    * dr--当前特殊方格所在的行号 
    * dc--当前特殊方格所在的列号 
    * size:当前棋盘的:2^k 
    *****************************************************/  
    void chessBoard ( int tr, int tc, int dr, int dc, int size )  
    {  
        if ( size==1 )    //棋盘方格大小为1,说明递归到最里层  
            return;  
        int t=tile++;     //每次递增1  
        int s=size/2;    //棋盘中间的行、列号(相等的)  
        //检查特殊方块是否在左上角子棋盘中  
        if ( dr<tr+s && dc<tc+s )              //在  
            chessBoard ( tr, tc, dr, dc, s );  
        else         //不在,将该子棋盘右下角的方块视为特殊方块  
        {  
            board[tr+s-1][tc+s-1]=t;  
            chessBoard ( tr, tc, tr+s-1, tc+s-1, s );  
        }  
        //检查特殊方块是否在右上角子棋盘中  
        if ( dr<tr+s && dc>=tc+s )               //在  
            chessBoard ( tr, tc+s, dr, dc, s );  
        else          //不在,将该子棋盘左下角的方块视为特殊方块  
        {  
            board[tr+s-1][tc+s]=t;  
            chessBoard ( tr, tc+s, tr+s-1, tc+s, s );  
        }  
        //检查特殊方块是否在左下角子棋盘中  
        if ( dr>=tr+s && dc<tc+s )              //在  
            chessBoard ( tr+s, tc, dr, dc, s );  
        else            //不在,将该子棋盘右上角的方块视为特殊方块  
        {  
            board[tr+s][tc+s-1]=t;  
            chessBoard ( tr+s, tc, tr+s, tc+s-1, s );  
        }  
        //检查特殊方块是否在右下角子棋盘中  
        if ( dr>=tr+s && dc>=tc+s )                //在  
            chessBoard ( tr+s, tc+s, dr, dc, s );  
        else         //不在,将该子棋盘左上角的方块视为特殊方块  
        {  
            board[tr+s][tc+s]=t;  
            chessBoard ( tr+s, tc+s, tr+s, tc+s, s );  
        }  
    }  
      
    void main()  
    {  
        int size;  
        cout<<"输入棋盘的size(大小必须是2的n次幂): ";  
        cin>>size;  
        int index_x,index_y;  
        cout<<"输入特殊方格位置的坐标: ";  
        cin>>index_x>>index_y;  
        chessBoard ( 0,0,index_x,index_y,size );  
        for ( int i=0; i<size; i++ )  
        {  
            for ( int j=0; j<size; j++ )  
                cout<<board[i][j]<<"/t";  
            cout<<endl;  
        }  
    }  
     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值