携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第22天,点击查看活动详情
一、前言
堆排序 是一种基于比较的排序算法,不稳定排序。
- 它的算法思想和选择排序相似,都是把数组分为有序区和无序区,每次都从无序区中取最大值或最小值,放入有序区,直到整个数组有序。
- 和选择排序的区别是,堆排序使用了一个二叉堆来组织无序区中的数据,以此减少从无序区中查找最值的时间。
二叉堆逻辑上是一棵完全二叉树,但实际上存储在一维数组中即可。
- 在最大堆中:树上任意节点的值都大于等于它的子节点。
- 在最小堆中:树上任意节点的值都小于等于它的子节点。
堆在一维数组上父子节点关系:
- 若父节点
i
,则其子节点为2i + 1
、2i + 2
- 若子节点
i
,则其父节点为(i - 1) / 2
堆排序过程:这里以最大堆为例
- 步骤一:构建堆。
- 步骤二:形成最大堆:保证父节点比子节点大。
- 步骤三:确定当前堆最大值,交换堆顶元素和堆尾元素。
- 步骤四:重复步骤二和步骤三,直至堆为空。
代码如下:
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
思路解法
思路方法有三:
- 方法一:最小堆维护,一个个取,一个个构建关系
- 方法二:两两链表合并
- 方法三:分治方法
最优的方法是分治方法。
- 方法一:最小堆维护,一个个取,一个个构建关系
- 创建最小堆
- 遍历节点,放入最小堆中
- 从最小堆中取出,一个个取出构建链表
// 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;
}