救命!JS 链表居然可以这么好玩

63 阅读2分钟

一、什么是链表?

链表是一种常见的数据结构,与数组同为线性表,但存储方式截然不同。

  • 数组:有序、线性、连续型存储空间,访问效率高(O(1)),但增删元素可能需要移动大量元素(O(n))

  • 链表:有序、线性、离散型存储空间,增删元素效率高(O(1)),但访问元素需要从头遍历(O(n))

注意:JavaScript 中的数组未必是真正的数组,当存储不同类型数据时可能变为哈希表结构

二、链表的基本结构

1. 链表节点的构成

每个链表节点包含两部分:

  • 值域(val):存储数据

  • 指针域(next):指向后续节点

// 简单对象表示
let list = {
  val: 1,  // 值域
  next: {   // 指针域
    val: 2,
    next: null  // 尾节点指针为null
  }
}

2. 用构造函数创建节点

更规范的做法是使用构造函数创建节点:

function ListNode(val) {
  this.val = val;
  this.next = null;
}

// 创建节点
const node1 = new ListNode(1);
const node2 = new ListNode(2);

// 连接节点
node1.next = node2;  // 1 -> 2 -> null

三、链表的基本操作

1. 遍历链表

// 假设我们有一个链表: 1 -> 2 -> 3 -> ... -> 9 -> null
let list = {
  val: 1,
  next: {
    val: 2,
    next: {
      val: 3,
      next: {
        val: 4,
        next: {
          val: 5,
          next: {
            val: 6,
            next: {
              val: 7,
              next: {
                val: 8,
                next: {
                  val: 9,
                  next: null
                }
              }
            }
          }
        }
      }
    }
  }
};

// 遍历到第8个节点
let node = list;
for (let i = 1; i < 8 && node; i++) {
  node = node.next;
}
console.log(node);  // 输出第8个节点: {val: 8, next: {val: 9, next: null}}

2. 插入节点

// 在node1和node2之间插入node3
const node1 = new ListNode(1);
node1.next = new ListNode(2);

const node3 = new ListNode(3);
node3.next = node1.next;  // 先让node3指向node2
node1.next = node3;       // 再让node1指向node3

// 结果: 1 -> 3 -> 2 -> null

四、实战:合并两个有序链表

LeetCode 第21题:将两个升序链表合并为一个新的升序链表并返回

var mergeTwoLists = function (list1, list2) {
  // 创建虚拟头节点,简化边界处理
  const head = new ListNode();
  let cur = head;

  // 遍历两个链表,比较节点值大小
  while (list1 && list2) {
    if (list1.val <= list2.val) {
      cur.next = list1;
      list1 = list1.next;
    } else {
      cur.next = list2;
      list2 = list2.next;
    }
    cur = cur.next;
  }

  // 处理剩余节点
  cur.next = list1 === null ? list2 : list1;

  // 返回真正的头节点(跳过虚拟头节点)
  return head.next;
};

ScreenShot_2025-12-30_153002_819.png

LeetCode 第83题: 给定一个已排序的链表的头 head, 删除所有重复的元素,使每个元素只出现一次。返回 已排序的链表 。

/**
 * 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) {
       var cur = head;
    while(cur && cur.next) {
        if(cur.val == cur.next.val) {
            cur.next = cur.next.next;
        } else {
            cur = cur.next;
        }
    }
    return head;
};

ScreenShot_2025-12-30_153954_878.png

ScreenShot_2025-12-30_154002_730.png

ScreenShot_2025-12-30_154017_768.png

五、总结

  1. 链表适合频繁增删的场景,数组适合频繁访问的场景
  1. 操作链表时要注意指针的正确指向,避免断链
  1. 虚拟头节点技巧可以简化边界条件处理