研磨算法:排序之归并排序

研磨算法:排序之归并排序

标签(空格分隔): 研磨算法

  • 研磨算法:排序之归并排序
    • 理解归并排序
      • 分治模型
    • 代码演示
    • 算法分析


理解归并排序

归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法是分治(divide-and-conquer)的经典应用

简单来说就是将一个要排序的数组,先递归地将其分成两半,两半分别排序,然后归并起来。

分治模型

我们将整个数组递归地进行分割,不断二分,直到分隔为独立的元素。如

                 [8,4,5,7,1,3,6,2]
分为         [8,4,5,7]          [1,3,6,2]
分为      [8,4]     [5,7]     [1,3]    [6,2]
分为    [8]  [4]  [5]  [7]  [1]  [3]  [6]  [2]

然后对于最小的子问题,即每个独立元素,不断向上两两合并。

        [8]  [4]  [5]  [7]  [1]  [3]  [6]  [2]
合并      [4,8]     [5,7]     [1,3]    [2,6]
合并         [8,4,5,7]          [1,3,6,2]
合并             [1,2,3,4,5,6,7,8]

可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。

对于分来说,没有什么难点,我们使用递归的形式。

对于治来说,我们需要将两个已经有序的子序列合并成一个有序序列,我们先将所有元素复制到一个辅助数组中,将辅助数组分隔成两个待合并的数组。然后比较两个待合并数组的采用如下策略:
1. 在两个待合并数组中的头结点放置两个指针,并比较元素
2. 将小的那个放回的原数组中,并移动指针到下一个
2. 继续比较,直到某一边取尽,然后取另一边的元素

比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。

1. [4,5,7,8] [1,2,3,6]  比较4,1  得到 [1]
2. [4,5,7,8] [1,2,3,6]  比较4,2  得到 [1,2]
3. [4,5,7,8] [1,2,3,6]  比较4,3  得到 [1,2,3]
4. [4,5,7,8] [1,2,3,6]  比较4,6  得到 [1,2,3,4]
5. [4,5,7,8] [1,2,3,6]  比较5,6  得到 [1,2,3,4,5]
6. [4,5,7,8] [1,2,3,6]  比较7,6  得到 [1,2,3,4,5,6]
7. [4,5,7,8] [1,2,3,6]  取7  得到 [1,2,3,4,5,6,7]
8. [4,5,7,8] [1,2,3,6]  取8  得到 [1,2,3,4,5,6,7,8]

代码演示

对于复杂代码的书写,我们一般遵循一个原则,即:先写主干,再写各个小方法。

对于该算法,我们先写出主干函数mergeSort,然后再写sort,再写merge方法

package sort;

import java.util.Arrays;

/**
 * Created by japson on 4/18/2018.
 */
public class MergeSort {

    // 归并所需的辅助数组
    private static int[] temp;

    public static void main(String[] args) {
        int[] a =  {9,8,7,6,5,4,3,2,1};
        sort(a);
        System.out.println(Arrays.toString(a));
    }

    // 一般我们都使用一个参数的方法,在其内部再重载
    public static void sort(int[] a) {
        // 为临时数组等长赋值
        temp = new int[a.length];
        sort(a,0,a.length-1);
    }

    // 排序算法,主要是递归
    public static void sort(int[] a,int lo,int hi) {
        // 递归的边界条件 注意 <=
        if (hi <= lo) return;
        int mid = (lo + hi)/2;
        sort(a,lo,mid);     // 左边归并排序,使得左子序列有序
        sort(a,mid+1,hi);   // 右边归并排序,使得右子序列有序
        merge(a,lo,mid,hi); // 将两个有序子数组合并操作
    }

    // 归并算法 使用两个指针,从开头和中间扫描比较,将较小的复制的原数组中
    private static void merge(int[] a, int lo, int mid, int hi) {
        int i = lo;     // 左序列指针
        int j = mid + 1;    // 右序列指针

        // 必须是k=hi,因为hi是8,而一共是第9个
        for (int k = lo ; k <= hi ; k++) {
            temp[k] = a[k];
        }

        // 赋值回a数组
        for (int k = lo ; k <= hi ; k++) {
            if (i > mid) {
                a[k] = temp[j];
                j ++;
            } else if (j > hi) {
                a[k] = temp[i];
                i++;
            } else if (temp[i] > temp[j]) {
                a[k] = temp[j];
                j++;
            } else {
                a[k] = temp[i];
                i++;
            }
        }
    }
}

算法分析

归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。

java中Arrays.sort()采用了一种名为TimSort的排序算法,就是归并排序的优化版本。

每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。

你可能感兴趣的:(研磨算法)