归并排序
所谓归并排序是指将两个或两个以上有序的数列(或有序表),合并成一个仍然有序的数列(或有序表)。这样的排序方法经常用于多个有序的数据文件归并成一个有序的数据文件。归并排序的算法比较简单。
1. 基本思想
归并排序是用分治思想,分治模式在每一层递归上有三个步骤:
- 分解(Divide):将n个元素分成2个含n/2个元素的子序列(n为奇数时,有一个序列会多一)。
- 解决(Conquer):用合并排序法对两个子序列递归的排序。
- 合并(Combine):合并两个已排序的子序列已得到排序结果。
2. 实现逻辑
2.1 工作原理
① 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
② 设定两个指针,最初位置分别为两个已经排序序列的起始位置
③ 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
④ 重复步骤③直到某一指针到达序列尾
⑤ 将另一序列剩下的所有元素直接复制到合并序列尾
2.2 递归实现
① 将序列每相邻两个数字进行归并操作,形成floor(n/2)个序列,排序后每个序列包含两个元素
② 将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素
③ 重复步骤②,直到所有元素排序完毕
4. 复杂度分析
平均时间复杂度:O(nlogn)
最佳时间复杂度:O(n)
最差时间复杂度:O(nlogn)
空间复杂度:O(n)
排序方式:In-place
稳定性:稳定
#include<cstdio> const int maxn=500010; int a[maxn],c[maxn]; int n; long long ans=0; void msort(int l,int r){ if(l==r) return ; int mid=(l+r)/2; msort(l,mid);//分解 msort(mid+1,r); int i=l,j=mid+1,k=l; while(i<=mid&&j<=r){//合并 if(a[i]>a[j]) { // ans+=mid-i+1; c[k++]=a[j++]; } else c[k++]=a[i++]; } while(i<=mid) c[k++]=a[i++]; while(j<=r) c[k++]=a[j++]; for(int i=l;i<=r;i++) a[i]=c[i]; } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); msort(1,n); for(int i=1;i<=n;i++) printf("%d",a[i]); //printf("%lld\n",ans);//逆序对数 return 0; }
归并的空间复杂度就是那个临时的数组和递归时压入栈的数据占用的空间:n + logn;所以空间复杂度为: O(n)。
归并排序算法中,归并最后到底都是相邻元素之间的比较交换,并不会发生相同元素的相对位置发生变化,故是稳定性算法。
归并排序应用
问题1:求通过相邻位置的数字交换,使数列有序的最少交换次数?
问题2:求数列中的逆序对数。
这两个问题实际上是一个问题。
归并排序的交换次数就是这个数列的逆序对个数(也是使数列有序的相邻两元素交换的最少交换次数),为什么呢?
归并排序是将数列a[l,h]分成两半a[l,mid]和a[mid+1,h]分别进行归并排序,然后再将这两半合并起来。
在合并的过程中(设l<=i<=mid,mid+1<=j<=h),当a[i]<=a[j]时,并不产生逆序数;
当a[i]>a[j]时,在前半部分中比a[i]大的数都比a[j]大,将a[j]放在a[i]前面的话,通过相邻两数两两交换,交换次数为mid+1-I,即逆序数要加上mid+1-i。
因此,可以在归并排序中的合并过程中计算逆序数,也即求出了相邻两数交换的最少次数。
比如
i=1 mid=2 j=3
3 8 | 2 6
右边的2如果想放到3的前面,需要分别和8交换,再和3 交换,交换次数为2,即逆序对数为2=mid-i+1
A数列:3 2 1 4 B数列:2 3 1 4
下标: 1 2 3 4 下标: 1 2 3 4
A排序后:1 2 3 4 B排序后: 1 2 3 4
对应下标:3 2 1 4 对应下标:3 1 2 4
排序后如果A和B对应的下标相等,则结束,如果不相等则需要交换。
C[Ai下标]=Bi下标,
即如果c[i]=i,则什么也不用做,否则对c排序,使c[i]=i,求逆序对数即可
题目应用:洛谷:P1309,P1908,P1966