JavaScript算法——链表篇

链表理论基础

单链表

链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向 null(空指针)。

链表的入口节点称为链表的头结点也就是 head。

在这里插入图片描述

双链表

单链表中的指针域只能指向节点的下一个节点。

双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。

双链表既可以向前查询也可以向后查询。

在这里插入图片描述

循环链表

循环链表:就是链表收尾相连。

循环链表可以用来解决约瑟夫环问题。

在这里插入图片描述

链表的存储方式

链表是通过指针域的指针链接在内存中各个节点,所以,链表中的节点不是连续分布的,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

在这里插入图片描述

链表的定义

function ListNode(val, next) {
    this.val = (val === undefined ? 0 : val);
    this.next = (next === undefined ? null : next);
}

删除节点

在这里插入图片描述

如图所示:只需要将 C 节点的指针域指向 E 节点即可。

添加节点

在这里插入图片描述

如图所示:只需要将 C 节点的指针域指向 F 节点,F 节点的指针域指向 D 节点即可。

性能分析

查询(时间复杂度)插入/删除(时间复杂度)使用场景
数组O(1)O(n)数据量固定,频繁查询,较少增删
链表O(n)O(1)数据量不固定,频繁增删,较少查询

移除链表元素

移除链表元素

/*
	思路:创建虚拟头结点,从头开始与链表元素一一比对,直到找到对应节点
*/
var removeElements = function(head, val) {
	// 创建一个虚拟头节点
    const ret = new ListNode(0, head);
    let cur = ret;
    while(cur.next) {
        // 找到指定节点
        if(cur.next.val === val) {
            // 让指针指向下一个节点
            cur.next = cur.next.next;
            continue;
        }
        // 如果不是当前节点,则继续找下一个节点
        cur = cur.next;
    }
    return ret.next;
};

设计链表

设计链表

/*
	链表节点
*/
function LinkNode(val, next) {
    this.val = (val === undefined ? 0 : val); // 节点值
    this.next = (next === undefined ? null : next); // 节点的下一个节点
}

/*
	创建空链表
*/
var MyLinkedList = function() {
    this._size = 0; // 链表长度
    this._tail = null; // 头结点
    this._head = null; // 尾结点
};

/*
	获取目标结点
*/
MyLinkedList.prototype.getNode = function(index) {
    // 如果索引越界
    if(index < 0 || index >= this._size) return null;
    // 创建虚拟头节点
    let cur = new LinkNode(0, this._head);
    // 从头开始迭代,直到 index 减为0(找到了)
    while(index-- >= 0) {
        cur = cur.next;
    }
    return cur;
}

/*
	获取目标结点的值
*/
MyLinkedList.prototype.get = function(index) {
    // 如果索引越界
    if(index < 0 || index >= this._size) return -1;
    // 获取当前结点
    return this.getNode(index).val;
};

/*
	添加头结点
*/
MyLinkedList.prototype.addAtHead = function(val) {
    // 创建头结点
    const node = new LinkNode(val, this._head);
    // 把头结点设置为创建节点
    this._head = node;
    // 链表的长度+1
    this._size++;
    // 如果原链表是空链表
    if(!this._tail) {
        this._tail = node;
    }
};

/*
	添加尾结点
*/
MyLinkedList.prototype.addAtTail = function(val) {
    // 创建尾结点
    const node = new LinkNode(val, null);
    // 链表的长度+1
    this._size++;
    // 如果原链表不为空
    if(this._tail) {
        // 将原链表的尾结点的 next 指针指向 node
        this._tail.next = node;
        // 设置新的尾结点
        this._tail = node;
        return;
    }
    // 如果原链表为空
    this._tail = node;
    this._head = node;
};

/*
	添加目标结点
*/
MyLinkedList.prototype.addAtIndex = function(index, val) {
    // 索引越界
    if(index > this._size) return null;
    // 目标结点为头结点
    if(index <= 0) {
        this.addAtHead(val);
        return;
    }
    // 目标结点为尾结点的下一个结点(即添加一个尾结点)
    if(index == this._size) {
        this.addAtTail(val);
        return;
    }
    // 获取目标结点的上一个结点
    const node = this.getNode(index - 1);
    // 将 node 结点的 next 指针指向新结点,新结点的 next 指针指向目标结点
    node.next = new LinkNode(val, node.next);
    this._size++;
};

