浅析归并排序

浅析归并排序

归并排序的思想

归并排序是一种采用了分治思想的算法。对于正在操作的区间,这个算法假定这个区间被分为左右两段,左段和右段都已经完成从小到大的排序,然后再利用双指针将两段已排序的数据进行合并。为了使左段右段都排好序,我们需要进行递归。

递归的 b a s i c   c a s e basic \ case basic case:区间中只有一个元素或没有元素,此时结束递归

直接利用双指针对数组进行修改会导致数据的丢失,故需要定义一个临时数组来存放已经从小到大归并好的数据,随后再把数据从这个临时数组复制到原数组中。

归并排序的模板、算法正确性和边界问题

两种模板

下面给出模板I

const N = 1e5 + 10;
int tmp[N] // 临时数组

void merge_sort(int q[], int l, int r){
	if (l >= r) {return;}
	int mid = (l + r) >> 1;
	merge_sort(q, l, mid), merge_sort(q, mid + 1, r);
	int k = 0, a = l, b = mid + 1;
	while (a <= mid && b <= r){
		if (q[a] <= q[b]){tmp[k++] = q[a++];}
		else {tmp[k++] = q[b++];}
	}
	while (a <= mid) {tmp[k++] = q[a++];}
	while (b <= r) {tmp[k++] = q[b++];}
	// 复制的过程
	for (int i = l, j = 0; i <= r; i++){
		q[i++] = tmp[j++];
	}
}

模板来自闫总。
按照算法导论上的思路,有另一种模板,其中在双指针部分有不同:
模板II

int tmp1[N];
int tmp2[N];
int j1 = 0, j2 = 0;
for (int i = l; i <= mid; i++){
	tmp1[j++] = q[i];
}
for (int i = mid + 1; i <= r; i++){
	tmp2[j++] = q[i];
}
// 放置两张哨兵牌
tmp1[j1] = 1e5 + 1;
tmp2[j2] = 1e5 + 1;
// 要求哨兵牌的值在排序数据范围之外
int a = 0, b = 0, k = 0;
while (a <= j1 || b <= j2){
	if (q[a] <= q[b]){tmp[k++] = tmp1[a++];}
	else {tmp[k++] = tmp2[b++];}
} 
// 这时候临时数组最后一张牌将会是一张哨兵牌,但是不要紧,复制时候不要复制过去就行(复制前r - l + 1个数据就好)
for (int i = l, j = 0; i <= r;){
	q[i++] = tmp[j++];
}

但这个方法肉眼可见的繁琐…所以还是尽量不要用了。

边界问题

(1)当int mid = (l + r) >> 1;时,不可以用merge_sort(q, l, mid - 1), merge_sort(q, mid, r),因为这样定义的 m i d mid mid是下取整的,故而 m i d mid mid可以取到 l l l(e.g. 当区间中只有两个数据,则 m i d = l mid = l mid=l);
不过当int mid = (l + r + 1) >> 1(即上取整)时,我们是可以这么写的。但这时就不能写merge_sort(q, l, mid), merge_sort(q, mid + 1, r)因为 m i d mid mid可以取到 r r r

算法正确性1

采用循环不变式证明算法正确性:

循环不变式: t m p [ 0.. k − 1 ] tmp[0..k-1] tmp[0..k1]保存上述俩数组中从小到大排序的最小 k 个数
初始

k = 0 , t m p [ 0.. k − 1 ] k = 0, tmp[0..k-1] k=0,tmp[0..k1]为空,显然成立

保持

假设某轮循环开始之前,循环不变式成立

q [ i ] < = q [ j ] q[i] <= q[j] q[i]<=q[j], 则 t m p [ k ] = q [ i ] tmp[k] = q[i] tmp[k]=q[i]

其中, q [ i ] < = q [ i + 1.. m i d ] , q [ i ] < = q [ j ] < = q [ j + 1.. r ] q[i] <= q[i+1..mid], q[i] <= q[j] <= q[j+1..r] q[i]<=q[i+1..mid],q[i]<=q[j]<=q[j+1..r]

∴ q [ i ] ∴ q[i] q[i]是剩下的所有数中最小的一个

q [ i ] > q [ j ] q[i] > q[j] q[i]>q[j] 时,同理可以得到 t m p [ k ] = q [ j ] tmp[k] = q[j] tmp[k]=q[j] 是剩下数中最小的一个

t m p [ k ] tmp[k] tmp[k]是剩下数中最小的一个

k k k自增之后,下轮循环开始之前, t m p [ 0.. k − 1 ] tmp[0..k-1] tmp[0..k1]保存从小到大排序的最小k个数

终止

i > m i d i > mid i>mid j > r j > r j>r

q [ l . . m i d ] q[l..mid] q[l..mid] q [ m i d + 1.. r ] q[mid+1..r] q[mid+1..r] 其中一个数组的数都已遍历

t m p [ 0.. k − 1 ] tmp[0..k-1] tmp[0..k1]保存从小到大排序的最小 k k k个数


  1. 转自作者:ID醉生梦死
    链接:https://www.acwing.com/solution/content/16778/
    来源:AcWing ↩︎

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值