js中的数据结构(二) | 链表

398 阅读3分钟

| 常用数据结构

  1. 数组(Array)
  2. 栈(Stack)先进后出 (LIFO)
  3. 队列(Queue)先进先出(FIFO)
  4. 链表(Linked List)
  5. 树(Tree)
  6. 图(Graph)
  7. 堆(Heap)
  8. 散列表(Hash 哈希表

链表

与数组不同的是,组成链表的格子不是连续的
每个不连续的格子叫结点,多个结点就构成了链表

计算机需要知道分散结点的信息,这就是链表的关键了:

  1. 每个结点保存着数据,
  2. 还保存着链表里的下一结点的内存地址(链)
    用来指示下一结点的内存地址的额外数据,被称为

image.png

链表和数组的区别

  1. 数组是连续存放,链表可连续也可不连续
  2. 数组操作元素比较麻烦,链表比较容易,只需改指针
  3. 数组查找块 (时间复杂度为O(1)),链表查找慢( O(n) )
  4. 链表从堆中分配空间, 自由度大但申请管理比较麻烦
  5. 链表的最坏情况和最好情况与数组刚好相反

应用

如果需要快速访问数据,很少插入和删除元素,就应该用数组。
如果需要经常插入和删除元素你就需要用链表。

js实现链表基本功能

// 实现结点(包含结点数据、下一个结点的引用)
class Node {
  nextNode = null;
  constructor(data) {
    this.data = data;
  }
}

// 实例链表包装
class CreateLinkedList {
  firstNode = null;
  constructor(firstNode) {
    this.firstNode = firstNode;
  }

  // 根据索引找数据
  find(index) {
    this.currentNode = this.firstNode;
    this.currentIndex = 0;

    // 可见,链表的查找的时间复杂度是O(n)
    while (this.currentIndex < index) {
      if (this.currentNode.nextNode) {
        this.currentNode = this.currentNode.nextNode;
        this.currentIndex++;
      } else {
        return null;
      }
    }

    return this.currentNode.data;
  }
  // 根据数据查找索引
  findIndex(value) {
    this.currentNode = this.firstNode;
    this.currentIndex = 0;

    while (true) {
      if (this.currentNode.data == value) {
        return this.currentIndex;
      }
      
      if (!this.currentNode.nextNode) {
        return -1;
      }
      this.currentIndex++;
      this.currentNode = this.currentNode.nextNode;
    }
  }
  
  // 插入
  insertAtIndex(index, value) {
    // 创建一个新结点
    const newNode = new Node(value)
    if (index === 0) {
      newNode.nextNode = this.firstNode
      return this.firstNode = newNode
    }
    
    this.currentNode = this.firstNode;
    this.currentIndex = 0;
    
    // 获取前一项
    while (this.currentIndex < index -1) {
      if (!this.currentNode.nextNode) {
        break;
      }
      this.currentIndex++;
      this.currentNode = this.currentNode.nextNode;
    }
    
    newNode.nextNode = this.currentNode.nextNode
    this.currentNode.nextNode = newNode
    
    return this;
  }
  
  // 删除
  deleteAtIndex(index) {
    if (index === 0) {
      return  this.firstNode =  this.firstNode.nextNode
    }
    
    this.currentNode = this.firstNode;
    this.currentIndex = 0;
    
    // 获取删除的那项前一项
    while (this.currentIndex < index -1) {
      if (!this.currentNode.nextNode) {
        return -1;
        // break;
      }
      this.currentIndex++;
      this.currentNode = this.currentNode.nextNode;
    }
    
    // 被删除的那一项
    let deleteNode = this.currentNode.nextNode;
    
    // 被删除的那一项的后一项
    let afterNode = deleteNode.nextNode
    
    // 前一项和后一项相连
    this.currentNode.nextNode = afterNode;
    return deleteNode
  }
}

let node1 = new Node("node1");
let node2 = new Node("node2");
let node3 = new Node("node3");
let node4 = new Node("node4");

node1.nextNode = node2;
node2.nextNode = node3;
node3.nextNode = node4;

const linkedList = new CreateLinkedList(node1);
console.log(linkedList); // 链表

双向链表

数组的时间复杂度插入是O(1),删除是O(N)
链表则反过来,分别是O(N)O(1)

它们总有一种操作是 O(1),另一种是O(N)
基于以上分析,似乎用数组还是链表都无所谓

image.png
双向链表是链表的变种,就能使队列的插入和删除的时间复杂度都为O(1)

双向链表跟链表差不多,只是它每个结点都含有两个链
一个指向下一结点,另一个指向前一结点。
它还能直接访问第一个和最后一个结点。

image.png js实现双向链表

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

class DoubleLinkedList {
  tail = null;
  length = 0;
  head = null;

  // 常见的方法
  // 1.append方法
  append = (data) => {
    //1. 根据data创建新节点
    let newNode = new Node(data);

    // 2.添加节点
    // 情况1:添加的是第一个节点
    if (this.length == 0) {
      this.head = newNode;
      this.tail = newNode;
    } else {
      // 情况2:添加的不是第一个节点
      newNode.pre = this.tail;
      this.tail.next = newNode;
      this.tail = newNode;
    }

    // 3.length+1
    this.length += 1;
  };

  // 2.将链表转变为字符串形式
  // 2.1 toString方法
  toString = () => {
    return this.backwardString();
  };
  // 2.2 forwardString方法
  forwardString = () => {
    // 1.定义变量
    let current = this.tail;
    let resStr = "";

    // 2.依次向前遍历,获取每一个节点
    while (current) {
      resStr += current.data + "**";
      current = current.pre;
    }
    return resStr;
  };
  // 2.3 backwardString方法
  backwardString = () => {
    // 1.定义变量
    let current = this.head;
    let resStr = "";

    // 2.依次向后遍历,获取每一个节点
    while (current) {
      resStr += current.data + "--";
      current = current.next;
    }
    return resStr;
  };

  // 3.insert方法
  insert = (position, data) => {
    // 1.越界判断
    if (position < 0 || position > this.length) return false;

    // 2.根据data创建新的节点
    let newNode = new Node(data);

    // 3.插入新节点
    // 原链表为空
    // 情况1:插入的newNode是第一个节点 原链表为空
    if (this.length == 0) {
      this.head = newNode;
      this.tail = newNode;
    } else {
      // 原链表不为空
      // 情况2:position == 0
      if (position == 0) {
        this.head.pre = newNode;
        newNode.next = this.head;
        this.head = newNode;
      } else if (position == this.length) {
        // 情况3:position == this.length
        this.tail.next = newNode;
        newNode.pre = this.tail;
        this.tail = newNode;
      } else {
        // 情况4:0 < position < this.length
        let current = this.head;
        let index = 0;

        while (index++ < position) {
          current = current.next;
        }

        //修改pos位置前后节点变量的指向
        newNode.next = current;
        newNode.pre = current.pre;
        current.pre.next = newNode;
        current.pre = newNode;
      }
    }
    // 4.length+1
    this.length += 1;
    return true; // 返回true表示插入成功
  };

  // 4.get方法
  get = (position) => {
    // 1.越界判断
    if (position < 0 || position >= this.length) {
      //获取元素时position不能等于length
      return null;
    }

    // 2.获取元素
    let current = null;
    let index = 0;

    // this.length / 2 > position:从头开始遍历
    if (this.length / 2 > position) {
      current = this.head;
      while (index++ < position) {
        current = current.next;
      }
      return current.data;
    } else {
      // this.length / 2 =< position:从尾开始遍历
      current = this.tail;
      index = this.length - 1;
      while (index-- > position) {
        current = current.pre;
      }
    }

    return current.data;
  };

  // 5.indexOf方法
  indexOf = (data) => {
    // 1.定义变量
    let current = this.head;
    let index = 0;

    // 2.遍历链表,查找与data相同的节点
    while (current) {
      if (current.data == data) {
        return index;
      }
      current = current.next;
      index++;
    }
    return -1;
  };

  // 6.update方法
  update = (position, newData) => {
    // 1.越界判断
    if (position < 0 || position >= this.length) return false;

    // 2.寻找正确的节点
    let index = 0;
    let current = this.head;
    if (this.length / 2 > position) {
      while (index++ < position) {
        current = current.next;
      }
    } else {
      current = this.tail;
      index = this.length - 1;
      while (index-- > position) {
        current = current.pre;
      }
    }

    // 3.修改找到节点的data
    current.data = newData;
    return true; //表示成功修改
  };

  // 7.removeAt方法
  removeAt = (position) => {
    // 1.越界判断
    if (position < 0 || position >= this.length) return null;

    // 2.删除节点
    // 当链表中length == 1
    // 情况1:链表只有一个节点
    let current = this.head; //定义在最上面方便以下各种情况返回current.data
    if (this.length == 1) {
      this.head = null;
      this.tail = null;
    } else {
      // 情况2:删除第一个节点
      if (position == 0) {
        this.head.next.pre = null;
        this.head = this.head.next;
      } else if (position == this.length - 1) {
        // 情况3:删除最后一个节点
        current = this.tail; // 该情况下返回被删除的最后一个节点
        this.tail.pre.next = null;
        this.tail = this.tail.pre;
      } else {
        let index = 0;
        while (index++ < position) {
          current = current.next;
        }
        current.pre.next = current.next;
        current.next.pre = current.pre;
      }
    }
    // 3.length -= 1
    this.length -= 1;
    return current.data; // 返回被删除节点的数据
  };

  /*--------------------其他方法-------------------*/
  // 8.remove方法
  remove = (data) => {
    // 1.根据data获取下标值
    let index = this.indexOf(data);

    // 2.根据index删除对应位置的节点
    return this.removeAt(index);
  };

  // 9.isEmpty方法
  isEmpty = () => {
    return this.length == 0;
  };
  // 10.size方法
  size = () => {
    return this.length;
  };
  // 11.getHead方法:获取链表的第一个元素
  getHead = () => {
    return this.head.data;
  };
  // 12.getTail方法:获取链表的最后一个元素
  getTail = () => {
    return this.tail.data;
  };
}

let list = new DoubleLinkedList();

list.append("aaa");
list.append("bbb");
list.append("ccc");
list.append("ddd");
console.log(list);

| 参考

JavaScript实现双向链表
数据结构与算法图解