/*
	删除目标结点
*/
MyLinkedList.prototype.deleteAtIndex = function(index) {
    // 索引越界
    if(index < 0 || index >= this._size) return;
    // 如果是目标结点是头结点
    if(index == 0) {
        // 设置头结点指向头结点的下一个结点
        this._head = this._head.next;
        // 如果这个结点同时也是尾结点(即:链表只有一个结点)
        if(index == this._size - 1) {
            this._tail = this._head;
        }
        this._size--;
        return;
    }
    // 获取目标结点的下一个结点
    const node = this.getNode(index - 1);
    // 设置 node 结点的 next 指针指向目标结点的下一个结点
    node.next = node.next.next;
    // 如果目标结点是尾结点
    if(index == this._size - 1) {
        this._tail = node;
    }
    this._size--;
};

反转链表

反转链表

/*
	双指针
	思路:从头节点开始遍历,修改节点的指针指向前一个节点
*/
var reverseList = function(head) {
    // 如果是空链表或链表只有一个节点
    if(!head || !head.next) return head;
    let pre = null, cur = head;
    while(cur) {
        [cur.next, pre, cur] = [pre, cur, cur.next];
    }
    return pre;
};

/*
	递归
	思路:从头结点开始递归,修改节点的指针指向前一个节点
*/
var reverseList = function(head) {
    var reverse = function(pre, head) {
        // 如果链表为空链表
        if(!head) return pre;
        // temp:暂存节点
        const temp = head.next;
        // 修改节点的指针指向前一个节点
        head.next = pre;
        // 左指针改为当前结点
        pre = head
        return reverse(pre, temp);
    }
    return reverse(null, head);
};

/*
	递归
	思路:从尾节点开始递归,修改节点的指针指向前一个结点
*/
var reverseList = function(head) {
    var reverse = function(head) {
        // 节点为空 或 节点为链表最后一个结点
        if(!head || !head.next) return head;
        // 从后往前翻
        // next:head的下一个结点
        const next = reverse(head.next);
        [head.next, next.next] = [next.next, head];
        return head;
    }
    let cur = head;
    // 找到尾节点的前一个结点
    while(cur && cur.next) {
        cur = cur.next;
    }
    reverse(head);
    return cur;
};

两两交换链表中的节点

两两交换链表中的节点

/*
	思路:创建虚拟头节点,从头节点开始遍历,依次两两交换节点
*/
var swapPairs = function(head) {
    // ret:虚拟头节点
    // temp:临时节点
    let ret = new ListNode(0, head), temp = ret;
    // 当 临时结点的下一个节点 和 下下一个节点 都存在时
    while(temp.next && temp.next.next) {
        // cur:要交换的右节点
        // pre:要交换的左节点
        let cur = temp.next.next, pre = temp.next;
        // 交换节点
        [pre.next, cur.next, temp.next] = [cur.next, pre, cur];
        // 继续交换后续节点
        temp = pre;
    }
    return ret.next;
};

删除链表的倒数第N个节点

删除链表的倒数第N个节点

/*
	双指针
	思路:创建虚拟头结点,要找到倒数第n个节点,只需要找到正数第n个节点(右指针),然后从头结点(左指针)开始依次向右移动,当右指针移动到尾节点的下一个节点(null)时,此时的左指针就是倒数第n个节点
*/
var removeNthFromEnd = function(head, n) {
    // node:虚拟头节点
    let node = new ListNode(0, head);
    // pre:左指针
    // cur:右指针
    let pre = node, cur = head;
    // 找到第n个节点(cur)
    while(n > 0) {
        cur = cur.next;
        n--;
    }
    // 找到要删除的节点(pre)
    while(cur != null) {
        pre = pre.next;
        cur = cur.next;
    }
    // 删除节点
    pre.next = pre.next.next;
    return node.next;
};

链表相交

链表相交

