数据结构-JavaScript实现一个单链表

250 阅读2分钟

链表介绍

线性表的链式存储结构生成的表,称作“链表”。

每个元素本身由两部分组成:

  1. 本身的信息,称为“数据域”;
  2. 指向直接后继的指针,称为“指针域”

链表和数组的对比

数组的特点

  1. 线性结构,顺序存储
  2. 数组最好用于索引有寓意的情况
  3. 最大的优点:支持快速查询

链表的特点

  1. 线性结构,随机存储
  2. 链表不适用于索引有寓意的情况
  3. 最大优点:动态(真正的动态数据结构)

链表基本结构

class Node {
  constructor(data) {
    this.data = data;
    this.next = null;
  }
}

实现一个链表

1. 为链表设置虚拟头结点

const dummyHead = Symbol("head");
class LinkedList {
  constructor() {
    // 虚拟头结点,方便遍历
    this[dummyHead] = new Node(null);
    this.size = 0;
  }
  // 打印链表信息
  toString() {
    let res = ''
    let cur = this[dummyHead].next;
    while(cur != null) {
      res += cur.data + '->';
      cur = cur.next;
    }
    res += 'NULL';
    return res;
  }
}

2. 在链表中添加新元素

class LinkedList {
  // ...
  
  // 在链表的index(0-based)位置添加新的元素 data
  add(index, data) {
    if (index < 0 || index > this.size) {
      throw 'Add failed, Illegal index.'
    }
    let prev = this[dummyHead];
    for (let i = 0; i < index; i++) {
      prev = prev.next
    }
    const newNode = new Node(data);
    newNode.next = prev.next;
    prev.next = newNode;
    this.size ++;
  }
  
  // 在链表头添加新的元素
  addFirst(data) {
    this.add(0, data)
  }
  
  // 在链表末尾添加新的元素
  addLast(data) {
    this.add(this.size, data)
  }
  // 打印链表信息
  toString() {
    let res = ''
    let cur = this[dummyHead].next;
    while(cur != null) {
      res += cur.data + '->';
      cur = cur.next;
    }
    res += 'NULL';
    return res;
  }
}

3. 获得链表第n个位置元素

class LinkedList {
  // ...
  
  // 获得链表的第index(0-based)个位置的元素
  get(index) {
    if (index < 0 || index >= this.size) {
      throw 'get failed, Illegal index.'
    }
    let cur = this[dummyHead].next
    for(let i = 0; i < index; i++) {
      cur = cur.next
    }
    return cur.data
  }
  
  // 获得链表的第一个元素
  getFirst(){
    return this.get(0);
  }
  
  // 获得链表的最后一个元素
  getLast() {
    return this.get(this.size - 1)
  }
}

4. 修改链表的第index(0-based)个位置的元素为data

class LinkedList {
  // ...
  
  set(index, data) {
    if (index < 0 || index >= this.size) {
      throw 'set failed, Illegal index.'
    }
    let cur = this[dummyHead].next;
    for(let i = 0; i < index; i ++ ) {
      cur = cur.next
    }
    cur.data = data
  }
}

5. 删除链表中第n个位置的元素

class LinkedList {
  // ...
  
  // 从链表中删除index(0-based)位置的元素, 返回删除的元素
  remove(index) {
    if (index < 0 || index >= this.size) {
      throw 'remove failed, Illegal index.'
    }
    let prev = this[dummyHead];
    for(let i = 0; i < index; i++) {
      prev = prev.next
    }
    let retNode = prev.next;
    prev.next = retNode.next;
    retNode.next = null
    this.size --;
    
    return retNode;
  }
  
  // 从链表中删除第一个元素, 返回删除的元素
  removeFirst() {
    return this.remove(0)
  }
  
  // 从链表中删除最后一个元素, 返回删除的元素
  removeLast() {
    return this.remove(this.size - 1)
  }
}

6. 从链表中删除元素data

class LinkedList {
  // ...
  
  removeElement(data) {
    let prev = this[dummyHead];
    while(prev.next !== null) {
      if(prev.next.data === data) {
        break;
      }
      prev = prev.next;
    }
    if(prev.next !== null) {
      let delNode = prev.next;
      prev.next = delNode.next;
      delNode.next = null;
      this.size --;
    }
  }
}

7. 查找链表中是否有元素data

class LinkedList {
  // ...
  
  contains(data) {
    let cur = this[dummyHead].next;
    while(cur.next !== null) {
      if(cur.data === data) {
        return true
      }
      cur = cur.next
    }
    return false
  }
  
}

链表时间复杂度分析

  • 增:O(n)
  • 删:O(n)
  • 改:O(n)
  • 查:O(n)