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

    目录结构:

    contents structure

    1. 简介

    归并排序(MergeSort) 和快排的思想有相似之处。都是采用分治的思想,也就是,首先在一个数组中选择一个基准点,把数组分成两半,然后再对每一半再进行排序,递归直到所有数据都排好序。归并排序取分割点都是在数组的最中间,而且归并的过程也是采用了一个额外的数组变量来周转实现的。

    首先先用C语言实现一个归并排序的过程。

    #include <stdio.h>
    
    int temp_array[];        //临时数组
    
    void print(int*,int,int);
    void merge(int*,int,int,int,int);
    void mergeSort(int*,int,int);
    
    int main () 
    {
        int arr[] = {9,7,6,10,123,100,8,-1,0,6,-20,90,00,-110,31};
        size_t n = sizeof (arr) / sizeof (int);  //获取数组的长度
        //初始化数组,数组的长度为n,这里需要分配和排序数组一样的长度即可。
        //因为在归并过程中,可能用到的最大长度也就是n.
        memset (temp_array, 0, n * sizeof (int));
        
        //进行归并排序
        mergeSort (&arr, 0, n-1);
        
        print(&arr,0,n);
        
        return 0;
    }
    void mergeSort (int *arr, int begin, int end)
    {
        if (begin >= end)
            return;
        
        //获取中间元素的下标
        int middle = (begin + end) / 2;
        
        int left_begin = begin;
        int left_end = middle;
        int right_begin = middle + 1;
        int right_end = end;
        
        //对左边进行排序
        mergeSort (arr, left_begin, left_end);
        
        //对右边进行排序
        mergeSort (arr, right_begin, right_end);
        
        //将左右两边排好序的序列汇总成一个序列
        merge (arr, left_begin, left_end, right_begin, right_end);
    }
    void merge (int *arr, int left_begin, int left_end, int right_begin, int right_end)
    {
        size_t index = 0;
        
        //得到左边排好序的范围
        int left_index = left_begin;
        int left_index_max = left_end;
        
        //得到右边排好序的范围
        int right_index = right_begin;
        int right_index_max = right_end;
        
        //将两个范围的数据,按照升序,汇总到temp_array变量中。
        //左边和右边肯定有一边先退出条件,而另一边剩下的数据,是已经排好序的,
        //因此只需要将剩下的数据追加到temp_array即可。
        //比如:[1,4,5] 和 [1,3,7,8]
        while (left_index <= left_index_max && right_index <= right_index_max)
        {
            int left_val = arr[left_index];
            int right_val = arr[right_index];
            
            if (left_val < right_val)
            {
                temp_array[index++] = left_val;
                ++left_index;
            }
            else
            {
                temp_array[index++] = right_val;
                ++right_index;
            }
        }
        
        //比如: [1,4,5] 和 [1,3,7,8]
        //经过上面的合并,左边的数组先退出,这时候temp_array=[1,1,3,4,5]
        //右边还剩下[7,8], 只需要附加到temp_array末尾即可[1,1,3,4,5,7,8]
        if (left_index > left_index_max)  //左边先退出条件,将右边的数据追加到temp_array
            while (right_index <= right_index_max)
                temp_array[index++] = arr[right_index++];
        else  //右边先退出条件, 将左边的数据追加到temp_array
            while (left_index <= left_index_max)
                temp_array[index++] = arr[left_index++];
        
        //此时temp_array中的数据是排好了序的
        //最后将临时数组temp_array[0,right_end]中的数据,复制到arr中
        int arr_index = left_begin;
        int arr_index_max = right_end;
        size_t temp_index = 0;
        while (arr_index <= arr_index_max)
            arr[arr_index++] = temp_array[temp_index++];
    }
    void print(int *arr, int start, int end)
    {
        for(int index=start; index < end; ++index)
            printf("%d ",arr[index]);
    }

    点我在Online GDB在线编译, 是不是上面的代码不太好理解,别担心,下面笔者制作了一张gif图片,可以更加形象理解归并排序的过程。

     

    2. 归并排序的时间复杂度

    上面讨论了归并排序的实现过程和理论基础,接下来继续讨论归并排序的时间复杂度。归并排序和快速排序相似,接下来深入分析一下归并排序的时间复杂度,由于归并排序每次取的都是数列最中间的位置,于是我们可以得出如下的表达式:

    T(n)  = T(n/2) + T(n/2) + n

    这个表达式和快排最优情况下的表达式一样,这里我就不再解一遍了,详情请移步快速排序最优时间复杂度。 解上面表达式最终可以得到时间复杂度为: n logn

    因为归并排序总是取的最中间的位置,所以无论排序的数列是什么样的,数列的排序都只有一种情况。换句话说,归并排序的最差,最优平均时间复杂度都是O(n logn)

    3. 归并排序的空间复杂度

     上面讨论了时间复杂度,接下来讨论空间复杂度,这里的空间复杂度就比较简单了,因为整个算法只声明了一个额外的变量 temp_array ,大小为n。所以总的空间使用的空间,就是temp_array所占用的空间 加上 递归时压入栈的空间,也就是 n + logn, 因此归并的空间复杂度就是O(n).

    4. 总结

    这篇文章写的比较短,主要是省略了归并排序时间复杂度的推算,因为它和 快排最优情况下的时间复杂度推算 是一样的。而归并的空间复杂度也是比较简单的。最后,归并排序是一种稳定的算法,而快排是一种不稳定的算法(比如:1140,如果基准点选首元素,那么最终的结果第一个1和第二个1就会互调位置,因此不稳定)。在选择归并和快排的时候,需要综合它们的时间复杂度,空间复杂度 和 稳定性 再做选择。

  • 相关阅读:
    道路和航线
    Sorting It All Out
    Sightseeing Cows(0/1分数规划+Spfa判负环)
    【模板】缩点
    间谍网络
    Tarjan算法专练
    数论知识点总结
    博客迁移到博客园
    第一届CCPC河南省赛
    find程序实现
  • 原文地址:https://www.cnblogs.com/HDK2016/p/13495044.html
Copyright © 2011-2022 走看看