Skip to content
On this page

链表结构

非常非常重要的结构

什么是链表结构

链表是一种数据结构,由一组节点组成,每个节点包含数据和指向下一个节点的指针。链表可以用来存储任意数量的数据,每个节点可以随时添加或删除。与数组不同,链表中的元素不必按顺序存储,因此可以更灵活地管理数据。链表在许多计算机算法和编程语言中都得到广泛应用。

  • 数组

    • 优点:可以随机访问任何位置的元素
    • 缺点:在数组的开头或中间插入或删除元素很慢,因为需要移动元素
  • 链表

    • 优点:在链表的开头或中间插入或删除元素很快,因为不需要移动元素
    • 缺点:不能随机访问元素,必须从开头开始迭代列表直到找到所需的元素

实现一个链表

封装两个类

  • Node类:表示节点
  • LinkedList类:提供插入节点、删除节点、显示列表元素的方法

TIP

链表中的常见操作

  • append(element):向列表尾部添加一个新的项
  • insert(position, element):向列表的特定位置插入一个新的项
  • get(position):获取对应位置的元素
  • indexOf(element):返回元素在列表中的索引。如果列表中没有该元素则返回-1
  • update(position, element):修改某个位置的元素
  • removeAt(position):从列表的特定位置移除一项
  • remove(element):从列表中移除一项
  • isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false
  • size():返回链表包含的元素个数
  • toString():由于列表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法,让其只输出元素的值
  • print():打印链表
  • getHead():获取链表的头部
  • getTail():获取链表的尾部
js
// Node类
class Node<T> {
  public value: T;
  next: Node<T> | null;

  constructor(value: T) {
    this.value = value;
    this.next = null;
  }
}
// Node类
class Node<T> {
  public value: T;
  next: Node<T> | null;

  constructor(value: T) {
    this.value = value;
    this.next = null;
  }
}
js
// LinkedList类
class LinkedList<T> {
  private head: Node<T> | null = null;
  private size: number = 0;

  append(value: T) {
    const node = new Node(value);
    if (this.head === null) {
      this.head = node;
    } else {
      let current = this.head;
      while (current.next) {
        current = current.next;
      }
      current.next = node;
    }
    this.size++;
  }

  traverse(callback: (node: Node<T>) => void) {
    let current = this.head;
    while (current) {
      callback(current);
      current = current.next;
    }
  }

  insert(position: number, value: T) {
    if (position < 0 || position > this.size) {
      return false
    }
    // 双指针做法
    const node = new Node(value);
    if (position === 0) {
      node.next = this.head;
      this.head = node;
    } else {
      let current = this.head;
      let previous = null;
      let index = 0;
      while (index++ < position) {
        previous = current;
        current = current.next;
      }
      node.next = current;
      previous.next = node;
    }
    this.size++;
    return true;
  }

  remove(value: T) {
    const index = this.indexOf(value);
    return this.removeAt(index);
  }

  removeAt(position: number) {
    if (position < 0 || position >= this.size) {
      return null;
    }
    let current = this.head;
    if (position === 0) {
      this.head = this.head.next;
    } else {
      let previous = null;
      let index = 0;
      while (index++ < position) {
        previous = current;
        current = current.next;
      }
      previous!.next = current?.next ?? null;
    }
    this.size--;
    // value :: undefined
    return current.value ?? null;
  }

  update(position: number, value: T) {
    if (position < 0 || position >= this.size) {
      return false;
    }
    let current = this.head;
    let index = 0;
    while (index++ < position) {
      current = current!.next;
    }
    current!.value = value;
    return true;
  }

  indexOf(value: T) {
    let current = this.head;
    let index = 0;
    while (current) {
      if (current.value === value) {
        return index;
      }
      current = current.next;
      index++;
    }
    return -1;
  }

  isEmpty() {
    return this.size === 0;
  }


  get(position: number): T | null {
    if (position < 0 || position >= this.size) {
      return null;
    }
    let current = this.head;
    let index = 0;
    while (index++ < position) {
      current = current!.next;
    }
    return current!.value;
  }

  get length() {
    return this.size
  }
}
// LinkedList类
class LinkedList<T> {
  private head: Node<T> | null = null;
  private size: number = 0;

  append(value: T) {
    const node = new Node(value);
    if (this.head === null) {
      this.head = node;
    } else {
      let current = this.head;
      while (current.next) {
        current = current.next;
      }
      current.next = node;
    }
    this.size++;
  }

  traverse(callback: (node: Node<T>) => void) {
    let current = this.head;
    while (current) {
      callback(current);
      current = current.next;
    }
  }

  insert(position: number, value: T) {
    if (position < 0 || position > this.size) {
      return false
    }
    // 双指针做法
    const node = new Node(value);
    if (position === 0) {
      node.next = this.head;
      this.head = node;
    } else {
      let current = this.head;
      let previous = null;
      let index = 0;
      while (index++ < position) {
        previous = current;
        current = current.next;
      }
      node.next = current;
      previous.next = node;
    }
    this.size++;
    return true;
  }

  remove(value: T) {
    const index = this.indexOf(value);
    return this.removeAt(index);
  }

