排序系列(七)归并排序

归并排序(外部排序)

归并排序的基本原理是:
将大小为N的序列看成N个长度为1的子序列,
接下来将相邻子序列两两进行归并操作,
形成N/2(+1)个长度为2(或1)的有序子序列;
然后再继续进行相邻子序列两两归并操作,
如此一直循环,
直到剩下1个长度为N的序列,则该序列为原序列完成排序后的结果,如下图所示:

在这里插入图片描述

归并排序的核心在于归并操作的实现。
归并操作的过程如下:
首先申请额外的空间用于放置两个子序列归并后的结果,
接着设置两个指针分别指向两个已排序子序列的第一个位置,
然后比较两个指针指向的元素,将较小的元素放置到已申请的额外空间内,并将当前位置向后移动一格,
重复以上过程,直到某一个子序列的指针指向该序列的结尾。
这时候将另一个指针所指向序排序列的剩余元素全部放置到额外空间内,归并操作结束。

从特定角度看,归并排序也可以用分治法的思想去自下而上地理解,
就是将原序列划分成两个等长子序列,
再递归地排序这两个子序列,
最后再调用归并操作合并成一个完整的有序序列。

算法实现(C#):

// 入口函数
public static void MergeSort(int[] arr)
{
    int[] tmpArr = new int[arr.Length];
    MSort(arr, tmpArr, 0, arr.Length - 1);
}

// 核心递归函数
public static void MSort(int[] arr, int[] tmpArr, int l, int rightEnd)
{
    int center;
    // 计算中心位置
    center = (l + rightEnd) / 2;
    // 递归终止条件
    if (l < rightEnd)
    {
        // 递归解决左边
        MSort(arr, tmpArr, l, center);
        // 递归解决右边
        MSort(arr, tmpArr, center + 1, rightEnd);
        // 归并左右两个有序序列
        Merge(arr, tmpArr, l, center + 1, rightEnd);
    }
}

/// <summary>
/// 将两个有序序列合并为一个有序序列
/// </summary>
/// <param name="arr"></param>
/// <param name="tmpArr"></param>
/// <param name="l">左边起始位置</param>
/// <param name="r">右边起始位置</param>
/// <param name="rightEnd">右边终点位置</param>
public static void Merge(int[] arr, int[] tmpArr, int l, int r, int rightEnd)
{
    // 将arr[l]~arr[r-1]和arr[r]~arr[rightEnd]左右两个序列合并为一个有序序列
    // 左边终点位置
    int leftEnd = r - 1;
    // 合并有序序列起始位置
    int tmpIndex = l;
    // 元素数量
    int numCount = rightEnd - l + 1;

    // 合并两个序列到新序列
    while (l <= leftEnd && r <= rightEnd)
    {
        if (arr[l] <= arr[r])
            tmpArr[tmpIndex++] = arr[l++];
        else
            tmpArr[tmpIndex++] = arr[r++];
    }
    // 处理左边序列剩余元素
    while (l <= leftEnd)
        tmpArr[tmpIndex++] = arr[l++];
    // 处理右边序列剩余元素
    while (r <= rightEnd)
        tmpArr[tmpIndex++] = arr[r++];

    // 将结果复制回arr
    for (int i = 0; i < numCount; i++, rightEnd--)
        arr[rightEnd] = tmpArr[rightEnd];
}

由非递归的算法描述可以看到,每一趟归并操作(图7.3中的–行)需要进行0(N)次比较,
而一共将进行0(log2N)趟归并操作,因此整个归并排序的时间复杂度为0(Nlog2N),哪怕在最
坏情况下时间复杂度也是一样。递归的时间复杂度分析略复杂,不过结果也是0( Nlog2N)。
空间复杂度上,由于归并操作过程中需要额外的空间用于保存已排序的子序列,因此,如果
实现方法正确的话 ,整个归并排序的空间复杂度为0(N)。若使用递归方法进行实现并在Merge
函数内部申请空间,如果不及时释放,则可以证明将耗费0(Nlog2N)的额外空间;
此外,相对于快速排序和堆排序,归并排序虽然耗费更多的额外空间,但整体的排序过程
是稳定的,关键字值相同的两个元素在排序过程中并不会发生相对位置上的变化。

归并排序虽然看上去稳定而且时间复杂度不高,但是在实际应用中,开辟大块的额外空间并且将两个数组的元素来回复制却是很耗时的,
所以归并排序一般不用于内部排序。但它是进行外部排序的非常有用的工具。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值