一文搞懂归并排序

207 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第20天,点击查看活动详情

一、前言

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

它的算法思想是:

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

举栗,动图如下:可视化归并

mergeSort.gif


实现方式有二:递归方式 和 非递归方式。

  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);
    }
}



二、题目

单链表排序(中)

leetcode 148

题干分析

这个题目说的是,给你一个单链表,你要写一个函数,对它进行排序,然后返回排序后的链表。

# 比如说,给你的单链表是:
4 -> 8 -> 2 -> 1
​
# 你要返回排序后的链表:
1 -> 2 -> 4 -> 8

思路解法

解法有二: 快排 和 归并排序。

  1. 方法二:归并排
  • 不断把链表切半成 2 个链表
  • 递归处理这 2 个链表
  • 最后把这 2 个排好序的链表合并起来

使用快慢指针:慢指针一次走一步,快指针一次走 2 步。

归并-链表.png

// Time: O(n*log(n)), Space: O(log(n)), Faster: 97.67%
public ListNode mergeSortList(ListNode head) {
    if (head == null || head.next == null) return head;
    ListNode fast = head, slow = head;
    while (fast.next != null && fast.next.next != null) { // 1. 切分成 2 个链表
        slow = slow.next;
        fast = fast.next.next;
    }
    ListNode right = mergeSortList(slow.next); // 2. 递归拆分:右侧边
    slow.next = null;
    ListNode left = mergeSortList(head);       // 2. 递归拆分:左侧边 
    return mergeTwoSortedLists(left, right);   // 3. 合并并排序
}
// 排序并合并两个链表
private ListNode mergeTwoSortedLists(ListNode l1, ListNode l2) {
    ListNode dummy = new ListNode(0), p = dummy;

    while (l1 != null && l2 != null) {
        if (l1.val < l2.val) {
            p.next = l1;
            l1 = l1.next;
        } else {
            p.next = l2;
            l2 = l2.next;
        }
        p = p.next;
    }

    if (l1 != null) p.next = l1;
    if (l2 != null) p.next = l2;
    return dummy.next;
}