  removeAt(position: number) {
    if (position < 0 || position >= this.size) {
      return null;
    }
    let current = this.head;
    if (position === 0) {
      this.head = this.head.next;
    } else {
      let previous = null;
      let index = 0;
      while (index++ < position) {
        previous = current;
        current = current.next;
      }
      previous!.next = current?.next ?? null;
    }
    this.size--;
    // value :: undefined
    return current.value ?? null;
  }

  update(position: number, value: T) {
    if (position < 0 || position >= this.size) {
      return false;
    }
    let current = this.head;
    let index = 0;
    while (index++ < position) {
      current = current!.next;
    }
    current!.value = value;
    return true;
  }

  indexOf(value: T) {
    let current = this.head;
    let index = 0;
    while (current) {
      if (current.value === value) {
        return index;
      }
      current = current.next;
      index++;
    }
    return -1;
  }

  isEmpty() {
    return this.size === 0;
  }


  get(position: number): T | null {
    if (position < 0 || position >= this.size) {
      return null;
    }
    let current = this.head;
    let index = 0;
    while (index++ < position) {
      current = current!.next;
    }
    return current!.value;
  }

  get length() {
    return this.size
  }
}
ts
const link = new LinkedList<number>();
link.append(1);
link.append(2);
link.traverse((node) => {
  console.log(node.value);
});
const link = new LinkedList<number>();
link.append(1);
link.append(2);
link.traverse((node) => {
  console.log(node.value);
});

删除 node 节点

ts
// 删除节点
class ListNode {
  val: number;
  next: ListNode | null;
  constructor(val?: number, next?: ListNode |null) {
    this.val = val
    this.next = next
  }
}

function deleteNode(node: ListNode | null): void {
  node!.val = node!.next.val
  node.next = node.next.next
}
// 删除节点
class ListNode {
  val: number;
  next: ListNode | null;
  constructor(val?: number, next?: ListNode |null) {
    this.val = val
    this.next = next
  }
}

function deleteNode(node: ListNode | null): void {
  node!.val = node!.next.val
  node.next = node.next.next
}

反转列表

栈结构方式 入栈出栈

ts
class ListNode {
  val: number;
  next: ListNode | null;
  constructor(val?: number, next?: ListNode |null) {
    this.val = val
    this.next = next
  }
}

function reverseList(head: ListNode | null): ListNode | null {
  if (!head) return null
  if (!head.next) return head

  // 使用栈结构
  const stack: ListNode[] = []
  let current: ListNode | null = head
  while(current) {
    stack.push(current)
    current = current.next
  }

  // 从栈中取出元素 重新组成链表 返回 head 节点
  const newHead = ListNode | null = stack.pop()
  let newHeadCurrent = newHead
  while(stack.length) {
    newHeadCurrent.next = stack.pop()
    newHeadCurrent = newHeadCurrent.next
  }
  newHeadCurrent.next = null
  return newHead
}
class ListNode {
  val: number;
  next: ListNode | null;
  constructor(val?: number, next?: ListNode |null) {
    this.val = val
    this.next = next
  }
}

function reverseList(head: ListNode | null): ListNode | null {
  if (!head) return null
  if (!head.next) return head

  // 使用栈结构
  const stack: ListNode[] = []
  let current: ListNode | null = head
  while(current) {
    stack.push(current)
    current = current.next
  }

  // 从栈中取出元素 重新组成链表 返回 head 节点
  const newHead = ListNode | null = stack.pop()
  let newHeadCurrent = newHead
  while(stack.length) {
    newHeadCurrent.next = stack.pop()
    newHeadCurrent = newHeadCurrent.next
  }
  newHeadCurrent.next = null
  return newHead
}

非递归的方式

ts
function reverseList(head: ListNode | null): ListNode | null {
  // 判断节点为空或者只有一个节点 直接返回 head
  if (!head || !head.next) return null

  // 反转列表
  let newHead: ListNode | null = null

  // 第一步 让一个 current 节点指向 head 的下一个节点 目的是保留着下一个节点的引用 防止被销毁

  // 第二部 改变head 当前指向的节点的指向 让其指向 newHead 对于第一个节点来说 指向 newHead 就是 null

  // 第三步 让 newHead 指向 head 目的是下一次遍历时 第二部操作可以让下一个节点 指向 第一个节点

  // 第四步 让 head 指向 current 目的是下一次遍历时 第二部操作可以让下一个节点 指向 第一个节点

  while(head) {
    let current = head.next

    head.next = newHead

    newHead = head

    head = current
  }

  return newHead

}
function reverseList(head: ListNode | null): ListNode | null {
  // 判断节点为空或者只有一个节点 直接返回 head
  if (!head || !head.next) return null

  // 反转列表
  let newHead: ListNode | null = null

  // 第一步 让一个 current 节点指向 head 的下一个节点 目的是保留着下一个节点的引用 防止被销毁

  // 第二部 改变head 当前指向的节点的指向 让其指向 newHead 对于第一个节点来说 指向 newHead 就是 null

  // 第三步 让 newHead 指向 head 目的是下一次遍历时 第二部操作可以让下一个节点 指向 第一个节点

  // 第四步 让 head 指向 current 目的是下一次遍历时 第二部操作可以让下一个节点 指向 第一个节点

  while(head) {
    let current = head.next

    head.next = newHead

    newHead = head

    head = current
  }

  return newHead

}

TIP

image.png

非递归的方式

链表结构 has loaded