/*
	思路:因为链表相交部分的长度是相同的,所以先求出链表的长度差,找到更长的链表的起始比较节点,将该节点与另一链表头节点进行比较,如果不同则继续比较后续节点,直到找到相同的节点
*/
var getIntersectionNode = function(headA, headB) {
    /*
    	获取链表长度
    	head:头结点
    */
    var getLength = function(head) {
        // 定义长度变量
        let len = 0;
        // 创建判断长度的节点
        let cur = head;
        // 求出链表的长度
        while(cur) {
            cur = cur.next;
            len++;
        }
        return len;
    }
    // lenA:链表A的长度
    // lenB:链表B的长度
    let lenA = getLength(headA), lenB = getLength(headB);
    // curA:链表A中的相交节点
    // curB:链表B中的相交节点
    let curA = headA, curB = headB;
    // 链表B更长
    while(lenA < lenB) {
        curB = curB.next;
        lenB--;
    }
    // 链表A更长
    while(lenA > lenB) {
        curA = curA.next;
        lenA--;
    }
    // 遍历 curA 和 curB,若相同则返回
    while(curA && curA !== curB) {
        curA = curA.next;
        curB = curB.next;
    }
    return curA;
};

环形链表

环形链表II

/*
	思路:如下图所示,慢指针的步数为(x + y),快指针的步数为(x + n(y + z) + y),则有 2(x + y) = x + n(y + z) + y => x = (n - 1)(y + z) + z,即快指针从相遇处出发,慢指针从头节点出发,它们一定会在环的入口处相遇
*/
var detectCycle = function(head) {
    // 空链表 或 单节点链表
    if(!head || !head.next) return null;
    // fast:快指针
    // slow:慢指针
    let fast = head.next.next, slow = head.next;
    // 当快、慢指针没有相遇 且 快指针不为null 且 快指针的下一个节点不为null
    while(fast !== slow && fast && fast.next) {
        // 快指针每次走两个节点
        fast = fast.next.next;
        // 慢指针每次走一个节点
        slow = slow.next;
    }
    // 不存在环
    if(!fast || !fast.next) return null;
    // 快、慢指针相遇了,现在要找到环的入口
    slow = head;
    // 遍历快、慢指针,直到快、慢指针再次相遇的节点即为入口节点
    while(slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
};

在这里插入图片描述

注意:x = (n - 1)(y + z) + z,即只要将慢指针从头结点开始、快指针从相遇节点开始遍历,一定会在入口节点再次相遇

总结

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
哈希表是一种高效的数据结构,可以用来存储和查找键值对。其中,哈希函数将键映射到一个特定的桶中,每个桶中存储一组键值对。在哈希表中,如果两个键被映射到同一个桶中,就会发生碰撞。为了解决这个问题,可以使用链表法。 链表法是一种解决哈希表碰撞问题的方法。具体来说,对于哈希表中的每个桶,可以使用一个链表来存储所有映射到该桶的键值对。如果发生碰撞,只需要将新的键值对添加到链表的末尾即可。 下面是一个使用链表法实现哈希表的示例代码: ```python class Node: def __init__(self, key, value): self.key = key self.value = value self.next = None class HashTable: def __init__(self, capacity): self.capacity = capacity self.buckets = [None] * capacity def hash_function(self, key): return hash(key) % self.capacity def put(self, key, value): index = self.hash_function(key) node = self.buckets[index] while node: if node.key == key: node.value = value return node = node.next new_node = Node(key, value) new_node.next = self.buckets[index] self.buckets[index] = new_node def get(self, key): index = self.hash_function(key) node = self.buckets[index] while node: if node.key == key: return node.value node = node.next return None def remove(self, key): index = self.hash_function(key) node = self.buckets[index] prev = None while node: if node.key == key: if prev: prev.next = node.next else: self.buckets[index] = node.next return prev = node node = node.next ``` 在这个示例中,我们定义了一个Node类来表示哈希表中的每个节点,每个节点包含一个键、一个值和一个指向下一个节点的指针。我们还定义了一个HashTable类来实现哈希表,其中包含一个桶数组和一些基本的操作方法,如put、get和remove。 在put方法中,我们首先使用哈希函数计算出键的索引,然后遍历桶中的链表,查找该键是否已经存在于哈希表中。如果找到了该键,我们只需要更新其对应的值即可。否则,我们创建一个新的节点,并将其添加到链表的开头。 在get方法中,我们同样使用哈希函数计算出键的索引,然后遍历桶中的链表,查找该键的值。如果找到了该键,我们返回其对应的值。否则,返回None。 在remove方法中,我们首先使用哈希函数计算出键的索引,然后遍历桶中的链表,查找该键。如果找到了该键,我们将其从链表中删除即可。 总的来说,链表法是一种简单且常用的哈希表解决碰撞问题的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jackson Mseven

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值