算法基础-归并排序

在介绍 归并排序(merge sort) 算法之前, 我们先学习一下 分治法 .

算法的设计技术有很多, 插入排序 使用了 增量 方法:
在排序子数组 a[0..i-1] 后, 将单个元素 a[i] 插入到子数组的适当位置, 产生排序好的子数组 a[0..i].

这里我们学习另一种算法设计方法: 分治法.

1 分治法(The divide-and-conquer approach)

很多算法在 结构上是递归的: 为了解决一个给定的的问题, 算法 多次递归地调用自身 以解决紧密相关的若干 子问题.

这些算法典型地遵循 分治法(Divide-and-Conquer) 的思想:
将原问题分解为 多个规模较小但类似原问题的 子问题, 递归地求解这些子问题, 然后再合并这些子问题的解 以求得原问题的解.

分治模式(divide-and-conquer paradigm) 在每层递归时都有 3 个步骤:
分解(Divide) 原问题为若干子问题. 这些子问题都是原问题的规模较小的实例.
解决(Conquer) 这些子问题, 递归地求解各个子问题. 当子问题规模足够小时, 可以直接求解(不用递归).
合并(Combine) 这些子问题的解 成 原问题的解.

2 归并排序(merge sort)

归并排序(merge sort) 算法完全遵循 分治模式:
分解(divide): 分解待排序的 n 个元素的序列 成 各具 n/2 个元素的 两个子序列.
解决(conquer): 使用归并排序递归地排序 两个子序列.
合并(combine): 合并 两个已排好序的子序列 以 产生已排好序的答案.

归并排序的关键在于 合并.
这个合并过程就好比:
桌上有两堆牌, 每堆都排好序(最小的在最上面), 现在要把两堆合成一堆. 我们可以比较两堆的最上面的一张牌, 把小的一张取出放在手上; 重复这个动作最终会有一个堆的牌会先取完, 这时把另外一个堆的牌全部依次放在手上. 这样手上的牌就是 两堆牌合并后的 有序的牌.

我们通过一个辅助过程 merge(a[], begin, mid, end) 来完成合并. 其中 a[] 是一个数组, begin 是数组中需要合并的起始下标, end 是需要合并的终点下标, midbeginend 的中间下标. begin <= mid < end.
这个过程的示意图:
a[begin..end]中分出 left[begin..mid]right[mid+1..end] 两个数组:
mergesort_1
循环比较 left[]right[] 的元素(黄色水彩标出的元素), 把小的元素放入 a[] 并把响应的下标往后移(lr), 图中绿色水彩标出的元素表示已经排好序:
mergesort_2

归并排序代码实现:

public class MergeSort2 {
    public void mergeSort(int[] a, int begin, int end) {
        if (begin < end) {
            int mid = (begin + end) / 2;
            mergeSort(a, begin, mid);
            mergeSort(a, mid + 1, end);
            merge(a, begin, mid, end);
        }
    }

    private void merge(int[] a, int begin, int mid, int end) {
        int[] left = new int[mid - begin + 1];
        int[] right = new int[end-mid];
        for (int i = 0; i < left.length; i++) {
            left[i] = a[begin + i];
        }
        for (int i = 0; i < right.length; i++) {
            right[i] = a[mid + 1 + i];
        }

        int l = 0, r = 0;
        int i = begin;
        while (i <= end) {
            if (l >= left.length || r >= right.length) {
                break;
            }
            if (left[l] <= right[r]) {
                a[i++] = left[l++];
            } else {
                a[i++] = right[r++];
            }
        }

        if (l >= left.length) { // 左边序列元素读完, 直接把右边的序列依次放入a中
            while (r < right.length) {
                a[i++] = right[r++];
            }
        } else { // 右边序列元素读完, 直接把左边的序列依次放入a中
            while (l < left.length) {
                a[i++] = left[l++];
            }
        }
    }

    public static void main(String[] args) {
        int[] a = {4, 5, 1, 2, 7, 11, 3};
        MergeSort2 mergeSort = new MergeSort2();
        mergeSort.mergeSort(a, 0, a.length - 1);
        System.out.println(Arrays.toString(a));
    }
}

merge 方法看似很长, 其实思想很简单.

由以上 分析/代码 可知, 归并排序是 稳定的排序算法. 不会因a[]的顺序 对时间复杂度产生什么影响.

因为merge 操作需要O(n)的时间, 递归产生的递归树高 lgn, 树的每层调用代价都是 n, 所以归并排序是时间复杂度是 O(nlgn).

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值