分治算法学习 Divide and Conquer

分治思想:

     分治算法的思想就是 对于某些特定种类的问题  如果问题的规模很小,那么就直接解决,如果问题的规模比较大,那么就把问题先分解为规模小的但是问题相同的子问题 ,并且不断分解直到规模足够小,再递归地解决这些问题 如果原问题可分割成k个子问题,1<k≤n,且这些子问题都可解并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。 递归与分治经常是一起使用的 

能够用分治的情况 :

1.问题复杂性随规模减小而减小

2.问题具有最优子结构性质

    

       最优子结构:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。

例如,最短路径问题有以下最优子结构性质:如果一个节点x是到源节点ü的最短路径,同时又是到目的节点V的最短路径,则最短路径从u到v是结合最短路径:u到x和x到v。解决任意两点间的最短路径的算法的Floyd-Warshall算法和贝尔曼-福特是动态规划的典型例子。

3.分解的问题的解可以合并为该问题的解

  如果问题不能合就不能用分治,这时可以考虑贪心动态规划 

4.同一个分割级别的各个子问题之间不互相包含 是独立的 

    Fibonacci 就不是独立的  因为计算 F(9)=F(8)+F(7)时候已经  包含了 F(8)这个子问题 而由于用的是递归的思想 ,所以 F(8) 会被重复计算,效率不高 子问题互相重叠应该考虑用动态规划 

分治的具体步骤:

很多书都有介绍了 步骤就是:

1.分解 2.解决 3.合并

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

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

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

使用分治的经典问题:

二分搜索

大整数乘法

Strassen矩阵乘法

棋盘覆盖

mergesort 归并排序

快速排序

线性时间选择

最接近点对问题

循环赛日程表

汉诺塔

 

经典问题解决举例 归并排序

  

依据分治法设计程序时的思维过程

实际上就是类似于数学归纳法,找到解决本问题的求解方程公式,然后根据方程公式设计递归程序。
1、一定是先找到最小问题规模时的求解方法
2、然后考虑随着问题规模增大时的求解方法
3、找到求解的递归函数式后(各种规模或因子),设计递归程序即可。
 
参考算法导论上的第一章 第二章 "归并排序"
 
 
merge过程的伪代码
 
归并排序的整个过程: mergesort(A,a,b)
 1 /*************************************************************************
 2     > File Name: mergesort.cpp
 3     > Author: VOID_133
 4     > Created Time: 2014年08月06日 星期三 14时17分16秒
 5     > Content : Divide and Conquer Classic Problem 
 6                     MergeSort
 7  ************************************************************************/
 8 
 9 #include<iostream>
10 #include<cstdio>
11 using namespace std;
12 const int MAX=100000;
13 int L[MAX];
14 int R[MAX];
15 void mergesort(int* A,int first,int end);
16 void merge(int *A,int first,int mid,int end);
17 void mergesort(int* A,int first,int end)
18 {
19     if(first<end)
20     {
21         int mid=(first+end)/2;        
22         mergesort(A,first,mid);             
23         mergesort(A,mid+1,end);    //Solve the problem recursively
24         merge(A,first,mid,end);
25     }
26     return ;
27 }
28 
29 void merge(int *A,int first,int mid,int end)
30 {
31     int lenL=mid-first+1;
32     int lenR=end-mid;
33     for(int i=1;i<=lenL;i++) L[i]=A[first+i-1];
34     for(int i=1;i<=lenR;i++) R[i]=A[mid+i];
35     int li=1;
36     int ri=1;
37     int cnt=first;                        //the array we should merge is from A[first] to A[end] so we can only modify these value ,we shouldn'tmodify other A values
38     //The Problem has fixed
39     while(li<=lenL && ri <=lenR)
40     {
41         int tmp;
42         if(L[li]>R[ri])
43         {
44             tmp=R[ri];
45             ri++;
46         }
47         else
48         {
49             tmp=L[li];
50             li++;
51         }
52         A[cnt++]=tmp;
53     }
54     while(li<=lenL)
55     {
56         A[cnt++]=L[li++];    
57     }
58     while(ri<=lenR)
59     {
60         A[cnt++]=R[ri++];
61     }
62     return ;
63 }
64 
65 
66 int main(void)
67 {
68     int arr[MAX];
69     int n;
70     while(scanf("%d",&n)!=EOF && n)
71     {
72         for(int i=1;i<=n;i++) cin>>arr[i];
73         mergesort(arr,1,n);
74         for(int i=1;i<=n;i++) cout<<arr[i]<<" ";
75         cout<<endl;
76     }
77     return 0;
78 }

 

第一次写的时候 由于把合并的原理弄错了 导致代码运行结果异常
合并的时候 只能修改你要合并的那个区间里的元素的值不能修改别的地方的元素的值
 
归并排序是最简单的分治方法 问题分解为 2个 每个问题的规模是原先的 1/2
 

 

 

 

 

posted @ 2014-08-06 16:06  VOID修罗  阅读(863)  评论(0编辑  收藏  举报