分治思想的三个步骤

  1. 划分问题
  2. 递归求解
  3. 合并问题

基础例子一

求最大连续和,当然这个问题最好的解决方法是DP,不过通过一些简单的例子来展现一种思想,更加清晰一些。

找一串数的最大连续和,那么先把这串数分成两个子串:子串a,子串b(习惯从中间分成两半)。那么最大连续和只可能有三种情形,第一种,在子串a中,第二种,在子串b中,第三种,横跨子串a和b。这样的算法在时间复杂度上位nlogn,还算可以。具体代码和详细说明如下:

1 int maxsum(int *A,int x,int y){
2 int i,m,v,L,R,max;
3 if(y-x==1) return A[x];//就剩一个元素,直接返回
4 //分治第一步:划分成[x,m)和[m,y)
5   m = x+(y-x)/2;
6 //分治第二步:递归求解
7   max = (maxsum(A,x,m)>maxsum(A,m,y))?
8 maxsum(A,x,m):maxsum(A,m,y);
9 //分治第三部:合并1:求从分界点开始往左的最大连续和L
10   v = L = 0;
11 for(i = m;i>=x;i--){
12 v+=A[i];
13 if(v>L)L = v;
14 }
15 //分治第三部:合并2:求从分界点开始往右最大连续和R
16   v = R = 0;
17 for(i = m+1 ;i<y;i++){
18 v+=A[i];
19 if(v>R)R = v;
20 }
21 //把子问题的解同(R+L)进行比较
22 max = (max>(L+R))?max:(L+R);
23 return max;
24 }

PS:上面代码有两个小技巧需要注意,第一个是采用左闭右开的区间表示一个范围,好处是在处理数组分割时比较自然:区间[x,y)被分割成[x,m)和[m,y),不需要在任何地方加减1,另外,空区间表示为[x,x),比[x,x-1]顺眼多了。第二个是去中间点的时候是x+(y-x)/2,而不是(x+y)/2是因为“/”运算是朝零方向,而不是向下取整。这一点在这段代码上意义不大,但是在有些地方,这点注意不到,会产生意想不到的错误。

基础例子二

归并排序

  1. 划分问题:把数列分成尽量相等的两半
  2. 递归求解:把两半元素分别排序
  3. 合并问题:把两个有序子数列合并成一个完整的序列
1 const int SIZE = 100;
2 void mergeSort(int *A,int x,int y){
3 if(y-x>1){//如果数组就一个元素,那么肯定是有序的
4 int m = x+(y-x)/2; //划分
5 int p = x,q = m,i = x;
6 //递归求解
7 mergeSort(A,x,m);
8 mergeSort(A,m,y);
9 //合并问题
10 int T[SIZE];//辅助空间
11 while(p<m||q<y){
12 if(q>=y||(p<m&&A[p]<A[q]))
13 T[i++] = A[p++];
14 else
15 T[i++] = A[q++];
16 }
17 for(i = x;i<y;i++)A[i] = T[i];//从辅助空间复制会A数组
18 }
19 }

PS:可以看到这个归并排序的写法就很简洁,尤其是在合并的时候,简洁那么必然会带了的问题是可读性不是那么好,其实我们课本上的归并写法也不错,虽然啰嗦但是非常容易理解,把合并过程中每一种情况独立的列出,实质上是一模一样的(这次考试还出了呢...)

const int SIZE = 100;
void mergeSort(int *A,int x,int y){
if(y-x>1){//如果数组就一个元素,那么肯定是有序的
int m = x+(y-x)/2; //划分
int p = x,q = m,i = x;
//递归求解
mergeSort(A,x,m);
mergeSort(A,m,y);
//合并问题
int T[SIZE];//辅助空间
for(i = x;i<y;i++){
if(p==m) T[i] = A[q++];//左子列越界
else if(q>=y) T[i] =A[p++];//右子列越界
else if(A[p]<A[q])T[i] = A[p++];//都没有越界
else T[i] = A[q++];
}
for(i = x;i<y;i++)A[i] = T[i];//从辅助空间复制会A数组
}
}
posted on 2011-07-04 10:28  geeker  阅读(242)  评论(0编辑  收藏  举报