浅析归并排序
归并排序的思想
归并排序是一种采用了分治思想的算法。对于正在操作的区间,这个算法假定这个区间被分为左右两段,左段和右段都已经完成从小到大的排序,然后再利用双指针将两段已排序的数据进行合并。为了使左段右段都排好序,我们需要进行递归。
递归的 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..k−1]保存上述俩数组中从小到大排序的最小 k 个数
初始
k = 0 , t m p [ 0.. k − 1 ] k = 0, tmp[0..k-1] k=0,tmp[0..k−1]为空,显然成立
保持
假设某轮循环开始之前,循环不变式成立
若 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..k−1]保存从小到大排序的最小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..k−1]保存从小到大排序的最小 k k k个数
转自作者:ID醉生梦死
链接:https://www.acwing.com/solution/content/16778/
来源:AcWing ↩︎