算法-归并排序
前置知识
- 递归
- 分治
思路
我们现在有一个序列,怎么对它排序?
这是一个非常经典的问题,这里我们使用一个经典的分治算法——归并排序解决。
这里有一个序列(为了便于分治,此处使用
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}
3714∣5862
一直分到底
3
∣
7
∣
1
∣
4
∣
5
∣
8
∣
6
∣
2
\begin{array}{cc} 3&|&7&|&1&|&4&|&5&|&8&|&6&|&2 \end{array}
3∣7∣1∣4∣5∣8∣6∣2
明显,现在每一个区间都已经排好序了。
我们知道,分治是把一个问题分成两个子问题求解,再将得到的结果组合为答案。
所以,最底层的子问题解决了,怎么进行合并?
下面我们以两个有序序列来演示归并排序的贪心合并。
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:
1346∣2578ans:
两边都是有序序列,所以答案序列的最小元素一定是两个序列最小元素中更小的那个。
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,先加入 1346∣2578ans: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,先加入 2346∣578ans:123<5,先加入 346∣578ans:1234<5,先加入 46∣578ans:12346>5,先加入 56∣78ans:123456<7,先加入 6∣78ans: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}
3∣7∣1∣4∣5∣8∣6∣237∣14∣58∣261347∣256812345678
排序成功!
归并排序学完了
算法参数
- 平均时间复杂度: 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));//合并区间
}
}