Take a look at GW

【算法】归并排序

目录结构:

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就会互调位置,因此不稳定)。在选择归并和快排的时候,需要综合它们的时间复杂度,空间复杂度 和 稳定性 再做选择。

 

posted @ 2020-08-20 16:59  HDWK  阅读(319)  评论(0编辑  收藏  举报