【算法】堆排序

127 阅读3分钟

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

一、前言

堆排序 是一种基于比较的排序算法,不稳定排序。

  • 它的算法思想和选择排序相似,都是把数组分为有序区和无序区,每次都从无序区中取最大值或最小值,放入有序区,直到整个数组有序。
  • 和选择排序的区别是,堆排序使用了一个二叉堆来组织无序区中的数据,以此减少从无序区中查找最值的时间。

heapSort.gif


二叉堆逻辑上是一棵完全二叉树,但实际上存储在一维数组中即可。

  • 在最大堆中:树上任意节点的值都大于等于它的子节点。
  • 在最小堆中:树上任意节点的值都小于等于它的子节点。

堆排序-2022-08-1220-38-06.png

堆在一维数组上父子节点关系:

  • 若父节点 i,则其子节点为 2i + 12i + 2
  • 若子节点 i,则其父节点为 (i - 1) / 2

堆排序过程:这里以最大堆为例

  1. 步骤一:构建堆。
  2. 步骤二:形成最大堆:保证父节点比子节点大。
  3. 步骤三:确定当前堆最大值,交换堆顶元素和堆尾元素。
  4. 步骤四:重复步骤二和步骤三,直至堆为空。

堆排序-2022-08-1221-28-27.png

代码如下:

public class HeapSort {

    // Time: O(n*log(n)), Space: O(1)
    public void sort(int[] arr) {
        if (arr == null || arr.length == 0) return;
        // 1. 构建堆: 最大堆
        buildMaxHeap(arr, arr.length - 1);

        for (int end = arr.length - 1; end > 0; --end) {
            swap(arr, 0, end);        // 确定当前堆最大值:交换
            siftDown(arr, 0, end - 1); // 筛选:形成最大堆
        }
    }

    private void buildMaxHeap(int[] arr, int end) {
        for (int i = end / 2; i >= 0; --i) {
            siftDown(arr, i, end); // 筛选:形成最大堆
        }
    }

    // 2. 筛选:形成最大堆
    // Time: O(log(n))
    private void siftDown(int[] arr, int i, int end) {
        int parent = i, child = 2 * parent + 1;
        while (child <= end) {
            if (child + 1 <= end && arr[child + 1] > arr[child]) ++child;
            if (arr[parent] >= arr[child]) break;
            swap(arr, parent, child);
            parent = child;
            child = 2 * parent + 1;
        }
    }

    private void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}



二、题目

(1)合并 K 个有序链表(难)

题干分析

这个题目说的是,给你 K 个递增排序的单链表,你要把它们合成一个链表,并且保持递增排序。合成链表的节点直接使用 K 个链表中的节点即可,无需创建新节点。

# 比如说,给你以下 3 个有序链表:
1 -> 2 -> 4
1 -> 4 -> 8
0 -> 2

# 合并后的有序链表是:
0 -> 1 -> 1 -> 2 -> 2 -> 4 -> 4 -> 8

思路解法

思路方法有三:

  1. 方法一:最小堆维护,一个个取,一个个构建关系
  2. 方法二:两两链表合并
  3. 方法三:分治方法

最优的方法是分治方法。

  1. 方法一:最小堆维护,一个个取,一个个构建关系
    1. 创建最小堆
    2. 遍历节点,放入最小堆中
    3. 从最小堆中取出,一个个取出构建链表
// Time: O(n*log(k)), Space: O(k), Faster: 70.34%
public ListNode mergeKSortedListsMinHeap(ListNode[] lists) {
    if (lists == null || lists.length == 0) return null;
    Queue<ListNode> q = new PriorityQueue<>((a, b) -> a.val - b.val); // 最小堆
    for (ListNode list: lists) {
        if (list != null) q.add(list);
    }
    ListNode dummy = new ListNode(0), p = dummy;

    while (!q.isEmpty()) {
        ListNode min = q.poll();
        p.next = min;
        p = p.next;
        if (min.next != null) q.add(min.next); // 节点下是否还有节点,有则放入堆中
    }
    return dummy.next;
}