归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
[cpp] view plain copy
1. //将有序数组a[]和b[]合并到c[]中
2. void MemeryArray(int a[], int n, int b[], int m, int c[])
3. {
4. int i, j, k;
5.
6. i = j = k = 0;
7. while (i < n && j < m)
8. {
9. if (a[i] < b[j])
10. c[k++] = a[i++];
11. else
12. c[k++] = b[j++];
13. }
14.
15. while (i < n)
16. c[k++] = a[i++];
17.
18. while (j < m)
19. c[k++] = b[j++];
20. }
可以看出合并有序数列的效率是比较高的,可以达到O(n)。
解决了上面的合并有序数列问题,再来看归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?
可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。
[cpp] view plain copy
1. //将有二个有序数列a[first...mid]和a[mid...last]合并。
2. void mergearray(int a[], int first, int mid, int last, int temp[])
3. {
4. int i = first, j = mid + 1;
5. int m = mid, n = last;
6. int k = 0;
7.
8. while (i <= m && j <= n)
9. {
10. if (a[i] <= a[j])
11. temp[k++] = a[i++];
12. else
13. temp[k++] = a[j++];
14. }
15.
16. while (i <= m)
17. temp[k++] = a[i++];
18.
19. while (j <= n)
20. temp[k++] = a[j++];
21.
22. for (i = 0; i < k; i++)
23. a[first + i] = temp[i];
24. }
25. void mergesort(int a[], int first, int last, int temp[])
26. {
27. if (first < last)
28. {
29. int mid = (first + last) / 2;
30. mergesort(a, first, mid, temp); //左边有序
31. mergesort(a, mid + 1, last, temp); //右边有序
32. mergearray(a, first, mid, last, temp); //再将二个有序数列合并
33. }
34. }
35.
36. bool MergeSort(int a[], int n)
37. {
38. int *p = new int[n];
39. if (p == NULL)
40. return false;
41. mergesort(a, 0, n - 1, p);
42. delete[] p;
43. return true;
44. }
归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(N*logN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。
弱分治归并
归并的核心算法就是上面提到过的两个过程。分别是分治与合并。合并都好理解,那么什么是分治呢?下面就来逐一说明一下。
算法原理
弱分治归并排序算法中,我们主要说的是合并,因为这里的分治更像是分组。
背景
假设我们有序列 T0 = [ 4, 3, 6, 5, 9, 0, 8, 1, 7, 2 ]
那么,在一开始,我们的序列就被分成了 10 组,每一组的元素个数为 1。
排序过程图解
强分治归并
算法原理
强分治归并相比弱分治归并的不同点在于,强分治归并有没在一开始就对数组 T0 进行分组,而是通过程序来对 T0 进行分组,现在可以看一张强分治归并排序算法的过程图感受一下。
1. ///归并排序模板。记得和快排一块对着看。
2. #include<stdio.h>
3. #include<stdlib.h>
4. #include<iostream>
5. #include<string.h>
6. #include<cstring>
7. #include<string>
8. #include<math.h>
9. #include<algorithm>
10. #define LL long long
11. #define inf 0x3f3f3f3f
12. #define mod 1e9+7
13. const int maxn=1e5+5;
14. using namespace std;
15. int temp[maxn];
16. int num=0;///统计逆序对数的。
17. void Merge(int a[],int left ,int mid,int right)
18. {
19. int i=left,j=mid+1,n=0,length=right-left;///i开始为左半部分最左边,j为右半部分最左边。temp数组是从下标0开始存数。
20. while(i<=mid&&j<=right){
21. if(a[i]>a[j]){///左边比右边大。
22. temp[n++]=a[j++];
23. num+=mid-i+1;///从i到mid都是比a[j]大。
24. }
25. else{
26. temp[n++]=a[i++];
27. }
28. }
29. if(i>mid){///这里说明的是左边全部填满了(因为前面的判断条件是i<=mid,那就是天右边了。
30. while(j<=right){
31. temp[n++]=a[j++];
32. }
33. }
34. else{
35. while(i<=mid){
36. temp[n++]=a[i++];
37. }
38. }
39. for(int k=0;k<=length;k++){///最后赋值到原数组必须要有的。
40. a[left+k]=temp[k];
41. }
42. }
43. void mergesort(int a[],int left,int right)
44. {
45. if(left<right){
46. int mid=(left+right)/2;
47. mergesort(a,left,mid);
48. mergesort(a,mid+1,right);
49. Merge(a,left,mid,right);
50. }
51. }
52. int main()
53. {
54. int number[30] = {23,3,26,24,5,1,12,21,29,15,17,10,7,22,6,20,19,11,2,4,9,25,13,27,14,18,28,8,16,30};
55. mergesort(number,0,30-1);///初始化调用也要注意额。
56.
57. for(int i=0;i<30;i++){
58. printf("%d ",number[i]);
59. }
60. }
例题 1:
题意:给出每个人的编号,分数,和能力值大小,m次比赛,每次按分数优先,编号从小到大排序。每次比赛相邻两个人比,能力值大的分数加1,再排序。问第m次后 排名为k的人的编号。
Sample Input
1
2 4 2
7 6 6 7
10 5 20 15
Sample Output
1
代码:
struct node
{
int id,pi,sore;
bool operator <(const node &t)const
{
return sore>t.sore||(sore==t.sore&&id<t.id);
}
}a[N<<1],b[N],c[N];int t,n,m,k;
void solo()
{
int num1=0,num2=0;
for(int i=0;i<n;i+=2)
{
if(a[i].pi>a[i+1].pi)
{
b[num1]=a[i];
b[num1].sore++;
num1++;
c[num2++]=a[i+1];
}
else
{
b[num1]=a[i+1];
b[num1].sore++;
num1++;
c[num2++]=a[i];
}
}
int i=0,j=0;
int num=0;
while(i<num1&&j<num2)
{
if(b[i].sore>c[j].sore||(b[i].sore==c[j].sore&&b[i].id<c[j].id)) a[num++]=b[i++];
else a[num++]=c[j++];
}
while(i<num1) a[num++]=b[i++];
while(j<num2) a[num++]=c[j++];
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d",&n,&m,&k);
n*=2;
for(int i=0;i<n;i++)
{
scanf("%d",&a[i].sore);
a[i].id=i+1;
}
for(int i=0;i<n;i++)
{
scanf("%d",&a[i].pi);
}
sort(a,a+n);
while(m--)
{
solo();
}
printf("%d\n",a[k-1].id);
}
}
例题2 ;
Sample Input
3 1
2 2 1
3 0
2 2 1
Sample Output
1
2
题意:给出n个数,每次可以交换相邻的两个数,最多交换k次,求交换后最小的逆序数是多少。
分析:如果逆序数大于0,则存在1 ≤ i < n,使得交换ai和ai+1后逆序数减1。所以最后的答案就是max((inversion-k), 0)。利用归并排序求出原序列的逆序对数就可以解决问题了。
void Merge(long long l, long long m, long long r) //归并排序
{
long i = l, j = m + 1, t = 0;
while (i <= m && j <= r)
{
if (a[i]>a[j])
{
b[t++] = a[j++];
cnt += m - i + 1;//每次交换的距离;
}
else
{
b[t++] = a[i++];
}
}
while (i <= m)
b[t++] = a[i++];
while (j <= r)
b[t++] = a[j++];
for (long long i = 0; i<t; i++)
{
a[l + i] = b[i];//交换次序,使得变化后的a等于排序后的b;便于后面的比较;
}
}
void Mergesort(long long l, long long r)
{
if (l<r)
{
long long m = (l + r) / 2;
Mergesort(l, m);
Mergesort(m + 1, r);
Merge(l, m, r);
}
}
int main()
{
long long n, k;
while (scanf("%lld %lld", &n, &k) == 2)
{
cnt = 0;
for (int i = 0; i<n; i++) {
scanf("%lld", &a[i]);
}
Mergesort(0, n - 1);
if (cnt <= k)//判断cnt和k的值
printf("0\n");
else
printf("%I64d\n", cnt - k);//输出控制符
}
return 0;
}