
本文详细介绍了如何利用最小堆高效合并 k 个已排序的链表。通过构建一个虚拟头节点和尾指针,并利用优先级队列动态维护各链表当前最小元素,逐步构建合并后的有序链表。文章将深入解析指针操作的机制,特别是`head`和`last`如何协同工作,确保合并过程的正确性,并提供完整的java代码示例及详细解释。
合并 K 个有序链表是一个经典的算法问题,目标是将 K 个已经排序的链表合并成一个单一的、同样有序的链表。一个直观但效率不高的方法是两两合并,但这种方法的时间复杂度较高。更高效的解决方案是利用最小堆(优先级队列)来管理所有链表的当前最小元素。
最小堆方法的核心思想是:在任意时刻,我们只需要知道 K 个链表中所有当前未处理节点的最小值。优先级队列(Min-Heap)能够高效地提供这一功能。
通过这种方式,我们每次都能确保将当前所有链表中的最小元素添加到结果链表,从而保证最终合并链表的有序性。
在构建结果链表时,通常会使用一个虚拟头节点(dummy head)和两个指针:head 和 last。理解这两个指针如何协同工作是实现此算法的关键。
Node head = new Node(0); // 虚拟头节点 Node last = head; // last 指向当前结果链表的尾部
初始状态:head 和 last 都指向同一个虚拟节点,其 data 为 0,next 为 null。这个虚拟节点本身不包含任何实际数据,其作用是简化对结果链表头部的处理。
head last ↓ ↓ ┌────────────┐ │ data: 0 │ │ next: null │ └────────────┘
添加第一个实际节点: 假设从最小堆中取出的第一个最小节点是 curr (例如 data: 42)。 执行 last.next = curr; 这行代码将 last 指向的节点的 next 字段设置为 curr。由于 head 和 last 最初指向同一个节点,因此 head 指向的节点的 next 字段也被更新了。
head last curr ↓ ↓ ↓ ┌────────────┐ ┌────────────┐ │ data: 0 │ │ data: 42 │ │ next: ─────────►│ next: null │ └────────────┘ └────────────┘
接着执行 last = last.next; 这行代码将 last 指针向前移动,使其指向刚刚添加到链表中的新节点 curr。此时,head 仍然指向虚拟头节点,而 last 已经指向了结果链表的第一个实际节点。
head last curr ↓ ↓ ↓ ┌────────────┐ ┌────────────┐ │ data: 0 │ │ data: 42 │ │ next: ─────────►│ next: null │ └────────────┘ └────────────┘
后续节点添加: 当循环继续,从堆中取出下一个最小节点 curr (例如 data: 50)。 执行 last.next = curr; 此时 last 指向的是 data: 42 的节点。这行代码将 data: 42 节点的 next 字段设置为 data: 50 的节点。
head last curr ↓ ↓ ↓ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ data: 0 │ │ data: 42 │ │ data: 50 │ │ next: ─────────►│ next: ─────────►│ next: null │ └────────────┘ └────────────┘ └────────────┘
执行 last = last.next;last 指针再次向前移动,指向 data: 50 的节点。
head last curr ↓ ↓ ↓ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ data: 0 │ │ data: 42 │ │ data: 50 │ │ next: ─────────►│ next: ─────────►│ next: null │ └────────────┘ └────────────┘ └────────────┘
整个过程中,head 始终保持指向最初的虚拟头节点。通过 head 我们可以访问到整个合并后的链表,而 last 则负责不断地在链表末尾追加新节点。最终,我们需要返回 head.next,因为虚拟头节点本身不包含有效数据。
以下是使用 Java 实现合并 K 个有序链表的完整代码示例。
import java.util.Comparator;
import java.util.PriorityQueue;
// 链表节点定义
class Node {
int data;
Node next;
Node(int key) {
data = key;
next = null;
}
}
// 节点比较器,用于最小堆
class NodeComparator implements Comparator<Node> {
@Override
public int compare(Node k1, Node k2) {
return Integer.compare(k1.data, k2.data); // 简化比较逻辑
}
}
class Solution {
/**
* 合并 K 个有序链表
* @param arr 包含 K 个链表头节点的数组
* @param K 链表的数量
* @return 合并后的有序链表的头节点
*/
public static Node mergeKList(Node[] arr, int K) {
// 使用优先级队列作为最小堆,并传入自定义比较器
PriorityQueue<Node> queue = new PriorityQueue<>(new NodeComparator());
// 创建一个虚拟头节点,简化结果链表的构建
Node head = new Node(0);
Node last = head; // last 指向当前结果链表的尾部
// 将所有 K 个链表的第一个节点(如果存在)加入最小堆
for (int i = 0; i < K; i++) {
if (arr[i] != null) {
queue.add(arr[i]);
}
}
// 处理 K=0 或所有链表为空的情况
if (queue.isEmpty()) {
return null;
}
// 当堆不为空时,循环执行以下操作
while (!queue.isEmpty()) {
// 从堆中取出当前最小的节点
Node curr = queue.poll();
// 将取出的节点添加到结果链表的尾部
last.next = curr;
last = last.next; // 移动 last 指针到新添加的节点
// 如果当前节点所属的链表还有后续节点,则将其加入堆中
if (curr.next != null) {
queue.add(curr.next);
}
}
// 返回虚拟头节点的下一个节点,即合并后链表的实际头节点
return head.next;
}
// 打印链表
public static void printList(Node node) {
while (node != null) {
System.out.print(node.data + " ");
node = node.next;
}
System.out.println();
}
public static void main(String[] args) {
int N = 3;
// 创建 K 个链表
Node[] a = new Node[N];
// 链表1: 1 -> 3 -> 5 -> 7
Node head1 = new Node(1);
a[0] = head1;
head1.next = new Node(3);
head1.next.next = new Node(5);
head1.next.next.next = new Node(7);
// 链表2: 2 -> 4 -> 6 -> 8
Node head2 = new Node(2);
a[1] = head2;
head2.next = new Node(4);
head2.next.next = new Node(6);
head2.next.next.next = new Node(8);
// 链表3: 0 -> 9 -> 10 -> 11
Node head3 = new Node(0);
a[2] = head3;
head3.next = new Node(9);
head3.next.next = new Node(10);
head3.next.next.next = new Node(11);
// 合并链表并打印结果
Node res = mergeKList(a, N);
if (res != null) {
System.out.print("合并后的链表: ");
printList(res); // 预期输出: 0 1 2 3 4 5 6 7 8 9 10 11
} else {
System.out.println("合并结果为空链表。");
}
}
}通过使用最小堆(优先级队列)和巧妙的虚拟头节点与尾指针管理,我们可以高效地将 K 个有序链表合并成一个单一的有序链表。这种方法在处理大量链表时表现出良好的性能,是解决此类问题的标准方案。理解 head 和 last 指针的工作机制,特别是它们如何共同构建链表结构,是掌握此算法的关键。
以上就是使用最小堆合并 K 个有序链表:原理与实现的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号