【排序】归并排序|Java 刷题打卡

201 阅读2分钟

本文正在参加「Java主题月 - Java 刷题打卡」,详情查看活动链接

一、前言

归并排序:是由冯·诺伊曼提出的一种基于分治思想的高效排序算法。

它的算法思想是:

  1. 把当前序列平分成两个子序列
  2. 然后递归地对子序列进行排序
  3. 最后把排序好的子序列再合并成一个有序的序列

举栗,动图如下:

mergeSort.gif



二、知识点

知识点,如下:

  1. 时间复杂度
  2. 逆序对
  3. 实现:
    • 递归方式
    • 非递归方式

(1)时间复杂度

  • 最好情况:时间复杂度 O(N * logN)

  • 最坏情况:时间复杂度 O(N ^ 2),增量元素不互质,则小增量可能根本不起作用

  • 稳定性:不稳定。

    例如,原数组 {4, 1, 4}

    不稳定:排序过程中,第二个 4 排在了 第一个 4 前面。

排序总图,如图: 2021-05-2318-41-48.png


(2)逆序对

逆序对(inversion:对于下标 i < j,如果 arr[i] > a[j],则称 (i, j) 是一对逆序对。

举个栗子,序列 {34, 8, 64, 51, 32, 21} 有多少逆序对?

9对:

(34, 8), (34, 32), (34, 21), (64, 51), 

(64, 32), (64, 21), (51, 32), (51, 21), (32, 21)

可得定理:

  • 定理:任意 N 个不同元素组成的序列平均具有 N(N-1)/4 个逆序对
  • 定理:任何仅以交换相邻两元素来排序的算法,其平均时间复杂度为 O(N^2)

那么逆序对,有什么用呢?

  1. 代表了,需要交换的次数。

  2. 为提高算法效率提供基础

那么要提高算法效率,必须:

  1. 每次消去不止 1个逆序对

  2. 每次交换相隔较远的 2个元素


(3)实现

  1. 递归方式:
public class MergeSort {

    // Time: O(n * log(n)), Space: O(n)
    public void sortRecursive(int [] arr) {
        if (arr == null || arr.length == 0) return;

        int [] tmp = new int[arr.length];

        mergeSort(arr, 0, arr.length - 1, tmp);
    }

    private void mergeSort(int[] arr, int low, int high, int[] tmp) {
        if (low < high) {
            int mid = low + (high - low) / 2;
            mergeSort(arr, low, mid, tmp);
            mergeSort(arr, mid + 1, high, tmp);
            merge(arr, low, mid, high, tmp);
        }
    }
    
    private void merge(int[] arr, int low, int mid, int high, int[] tmp) {
        int i = low, j = mid + 1, k = 0;
        while (i <= mid && j <= high) {
            if (arr[i] <= arr[j]) tmp[k++] = arr[i++];
            else tmp[k++] = arr[j++];
        }
        while (i <= mid) tmp[k++] = arr[i++];
        while (j <= high) tmp[k++] = arr[j++];
        System.arraycopy(tmp, 0, arr, low, k);
    }
}
  1. 非递归方式
public class MergeSort {

    // Time: O(n * log(n)), Space: O(n)
    public void sortIterative(int [] arr) {

        if (arr == null || arr.length == 0) return;

        int n = arr.length;

        int [] tmp = new int[n];

        for (int len = 1; len < n; len = 2 * len) {
            for (int low = 0; low < n; low += 2 * len) {
                int mid = Math.min(low + len - 1, n - 1);
                int high = Math.min(low + 2 * len - 1, n - 1);
                merge(arr, low, mid, high, tmp);
            }
        }
    }
    
     private void merge(int[] arr, int low, int mid, int high, int[] tmp) {
        int i = low, j = mid + 1, k = 0;
        while (i <= mid && j <= high) {
            if (arr[i] <= arr[j]) tmp[k++] = arr[i++];
            else tmp[k++] = arr[j++];
        }
        while (i <= mid) tmp[k++] = arr[i++];
        while (j <= high) tmp[k++] = arr[j++];
        System.arraycopy(tmp, 0, arr, low, k);
    }
}