【算法】归并排序

算法-归并排序


前置知识
  • 递归
  • 分治

思路

我们现在有一个序列,怎么对它排序?
这是一个非常经典的问题,这里我们使用一个经典的分治算法——归并排序解决。

这里有一个序列(为了便于分治,此处使用 8 8 8 个元素),要对它升序排序
3 7 1 4 5 8 6 2 \begin{array}{cc} 3&7&1&4&5&8&6&2 \end{array} 37145862
利用分治的思想分为两个区间
3 7 1 4 ∣ 5 8 6 2 \begin{array}{cc} 3&7&1&4&|&5&8&6&2 \end{array} 37145862
一直分到底
3 ∣ 7 ∣ 1 ∣ 4 ∣ 5 ∣ 8 ∣ 6 ∣ 2 \begin{array}{cc} 3&|&7&|&1&|&4&|&5&|&8&|&6&|&2 \end{array} 37145862
明显,现在每一个区间都已经排好序了。
我们知道,分治是把一个问题分成两个子问题求解,再将得到的结果组合为答案
所以,最底层的子问题解决了,怎么进行合并
下面我们以两个有序序列来演示归并排序的贪心合并。
1 3 4 6 ∣ 2 5 7 8      a n s : \begin{array}{cc} 1&3&4&6&|&2&5&7&8 \end{array}\>\>\>\>ans: 13462578ans:
两边都是有序序列,所以答案序列的最小元素一定是两个序列最小元素中更小的那个。
1 < 2 ,先加入 1 3 4 6 ∣ 2 5 7 8      a n s : 1 1<2\text{,先加入 1}\\\begin{array}{cc} 3&4&6&|&2&5&7&8 \end{array}\>\>\>\>ans:\begin{array}{cc} 1 \end{array} 1<2,先加入 13462578ans:1
这时两边仍是有序序列,所以重复操作
3 > 2 ,先加入 2 3 4 6 ∣ 5 7 8      a n s : 1 2 3 < 5 ,先加入 3 4 6 ∣ 5 7 8      a n s : 1 2 3 4 < 5 ,先加入 4 6 ∣ 5 7 8      a n s : 1 2 3 4 6 > 5 ,先加入 5 6 ∣ 7 8      a n s : 1 2 3 4 5 6 < 7 ,先加入 6 ∣ 7 8      a n s : 1 2 3 4 5 6 3>2\text{,先加入 2}\\\begin{array}{cc} 3&4&6&|&5&7&8 \end{array}\>\>\>\>ans:\begin{array}{cc} 1&2 \end{array}\\3<5\text{,先加入 3}\\\begin{array}{cc} 4&6&|&5&7&8 \end{array}\>\>\>\>ans:\begin{array}{cc} 1&2&3 \end{array}\\4<5\text{,先加入 4}\\\begin{array}{cc} 6&|&5&7&8 \end{array}\>\>\>\>ans:\begin{array}{cc} 1&2&3&4 \end{array}\\6>5\text{,先加入 5}\\\begin{array}{cc} 6&|&7&8 \end{array}\>\>\>\>ans:\begin{array}{cc} 1&2&3&4&5 \end{array}\\6<7\text{,先加入 6}\\\begin{array}{cc} |&7&8 \end{array}\>\>\>\>ans:\begin{array}{cc} 1&2&3&4&5&6 \end{array} 3>2,先加入 2346578ans:123<5,先加入 346578ans:1234<5,先加入 46578ans:12346>5,先加入 5678ans:123456<7,先加入 678ans:123456
此时左边的序列已经为空,那么说明右边序列剩下的都是最大的,所以依次加入答案序列
a n s : 1 2 3 4 5 6 7 8 ans:\begin{array}{cc} 1&2&3&4&5&6&7&8 \end{array} ans:12345678
这就是归并排序的贪心合并了。
使用这样的方法,我们继续前面的排序
3 ∣ 7 ∣ 1 ∣ 4 ∣ 5 ∣ 8 ∣ 6 ∣ 2 3 7 ∣ 1 4 ∣ 5 8 ∣ 2 6 1 3 4 7 ∣ 2 5 6 8 1 2 3 4 5 6 7 8 \begin{array}{cc} 3&|&7&|&1&|&4&|&5&|&8&|&6&|&2 \end{array}\\\begin{array}{cc} 3&7&|&1&4&|&5&8&|&2&6 \end{array}\\\begin{array}{cc} 1&3&4&7&|&2&5&6&8 \end{array}\\\begin{array}{cc} 1&2&3&4&5&6&7&8 \end{array} 37145862371458261347256812345678
排序成功!
归并排序学完了


算法参数
  • 平均时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)
  • 最好时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)(常数小)
  • 最坏时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)(常数大)
  • 空间复杂度: O ( n ) O(n) O(n)
  • 排序稳定性:稳定

非常稳


实现代码
  • 递归写法
void MergeSort(int a[],int l,int r){//对[l,r]归并排序
	int tmp[];//辅助数组
	if (l==r) return;//单个元素特判
	int mid=(l+r)/2;
	MergeSort(a,l,mid),MergeSort(mid+1,r);//分治
	int i=l,j=mid+1,k=l;
	//贪心合并区间
	while (i<=mid&&j<=r)
		if (a[i]<=a[j]) tmp[k]=a[i],i++,k++;
		else tmp[k]=a[j],j++,k++;
	while (i<=mid) tmp[k]=a[i],k++,i++;
	while (j<=r) tmp[k]=a[j],k++,j++;
	for (int p=l;p<=r;p++) a[p]=tmp[p];//将tmp中的元素复制回a
}
  • 迭代写法
void Merge(int a[],int l,int mid,int r){//合并区间
	int tmp[];//辅助数组
	int i=l,j=mid+1,k=l;
	//贪心合并区间
	while (i<=mid&&j<=r)
		if (a[i]<=a[j]) tmp[k]=a[i],i++,k++;
		else tmp[k]=a[j],j++,k++;
	while (i<=mid) tmp[k]=a[i],k++,i++;
	while (j<=r) tmp[k]=a[j],k++,j++;
	for (int p=l;p<=r;p++) a[p]=tmp[p];//将tmp中的元素复制回a
}
void MergeSort(int a[],int n){
	for (int i=1;i<n;i*=2){//子区间大小
		int s=i*2;//父区间大小
		for (int l=1,mid=i,r=s;mid<n;l+=s,mid+=s,r+=s)//枚举所有父区间
			Merge(a,l,mid,min(r,n));//合并区间
	}
}

练习
  • 洛谷P1177 【模板】排序
  • 洛谷P1908 逆序对

你可能感兴趣的:(算法,#,排序,算法,贪心算法,排序算法)