归并排序精讲

 

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(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)

解决了上面的合并有序数列问题,再来看归并排序,其的基本思路就是将数组分成二组AB,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?

可以将AB组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。

[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,使得交换aiai+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;

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值