zoukankan      html  css  js  c++  java
  • 排序算法 —— 归并排序

    归并排序算法

    1.划分问题:把序列分成元素个数尽量相等的两半。

    2.递归求解:把两半元素分别排序。

    3.合并问题:把两个有序表合并成一个。

    归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

    算法代码

    void merge_sort(int *A, int x, int y, int *T) {
        if (y - x > 1) //等价于x+1<y,即左半部分的右边界还在右半部分的左边界左边
        {
            int m = x + (y - x) / 2;
            //划分
            int p = x, q = m, i = x;
            merge_sort(A, x, m, T);
            //递归求左解
            merge_sort(A, m, y, T);
            //递归求右解
            while (p < m || q < y)
            {
                if (q >= y || (p < m && A[p] <= A[q])) T[i++] = A[p++];
                //从左半数组复制到临时空间
                else T[i++] = A[q++];
                //从右半数组复制到临时空间
            }
            for (i = x; i < y; ++i) A[i] = T[i];
            //从辅助空间复制回A数组
        }
    }
    

    动图演示

    在这里插入图片描述

    实战演练

    #include <iostream>
    
    using namespace std;
    int A0[15] = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};
    int T0[15];
    
    void merge_sort(int *A, int x, int y, int *T) {
        if (y - x > 1) //等价于x+1<y,即左半部分的右边界还在右半部分的左边界左边
        {
            int m = x + (y - x) / 2;
            //划分
            int p = x, q = m, i = x;
            merge_sort(A, x, m, T);
            //递归求左解
            merge_sort(A, m, y, T);
            //递归求右解
            while (p < m || q < y)
            {
                if (q >= y || (p < m && A[p] <= A[q])) T[i++] = A[p++];
                //从左半数组复制到临时空间
                else T[i++] = A[q++];
                //从右半数组复制到临时空间
            }
            for (i = x; i < y; ++i) A[i] = T[i];
            //从辅助空间复制回A数组
        }
    }
    
    int main() {
        for (int m = 0; m < 15; ++m) {
            cout << A0[m] << ' ';
        }
        cout << endl;
        merge_sort(A0, 0, 15, T0);
        for (int n = 0; n < 15; ++n) {
            cout << A0[n] << ' ';
        }
        cout << endl;
        return 0;
    }
    
    3 44 38 5 47 15 36 26 27 2 46 4 19 50 48
    2 3 4 5 15 19 26 27 36 38 44 46 47 48 50
    

    算法精讲

    首先,只要有一个序列非空,就要继续合并(while (p < m || q < y)),因此在比较时不能直接比较A[p]和A[q],因为可能其中一个序列为空,从而A[p]或者A[q]代表的是一个实际不存在的元素。
    ~如果第二个排序为空(此时第一个序列一定非空),复制A[p];
    ~否则(第二个序列非空),当且仅当第一个序列也非空,且A[p]<=A[q]时,才复制A[p]。

    算法分析

    归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。

    逆序对问题

    给一列数a1,a2,……,an,求它的逆序对数,即有多少个有序对(i,j),使得i<j但ai>aj。n可以高达106

    分析

    分治三步法:
    “划分问题”:把序列分成元素个数尽量相等的两半;
    “递归求解”:统计i和j均在左边或者右边的逆序对个数;
    “合并问题”:统计i在左边,但j在右边的逆序对个数。

    关键在于合并:如何求出i在左边,而j在右边的逆序对数目?
    统计的常见技巧是“分类”:只要对于右边的每个j,统计左边比它大的元素的个数f(i),则所有f(j)之和便是答案。

    归并排序可以“顺便”完成f(j)的计算:由于合并操作是从小到大进行的,当右边的A[j]复制到T中时,左边还没来得及复制到T的那些数就是左边所有比A[j]大的数,此时在累加器中加上左边元素个数m-p即可(左边所剩的元素在区间[p,m)中,因此元素个数为m-p)。

    代码

    #include <iostream>
    using namespace std;
    int cnt=0,T0[15];
    int A0[15] = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};
    void Reverse(int *A,int x,int y,int *T)
    {
        if (y>x+1)
        {
            int m=x+(y-x)/2;
            int p=x,q=m,i=x;
            Reverse(A,x,m,T);
            Reverse(A,m,y,T);
            while (p<m||q<y)
            {
                if (q>=y||(p<m&&A[p]<=A[q]))
                    T[i++]=A[p++];
                else
                {
                    T[i++]=A[q++];
                    cnt+=m-p;
                }
            }
            for (int i = x; i < y; ++i)
                A[i]=T[i];
        }
    }
    int main()
    {
        Reverse(A0, 0, 15, T0);
        cout<<"cnt="<<cnt<<endl;
        return 0;
    }
    
  • 相关阅读:
    memcached与.NET的融合使用(二)
    memcached与.NET的融合使用(一)
    使用window2003安装邮件服务器最新实际操作记录
    2014 -> 2015
    数据挖掘入门 资料和步骤
    CSDN 论坛招聘区是不是有潜规则?在Cnblog招个人试试...
    C#薪水和前途
    面试准备
    面试准备
    面试准备
  • 原文地址:https://www.cnblogs.com/AlexKing007/p/12338481.html
Copyright © 2011-2022 走看看