Typescript实现链表数据结构

185 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 12 月更文挑战」的第 4 天,点击查看活动详情

最近在学习 Javascript 数据结构与算法相关知识,数据结构与算法对程序员来说就像是内功心法,只有不断修炼自己的内功,才能从容不迫的应对市场上各种让人应接不暇的框架,达到以不变应万变。学习数据结构与算法的过程中不仅要能看懂更要多写多练,今天就来手写下链表数据结构。

每种语言都实现了数组。这种数据结构有一个缺点:(在大多数语言中)数组的大小是固定的,从数组的起点或中间插入或移 除项的成本很高,因为需要移动元素。

数组需要一块连续的内存空间来存储,对内存的要求比较高。 链表不需要一块连续的内存空间来存储,它通过“指针”将一组零散的内存块串联起来使用

手写单链表结构

链表基本操作方法有:

  • push(element):向链表尾部添加一个新元素。
  • insert(element, position):向链表的特定位置插入一个新元素。
  • getElementAt(index):返回链表中特定位置的元素。如果链表中不存在这样的元素,则返回 undefined。
  • remove(element):从链表中移除一个元素。
  • indexOf(element):返回元素在链表中的索引。如果链表中没有该元素则返回-1。
  • removeAt(position):从链表的特定位置移除一个元素。
  • isEmpty():如果链表中不包含任何元素,返回 true,如果链表长度大于 0 则返回 false。
  • size():返回链表包含的元素个数,与数组的 length 属性类似。
  • toString():返回表示整个链表的字符串。由于列表项使用了 Node 类,就需要重写继承自 JavaScript 对象默认的 toString 方法,让其只输出元素的值。
class Node<T> {
  constructor(public element: T, public next?: Node<T>) {}
}
class LinkedList<T> {
  protected count = 0; // 存储链表中元素数量
  protected head: Node<T> | undefined; // 头指针

  push(element: T) {
    const node = new Node(element);
    let current: Node<T>;
    if (this.head) {
      current = this.head;
      // 遍历链表
      while (current.next && current.next !== null) {
        current = current.next;
      }
      // 将current的next指针指向新节点
      current.next = node;
    } else {
      this.head = node;
    }
    this.count++;
  }
  // 根据下标查找节点
  getElementAt(index: number) {
    if (index >= 0 && index < this.count) {
      let node = this.head;
      for (let i = 0; i < index && node !== null; i++) {
        node = node?.next;
      }
      return node;
    }
    return undefined;
  }
  //从链表中移除元素
  removeAt(index: number) {
    // 越界检查
    if (index >= 0 && index < this.count) {
      let current = this.head;
      if (index === 0) {
        //头节点
        this.head = current?.next;
      } else {
        // 中间节点
        const previous = this.getElementAt(index - 1); // 当前节点的前节点引用
        if (previous) {
          current = previous.next;
          // 跳过current节点 从而将其删除
          previous.next = current?.next;
        }
      }
      this.count--;
      return current?.element;
    }
    return undefined;
  }
  // 链表中间插入节点
  insert(element: T, index: number) {
    if (index >= 0 && index < this.count) {
      const node = new Node(element);
      if (index === 0) {
        const current = this.head;
        node.next = current;
        this.head = node;
      } else {
        const previous = this.getElementAt(index - 1);
        if (previous) {
          const current = previous.next;
          node.next = current;
          previous.next = node;
        }
      }
      this.count++;
      return true;
    }
    return false;
  }
  // 返回一个元素的位置
  indexOf(element: T) {
    let current = this.head;
    for (let i = 0; i < this.count && current !== null; i++) {
      if (element === current?.element) {
        return i;
      }
      current = current?.next;
    }
    return -1;
  }
  // 从链表中移除元素
  remove(element: T) {
    const index = this.indexOf(element);
    return this.removeAt(index);
  }
  size() {
    return this.count;
  }
  isEmpty() {
    return this.size() === 0;
  }
  toString() {
    if (this.head === null) {
      return "";
    }
    let objString = `${this.head?.element}`;
    let current = this.head?.next;
    for (let i = 1; i < this.size() && current !== null; i++) {
      objString = `${objString},${current?.element}`;
      current = current?.next;
    }
    return objString;
  }
}

const list = new LinkedList();
list.push(5);
list.push(10);
list.push(15);
list.removeAt(1);
console.log(list.insert(20, 1));
console.log(list.toString());

使用场景

leetcode 练习题:83

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

/*
 * @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;
};

解题思路

由于给定的链表是排好序的,因此重复的元素在链表中出现的位置是连续的,因此我们只需要对链表进行一次遍历,就可以删除重复的元素。

具体地,我们从指针 p 指向链表的头节点,随后开始对链表进行遍历。如果当前 p 与 p.next 对应的元素相同,那么我们就将 p.next 从链表中移除;否则说明链表中已经不存在其它与 p 对应的元素相同的节点,因此可以将 p 指向 p.next。

当遍历完整个链表之后,我们返回链表的头节点即可。