JavaScript 数据结构 链表

900 阅读4分钟

链表简介

1. 是什么?

  • 多个元素组成的列表

  • 元素存储不连续 , 用 next指针连在一起

    • image-20211011110953158

2. 链表与数组的关系

相同: 都是多个元素组成的列表

不同

  • 数组中增删非首尾的元素 ,是需要移动大部分的元素 ==》 数组中常用 split() 进行增删
  • 链表增删 不需要移动元素 , 仅仅改变next 指针的指向即可

3. 链表的常见操作

const a = {val: 'a'};
const b = {val: 'b'};
const c = {val: 'c'};
const d = {val: 'd'};
​
// 形成链表
a.next = b;
b.next = c;
c.next = d;
​
// 增加
const m = {val : 'm'};
c.next = m;
m.next = d;
​
//遍历链表
let p = a;
while (p){
    console.log(p.val);
    p = p.next;
}
​
// 删除
c.next = d;

刷题

1. 237. 删除链表中的节点 - 力扣(LeetCode) (leetcode-cn.com)

image-20211011115231278

思路:
  • 链表的特点是只能获取下一节点
  • 因此 删除此节点的最好方法就是将下一节点的值迁移 ,将本节点指向下下节点 即删除下一节点
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} node
 * @return {void} Do not return anything, modify node in-place instead.
 */
var deleteNode = function(node) {
    node.val = node.next.val;
    node.next = node.next.next;
};

2.206. 反转链表 - 力扣(LeetCode) (leetcode-cn.com)

思路:
  • 核心: 链表直接转化方向
  • 遍历链表 双指针
  • 改变方向后 ==》 如何再继续遍历?
  • 在改变方向之前直接将指针赋值为一个变量
  • 真正往前推进的是head 所在的指针
  • 因此赋值一定是head 所在的指针
  • 时间复杂度 On
  • 空间 O1
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    let p1 = head;
    let p2 = null;
    while(p1){
        const temp = p1.next; 
        p1.next = p2;
        p2 = p1;
        p1 = temp;
    }
    return p2
};

3. 2. 两数相加 - 力扣(LeetCode) (leetcode-cn.com)

image-20211011132307584

思路:
  • 第一次提交错误 ==》 末位有进位的情况
  • 步骤
  • 加减运算 ==》 进位 Math.floor (v3 /10) 余位 v3%10
  • 遍历 p1 = p1.next ==> 还要考虑为空的情况
  • 初始化 创造了空节点 末位要返回 p.next
  • 考虑 p不存在的情况 赋值为零

解决:

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function(l1, l2) {
    let l3 = new ListNode();
    let p1 = l1;
    let p2 = l2;
    let p3 = l3;
    //  进位
    let carry = 0;
    while(p1 || p2 || carry){
        const v1 = p1 ? p1.val : 0;
        const v2 = p2 ? p2.val : 0;
        let v3 = v1 + v2 + carry;
        // 初始化
        p3.next = new ListNode(v3 % 10);
        carry = Math.floor(v3 / 10);
        // 看是否存在 不存在赋 0  
        if(p1) p1 = p1.next;
        if(p2) p2 = p2.next;
        p3 = p3.next;
    }
    return l3.next;
};

4. 83. 删除排序链表中的重复元素 - 力扣(LeetCode) (leetcode-cn.com)

思路:
  • 相同就跳过
  • 连着相同 就一直跳 直到不同是 p.next
解决
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var deleteDuplicates = function(head) {
    let p = head;
    while(p && p.next){
        if(p.val === p.next.val){
            p.next = p.next.next;
        }else{
            p = p.next;
        }
    };
    return head
​
};

5. 141. 环形链表 - 力扣(LeetCode) (leetcode-cn.com)

思路:
  • 环形赛道 跑的慢的一定会被超越
  • 相遇时 即 p1 === p2
  • 确保只有一个元素情况 并且 快的p 存在 p1.next
解决:
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
​
/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function(head) {
    let p1 = head;
    let p2 = head;
​
    while(p1 && p2 && p1.next){
        p1 = p1.next.next;
        p2 = p2.next;
        if(p1 === p2){
            return true
        }
    }
    return false
};

前端与链表

1. js 中的原型链

1.1 是什么
  • 原型链的本质是链表
  • 原型链上的节点是各种的原型对象,比如: Function.prototype 、 Object.prototype
  • 原型链通过 __proto__属性连接各种原型对象
1.2 常见的原型链
obj ==> Object.prototype ==> null
arr ==> Array.prototype ==> Object.prototype ==> null
function ==> Function.prototype ==> Object.prototype ==> null

注:除object 外 其他类型的原型链 先指向自己类型,再指向其他类型

1.3 知识点
  • 如果A沿着原型链能找到B.prototype(B的原型对象) , 那么 A instanceof B 为 true

    • const obj = {};
      const fun = () => {};
      const arr = [];
      ​
      obj instanceof Object; true
      fun instanceof Object; true
      fun instanceof Function; true
      
  • 如果在A对象上没有找到x属性,那么会沿着原型链找到x属性

    • const obj = {};
      Object.prototype.x = 'dd';
      ==>
      obj.x : "dd"
      
1.4 面试题
  • 题目一:instanceof的原理 并用代码实现

    • instance(A , B){
          let p1 = A;
          while(p1){
          if(p1 === p2.prototype)
          return true
          p1 = p1.next;
          }
          return false;
      }
      
  • 题目二:image-20211014113254808

    • value a undefinded value a value b

      • 如果在A 对象上没有找到x属性,那么会沿着原型链找到x属性

2. 使用链表指针获取json的节点值

const json = {
    a: { b: { c : 1}},
    d: { e: 2},
};
​
let p = json;
path.forEach((key) =>{
    p = p[key];
})
const path = ['a' , 'b' , 'c']; // p = 1
const path = ['d' , 'e'];   //  p = 2 

总结

  • 链表中的元素是通过next 指针 连接 ,非连续、

  • JavaScript 中无链表 ,obj模拟链表

  • 常用操作

    • 修改next
    • 遍历
  • 链表与前端的联系

    • js中的原型链
    • 使用链表指针获取json的节点值

参考文献

lewis 《JavaScript版数据结构与算法》 系列课程-