分治法分治,分而治之也。将原问题划分成多个规模较小结构和原问题相似的子问题,分别递归解决这些子问题,然后将所有子问题的解合并即可得到原问题的解。 Divide:将原问题划分成一系列子问题 Conquer:分别递归解决子问题,如果子问题足够小,则可直接解决。 Combine:将子问题的结解合并一起成为原问题的解。
很多时候算法的设计都要靠分治策略。比如归并排序,也叫合并排序。
来看一下二路归并。
Divide:将n个输入序列划分为两个n/2个元素的子序列
Conquer:用MergeSort递归解决这两个字序列
Combine:将这两个已排序的有序序列合并为一个完全排序的序列
源代码
void MergeSort(int array[],int start,int end )
{
int mid;
if(start<end) //必须要加以判断。如果只剩一个则直接返回
{
mid=start+(end-start)/2; //为了防止溢出
MergeSort(array,start,mid);
MergeSort(array,mid+1,end);
Merge(array,start,mid,end);
}
}
举个例子 初始序列是 2 8 7 1 3 5 6 4
void Merge(int array[],int start,int mid,int end)
{ ///合并有序的array[start-mid]与array[mid+1-end]
int step;
for(int i=start,j=mid+1;i<j&&j<=end;)
{ 第一序列是i至j-1;第二序列是j至end;初始化j=mid+1
while(i<j&&array[i]<=array[j])
{ //这些元素位置将保持不变,因为它小于第二序列的初值
i++;
}
step=j-i; //此时step就是序列一的长度,也是要移位的位数
while(j<=end&&array[j]<=array[i])
{ //如果这些元素小于此时第一序列的初值//则和第一序列整体交换,因为都是有序的
j++; //j有变化
}
LeftShift(array,i,j-1,step); //整体向左循环移位。注意此时j的值
i=j-step; //i继续向前 i=i+(j-i-step) 继续迭代
}
}
这个算法是原地排序,较利用辅助数组的算法难一些,稍加理解即可弄明白。 i之前的元素位置不变,j之后的未知。i--j-1元素要调整,移位而已。 例子 3 4 6 7 和 2 5 8 9
中间函数实现
void Reverse(int array[],int start,int end) //序列逆序
{
int temp;
for(int i=start,j=end;i<j;i++,j--)
{
temp=array[i];
array[i]=array[j];
array[j]=temp;
}
}
void LeftShift(int array[],int lo,int hi,int x)
{ //将序列向左循环移位x个位置
Reverse(array,lo,lo+x-1); //将前X个元素逆序
Reverse(array,lo+x,hi); //将后N-X个元素逆序array[x]是第x+1个元素
Reverse(array,lo,hi);
}
时间复杂度T(n)=2T(n/2)+O(n)=O(nlgn) 空间复杂度O(1)。|| 别的算法有时候也会O(n)。
递归树好比是最优划分。
不仅仅是MergeSort,很多算法都应用分治策略。Power a number problem 乘方问题:给你一个数,整数或者浮点数X,求X^n X^n=X*X*X*X*X*X*X*X*X*X.....X*X*X*X 如何求得?蛮力?那会有N-1次乘法的出现,会很费时间的。
这时候就可以想到用分治法。 X^n=X^n/2*X^n/2=(X^n/4*X^n/4)*(X^n/4*X^n/4)=(X*X)*(X*X)*...*(X*X) 易知 T(n)=2T(n/2)+O(n)=O(nlgn),即便n是奇数也一样道理。
Fibonaci数列
又称黄金分割数列。0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ... 如果设F(n为该数列的第n项(n∈N+): 显然这是一个线性递推数列。 很容易想到递归,是不是,多简单啊。
int Fibonaci(int n)
{
if (n==0)
return 0;
if (n==1)
return 1;
if (n>1)
return Fibonaci(n-1)+Fibonaci(n-2);
}
看起来时间复杂度不是很高 T(n)=T(n-1)+T(n-2)+O(1)+O(1) 其实T(n)是指数级的复杂度T(ƞ^n),ƞ=(√5+1)/2,因为递归有很多重复的运算。 比如F(4)=F(3)+F(2)=F(2)+F(1)+F(1)+F(0)=F(1)+F(0)+F(1)+F(1)+F(0)=3。这个可以通过备忘录法,自底向上解决。用一个一维数组保存就搞定了。 其实有这样一个公式 这个可以证明的。这样就和乘方问题一样了。很容易得到Fn的值了。时间复杂度也是O(nlgn)。
矩阵乘法 A,B都是N*N的矩阵,相乘得到C。 容易想到的是 这样3个循环嵌套,T(n)=O(n^3) 换个思路,我们可以利用分治策略,将矩阵分块。将一个矩阵分成4个小矩阵,这样A,B中都有4个小矩阵,这样相乘的次数就会减少很多。 a b c d e f g h 都是n/2*n/2的小矩阵。 →r=ae+bg; s=af+bh; t=ce+dg; u=cf+dh; 这样8次递归,4次求和解决 T(n)=8(n/2)+(n^2)=O(n^3) 时间复杂度没有改变,但是相乘次数减少了。 目前最优的解决办法的复杂度是T(n)=O(n^2.376)
VTST布局 假设芯片是一个二叉树,要把它放在集成电路上,希望占据的空间较小。求最小占据空间面积。 H(n)=H(n/2)+O(1)=O(lgn) W(n)=2W(n/2)+O(1)=O(n) 则S(n)=H(n)*W(n)=O(nlgn),浪费了不少的空间。总共N个节点,却占据了nlgn的空间。 有没有好的方法可以使分布更加的均匀一些呢,当然有。如何求得? 可以使其变成O(n)。这个需要高度和宽度的同时减少。
同样利用分治策略 此时W(n)=H(n)=L(n)=2L(n/4)+O(1) L(n)=O(√n) 则S(n)=O(n) Very Cool!!! 转载请注明出处http://blog.csdn.net/sustliangbo/article/details/9290161