分治法
分治法一般都是用【递归】来实现的
【递归】的原理,是用【栈】来实现的。
所以,递归一定要有边界条件(递归出口),否则会爆栈!
下图中的内容来自于《软件设计师教程》P422页:
递归的2个基本要素:
① 边界条件(递归出口)
② 递归模式(递归体)
【分治法】的基本思想:
上文中的重点整理如下:
当n较大时,排序问题就不那么容易处理了。
要想直接解决一个较大的问题,有时是相当困难的。
【分治法】的设计思想是将一个难以直接解决的大问题分解成一些规模较小的相同问题,以便各个击破,分而治之。
如果规模为n的问题可分解成k个子问题,1<k<n,这些子问题互相独立且与原问题相同。
【分治法】产生的子问题往往是原问题的较小模式,这就为递归技术提供了方便。
一般来说,分治算法在每一层递归上都有3个步骤:
(1) 分解。将原问题分解成一系列子问题。
(2) 求解。递归地求解各子问题。若子问题足够小,则直接求解。
(3) 合并。将子问题的解合并成原问题的解。
以上讨论的是【分治法】的基本思想和一般原则,下面用一个具体的例子(归并排序)来说明如何针对具体问题用分治思想来设计有效算法。
归并排序
假设:
有a1-a6六个元素要进行排序。
我们的思路是:
先分成2组,左右各3个。
再分组一次,变成4组,分别是:2个,1个,2个,1个。
然后对第一组和第三组再分一次,这样每组都只有一个元素了,然后开始归并。
归并:
先对第一组(6和2)和第三组(1和7)进行排序;
然后第一组排序后的结果(2,6)与第二组(5)进行排序,第三组排序后的结果(1,7)与第四组(4)进行排序,得到两个有3个排好序的元素的组——即(2,5,6)和(1,4,7);
对这两个有3个元素的大组再进行排序,得到一个有6个排好序的元素的组——即(1,2,4,5,6,7)。
最终,从拆分后的小问题开始着手,逐步归并得到一开始的大问题的解决结果。
归并排序算法MergeSort(A, p, r)
归并排序算法的C代码如下:
/*
该函数的作用是:
p表示要排序的左边界的下标
r表示要排序的右边界的下标
*/
void MergeSort(int A[], int p, int r){
int q; // 用来存储指向中间元素的下标位置,也是分成两组之后的左边一组的右边界
if(p<r){ // p<r表示这一组中至少还有2个元素,还需要接着分解;p=r时表示这一组中只有1个元素了,不再需要分解了
q = (p+r)/2;
MergeSort(A, p, q);
MergeSort(A, q+1, r);
Merge(A, p, q, r);
}
}
归并排序算法MergeSort(A, p, r)的原理是:
先把数组A拆分成(p,q)和(q+1,r)左右两个部分,再把这两个部分依次迭代拆分下去,直到拆分成独立的元素之后(达到条件p=r之后)才停止。
例如,数组A如下图所示。那么p=0,r=5。
计算可以得到中间值r=(p+r)/2=2。于是把数组A拆成(下标0-下标2)和(下标3-下标5)两个部分。
依次迭代拆下去,直到拆成独立的元素之后(达到条件p=r之后)才停止。
归并函数(合并函数)Merge(A, p, q, r)
函数Merge(A, p, q, r)的C代码如下:
/*
该函数的作用是:将两个已经排好序的数组,合并成一个(排好序的)大数组
需要合并的两个数组都是数组A的一部分,其中:
左边一组对应数组A中的下标:p ~ q
右边一组对应数组A中的下标:q+1 ~ r
*/
void Merge(int A[], int p, int q, int r){
int n1 = q - p + 1; // 【左边子序列】的长度
int n2 = r - q; // 【右边子序列】的长度
int i, j, k;
// 先把【左边子序列】复制到数组L[]中
for(i=0; i<n1; i++){
L[i] = A[p+i];
}
// 再把【右边子序列】复制到数组R[]中
for(j=0; j<n2; j++){
R[j] = A[(q+1)+j];
}
L[n1] = INT_MAX; // 整型可取的最大值2147483647(用于左右子序列比较最后一位的大小的时候)
R[n2] = INT_MAX; // 整型可取的最大值2147483647(用于左右子序列比较最后一位的大小的时候)
i = 0;
j = 0;
for(k=p; k<r+1; k++){
if(L[i]<R[j]){
A[k] = L[i];
i++;
} else {
A[k] = R[j];
j++;
}
}
}
合并(Merge)算法实现的原理:
例如,现在数组A的左右两个部分(两个长度为3的部分)已经完成了排序,现在要把它们两个合并成在一起,产生一个长度为6的有序数组。
接下来,我们要做的是:
先把【左边子序列】复制到数组L[]中,
再把【右边子序列】复制到数组R[]中。
接下来,从左到右依次比较数组L和数组R中元素的大小,选择小的元素放入数组A中(再接着用下一个元素去比较),直到把数组A放满。
合并结束!
代码运行的C代码如下:
// 运行代码
int main(){
int A[] = {4,1,3,6,7,5,2,9};
MergeSort(A,0,7);
int i;
for(i=0;i<8;i++){
printf("%d ",A[i]);
}
return 0;
}
归并排序的时间复杂度
归并排序的时间复杂度:O(n·logn)
归并排序的空间复杂度:O(n)