【分治法】-【归并排序】

分治法

分治法一般都是用【递归】来实现的

【递归】的原理,是用【栈】来实现的。
所以,递归一定要有边界条件(递归出口),否则会爆栈!

下图中的内容来自于《软件设计师教程》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)

  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值