首页 > 编程语言 >Javascript常见算法(二)

Javascript常见算法(二)

时间:2024-08-10 22:28:12浏览次数:10  
标签:current 常见 dummy Javascript next 链表 算法 let 节点

合并K个排序链表详解

 在JavaScript中合并K个已排序的链表是一个常见的算法问题,它可以通过多种方法解决,包括使用优先队列(通常通过最小堆实现)或直接两两合并。这里,我将详细解释这两种方法,并给出示例代码。

方法一:使用优先队列(最小堆)

这种方法的核心思想是利用一个最小堆来持续跟踪所有链表头节点中的最小值。每次从堆中取出最小元素,将其加入到结果链表中,并将其下一个节点(如果存在)加入到堆中。这个过程一直进行,直到堆为空。

步骤:
  1. 创建一个最小堆,用于存储所有链表的头节点。
  2. 从堆中取出最小元素,添加到结果链表中,并将其下一个节点(如果存在)添加到堆中。
  3. 重复步骤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个链表分成两半,分别合并成两个链表,然后再合并这两个链表。这个过程可以递归进行。

步骤:
  1. 如果K为1,直接返回该链表。
  2. 使用分治法将链表分为两部分,分别递归合并。
  3. 合并两个已合并的链表。
示例代码:
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(指向链表中下一个节点的指针)。

思路

为了交换两个相邻的节点,我们需要关注它们之间的连接关系。假设我们要交换节点AB,其中AB的前一个节点。在交换之前,Anext指向B,而Bnext指向某个节点(我们称之为C)。交换之后,Anext应该指向C,而Bnext应该指向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;  
}

解释

  1. 创建哑节点:哑节点(dummy node)的next属性指向链表的头节点。使用哑节点的好处在于,它允许我们统一处理头节点和其他节点,因为头节点在交换前可能不是任何节点的next属性的一部分。

  2. 遍历链表:通过while循环遍历链表,直到到达链表末尾或只剩下一个节点(无法再进行两两交换)。

  3. 交换节点:在每次迭代中,我们记录当前节点(current)的next节点(nodeA)和next.next节点(nodeB),然后交换这两个节点。交换操作涉及更新current.nextnodeA.nextnodeB.next的指向。

  4. 移动当前节点:完成交换后,将current移动到nodeA,以便下一轮迭代能够处理下一对相邻节点。

  5. 返回新的头节点:最后,由于头节点可能已被交换,因此返回哑节点的next属性作为新的头节点。

注意

  • 确保在遍历链表时检查current.nextcurrent.next.next是否存在,以避免访问空指针的next属性。
  • 使用哑节点可以简化边界条件的处理,特别是当需要修改头节点时。

 

标签:current,常见,dummy,Javascript,next,链表,算法,let,节点
From: https://blog.csdn.net/m0_55049655/article/details/141097257

相关文章

  • LeetCode 算法:最小栈 c++
    原题链接......
  • Kmeans聚类算法(用于魔方机器人的色片分类及应用拓展)
    K-means聚类是一种广泛使用的无监督学习算法,用于将数据点分成K个聚类。它的主要目标是最小化每个聚类内数据点到聚类中心的距离之和,从而使得每个聚类内的数据点相似性最大,而不同聚类之间的差异性最大。目录1.K-means聚类的基本步骤1.1选择K个初始中心点1.2将每个数......
  • 【无人艇】模拟退火算法红蓝无人水面艇舰队对抗演练和攻防【含Matlab源码 6808期】
    ✅博主简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,Matlab项目合作可私信或扫描文章底部QQ二维码。......
  • 【配送路径规划】遗传算法求解带时间窗的电动汽车配送路径规划(目标函数:最小成本;约束条
    ✅博主简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,Matlab项目合作可私信或扫描文章底部QQ二维码。......
  • 虚拟DOM如何被渲染产生的?(虚拟DOM和diff算法(上))
    虚拟DOM如何被渲染产生的?答:h函数h函数的使用:1.产生虚拟节点(vnode)2.h函数可以嵌套,从而得到虚拟DOM树1.产生虚拟节点import{init,classModule,propsModule,styleModule,eventListenersModule,h}from"snabbdom";//创建出patch......
  • flex常见属性容器
    display:flex将一个元素定义为Flex布局容器的属性flex-direction设置Flex容器主轴方向的属性justify-content用于定义Flex容器内子元素在主轴上对齐方式align-items用于定义Flex子元素在交叉轴上对齐方式flex-wrap控制子元素在主轴方向空间不足是否换行gap子元素之间间隙......
  • 力扣面试经典算法150题:移除元素
    移除元素今日的题目依旧是力扣面试经典算法150题中数组相关的题目:移除元素题目链接:https://leetcode.cn/problems/remove-element/description/?envType=study-plan-v2&envId=top-interview-150题目描述给定一个排序数组nums和一个值val,需要在原地移除数组中所有等......
  • 位运算在算法中的部分应用
    示例1:奇偶性判断publicclassBitwiseExample{publicstaticvoidmain(String[]args){intnumber=5;if((number&1)==1){System.out.println(number+"是奇数");}else{System.......
  • 【算法/学习】:flood算法
    ✨                         君子坐而论道,少年起而行之    ......
  • 算法题系列3
    题目描述一个应用启动时,会有多个初始化任务需要执行,并且任务之间有依赖关系,例如A任务依赖B任务,那么必须在B任务执行完成之后,才能开始执行A任务。现在给出多条任务依赖关系的规则,请输入任务的顺序执行序列,规则采用贪婪策略,即一个任务如果没有依赖的任务,则立刻开始执行,如果同时有......