合并K个排序链表详解
在JavaScript中合并K个已排序的链表是一个常见的算法问题,它可以通过多种方法解决,包括使用优先队列(通常通过最小堆实现)或直接两两合并。这里,我将详细解释这两种方法,并给出示例代码。
方法一:使用优先队列(最小堆)
这种方法的核心思想是利用一个最小堆来持续跟踪所有链表头节点中的最小值。每次从堆中取出最小元素,将其加入到结果链表中,并将其下一个节点(如果存在)加入到堆中。这个过程一直进行,直到堆为空。
步骤:
- 创建一个最小堆,用于存储所有链表的头节点。
- 从堆中取出最小元素,添加到结果链表中,并将其下一个节点(如果存在)添加到堆中。
- 重复步骤2,直到堆为空。
示例代码(使用JavaScript的PriorityQueue
库,或者你可以自己实现一个最小堆):
这里我们假设你已经有了一个PriorityQueue
的实现,它支持比较器来定义元素间的顺序。
// 假设 LinkedListNode 是链表节点的定义
class LinkedListNode {
constructor(value = 0, next = null) {
this.value = value;
this.next = next;
}
}
function mergeKLists(lists) {
// 假设 priorityQueue 是一个最小堆
let priorityQueue = new PriorityQueue((a, b) => a.value - b.value);
let dummy = new LinkedListNode();
let current = dummy;
// 将所有链表的头节点加入堆中
for (let list of lists) {
if (list) {
priorityQueue.enqueue(list);
}
}
// 不断从堆中取出最小节点,并加入结果链表
while (!priorityQueue.isEmpty()) {
let node = priorityQueue.dequeue();
current.next = node;
current = current.next;
if (node.next) {
priorityQueue.enqueue(node.next);
}
}
return dummy.next;
}
// 注意:上面的代码需要有一个 PriorityQueue 的实现
// 这里没有给出 PriorityQueue 的具体实现,因为它可能比较复杂
// 你可以使用现成的库,如 js-priority-queue,或者自己实现一个最小堆
方法二:分治法(两两合并)
分治法的思想是将K个链表分成两半,分别合并成两个链表,然后再合并这两个链表。这个过程可以递归进行。
步骤:
- 如果K为1,直接返回该链表。
- 使用分治法将链表分为两部分,分别递归合并。
- 合并两个已合并的链表。
示例代码:
function mergeTwoLists(l1, l2) {
let dummy = new LinkedListNode();
let current = dummy;
while (l1 && l2) {
if (l1.value < l2.value) {
current.next = l1;
l1 = l1.next;
} else {
current.next = l2;
l2 = l2.next;
}
current = current.next;
}
current.next = l1 || l2;
return dummy.next;
}
function mergeKListsRecursive(lists, left, right) {
if (left === right) return lists[left] || null;
let mid = Math.floor((left + right) / 2);
let leftList = mergeKListsRecursive(lists, left, mid);
let rightList = mergeKListsRecursive(lists, mid + 1, right);
return mergeTwoLists(leftList, rightList);
}
function mergeKLists(lists) {
return mergeKListsRecursive(lists, 0, lists.length - 1);
}
这两种方法各有优缺点。使用优先队列的方法在平均情况下更高效,特别是当链表长度差异很大时。而分治法则具有更好的空间效率,因为它只使用了常数的额外空间(不考虑递归栈)。选择哪种方法取决于具体的应用场景和性能要求。
两两交换链表中的节点
在JavaScript中,两两交换链表中的节点是一个常见的链表操作问题。这里我们主要讨论如何在一个单向链表中,将相邻的节点对进行交换。链表节点通常定义为包含至少两个属性的对象:value
(节点的值)和next
(指向链表中下一个节点的指针)。
思路
为了交换两个相邻的节点,我们需要关注它们之间的连接关系。假设我们要交换节点A
和B
,其中A
是B
的前一个节点。在交换之前,A
的next
指向B
,而B
的next
指向某个节点(我们称之为C
)。交换之后,A
的next
应该指向C
,而B
的next
应该指向A
,同时还需要处理链表中这些节点的前一个节点(如果存在)的next
指针,以确保链表的整体连续性不受影响。
示例代码
首先,我们定义链表节点的结构:
class ListNode {
constructor(value = 0, next = null) {
this.value = value;
this.next = next;
}
}
然后,实现两两交换链表节点的函数:
function swapPairs(head) {
// 创建一个哑节点(dummy node),方便处理头节点变化的情况
let dummy = new ListNode(0);
dummy.next = head;
let current = dummy; // current用于遍历链表
while (current.next && current.next.next) {
// 节点A
let nodeA = current.next;
// 节点B
let nodeB = current.next.next;
// 交换A和B
current.next = nodeB;
nodeA.next = nodeB.next;
nodeB.next = nodeA;
// 移动current到下一对要交换的节点之前
current = nodeA;
}
// dummy.next现在是新的头节点
return dummy.next;
}
解释
-
创建哑节点:哑节点(dummy node)的
next
属性指向链表的头节点。使用哑节点的好处在于,它允许我们统一处理头节点和其他节点,因为头节点在交换前可能不是任何节点的next
属性的一部分。 -
遍历链表:通过
while
循环遍历链表,直到到达链表末尾或只剩下一个节点(无法再进行两两交换)。 -
交换节点:在每次迭代中,我们记录当前节点(
current
)的next
节点(nodeA
)和next.next
节点(nodeB
),然后交换这两个节点。交换操作涉及更新current.next
、nodeA.next
和nodeB.next
的指向。 -
移动当前节点:完成交换后,将
current
移动到nodeA
,以便下一轮迭代能够处理下一对相邻节点。 -
返回新的头节点:最后,由于头节点可能已被交换,因此返回哑节点的
next
属性作为新的头节点。
注意
- 确保在遍历链表时检查
current.next
和current.next.next
是否存在,以避免访问空指针的next
属性。 - 使用哑节点可以简化边界条件的处理,特别是当需要修改头节点时。
标签:current,常见,dummy,Javascript,next,链表,算法,let,节点 From: https://blog.csdn.net/m0_55049655/article/details/141097257