
本文详细介绍了如何在java中使用最小堆高效合并k个有序链表。文章阐述了该算法的核心思想、具体实现步骤,并通过代码示例展示了如何构建和操作链表。特别地,本文深入解析了在链表构建过程中,head和last这两个关键指针如何协同工作,确保合并后的链表正确连接,并澄清了head指针如何“感知”到last指针所做的修改。
合并K个已排序的链表是一个常见的算法问题,目标是将这些链表中的所有节点按升序排列,形成一个新的单一链表。一个直观但效率不高的方法是两两合并,其时间复杂度会较高。更优的解决方案是利用最小堆(优先队列)的特性。最小堆能够实时维护所有链表当前头节点中的最小值,从而确保我们每次都能取出全局最小的元素,并将其添加到结果链表中。
该策略的核心思想是:
在Java中实现这一算法,我们需要定义链表节点、自定义比较器以及主合并函数。
一个标准的单向链表节点包含数据和指向下一个节点的引用。
立即学习“Java免费学习笔记(深入)”;
class Node {
int data;
Node next;
Node(int key) {
data = key;
next = null;
}
}PriorityQueue在Java中默认实现的是最小堆,但它需要知道如何比较自定义对象(如Node对象)。因此,我们需要实现Comparator接口来定义节点的比较规则,即根据data字段进行升序比较。
import java.util.Comparator;
import java.util.PriorityQueue; // 导入PriorityQueue
class NodeComparator implements Comparator<Node> {
@Override
public int compare(Node k1, Node k2) {
if (k1.data > k2.data)
return 1; // k1 大于 k2
else if (k1.data < k2.data)
return -1; // k1 小于 k2
return 0; // k1 等于 k2
}
}这是算法的核心部分,负责协调最小堆和链表构建。
class GFG {
static Node mergeKList(Node[] arr, int K) {
// 使用自定义比较器初始化最小优先队列
PriorityQueue<Node> queue = new PriorityQueue<>(new NodeComparator());
// 创建一个虚拟头节点(dummy head),用于简化链表操作
Node head = new Node(0);
// last指针将始终指向当前结果链表的尾部
Node last = head;
// 将所有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();
// 将curr节点添加到结果链表的尾部
last.next = curr;
// 更新last指针,使其指向新添加的节点
last = last.next;
// 如果curr节点所属的链表还有后续节点,则将其下一个节点加入优先队列
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;
}
}
// 主函数示例
public static void main(String[] args) {
int N = 3;
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)
printList(res);
System.out.println(); // 输出结果:0 1 2 3 4 5 6 7 8 9 10 11
}
}在mergeKList函数中,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: 0,来自链表3)。 执行 last.next = curr; 时:
head last curr ↓ ↓ ↓ ┌────────────┐ ┌────────────┐ │ data: 0 │ │ data: 0 │ │ next: ─────────►│ next: 9 │ └────────────┘ └────────────┘
更新last指针: 紧接着执行 last = last.next; 时:
head last curr ↓ ↓ ↓ ┌────────────┐ ┌────────────┐ │ data: 0 │ │ data: 0 │ │ next: ─────────►│ next: 9 │ └────────────┘ └────────────┘
后续节点添加: 当循环继续,从优先队列中取出下一个最小节点(例如,`data
以上就是使用最小堆高效合并K个有序链表:Java实现与指针机制解析的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号