手撕JS算法 - 链表

114 阅读3分钟

思维导图

链表.png

思路理解

链表就是用一个个节点凑成的一个数据链,每一个节点有自己的数据,还有保存下一个节点的指针,这样操作相对于数组的优势有很多:

1.链表《增删数据效率会很高》,因为数组是一块连续的内存,要增删数据就需要更改原来连续内存的大小,那么就会涉及到重新分配内存,以及中间数据删除后,其他数据位置的移动等,效率会比较低,而链表只需要改变相邻的节点的指针指向就可以做到增删。

2.链表插入时间复杂度相比数组低很多。

缺点:

1.由于不是连续的内存地址,那么就无法用下标直接访问到数据,只能从头或者从尾部向里依次查找对应下标个数,效率很低。

2.由于要保存下一个节点的地址,会多耗费很多内存。

0.链表类
function LinkList() {

}

局部实现

1.在链表类中有节点内部类,来作为每个节点的数据保存形式,将要保存的数据存成这种形式这样就能保存下一个节点的地址
//节点内部类
  function Node(data) {
    this.data = data;
    this.next = null;
  }
  let head = null;  //指向链表头结点
  let length = 0;  //保存链表长度
2.链表末尾插入(这里是只实现头结点的做法,也可以实现一个尾节点,提高尾部插入效率)

主要思路:先将要保存的数据封装成一个节点,然后使用一个临时节点,遍历当前链表,寻找next属性为空的节点,就是最后一个节点,将要插入的节点赋值给最后一个节点的next属性,即完成链表的尾部插入操作

//末尾插入
  LinkList.prototype.append = function(element) {
      const newEle = new Node(element);
      if (length == 0) { //第一个节点
        head = newEle;
      } else { //不是第一个节点
        let current = head;
        while (current.next) {
          current = current.next;
        }
        current.next = newEle;
      }
      length += 1;
    }
2.指定位置插入

思路:设原数组ACD,我要将B插入第二个位置,那么就要让A节点的next改为指向B,B的next指向C,完成插入操作,链表变为ABCD

    //指定位置插入
  LinkList.prototype.insert = function(element, position = length) {
      const newEle = new Node(element);
      let current = head;
      if (position <= 0) {
        newEle.next = head;
        head = newEle;
      } else if (position >= length) {
        //增加健壮性 直接插入最后
        position = length;
        while (current.next) {
          current = current.next
        }
        current.next = newEle
      } else {
        let current = head;
        for (let i = 0; i < position - 1; i++) { // 找到插入位置
          current = current.next;
        }
        newEle.next = current.next;
        current.next = newEle;
      }
      length += 1
    }
指定位置删除

思路:设原数组ABCD,我要删除B,那么只需要让A的next指向C即可,B会在JavaScript的垃圾回收机制里,由于没有指针访问他,而被回收掉

    //指定位置删
  LinkList.prototype.removeAt = function(index) {
      if (index < 0 || index > length - 1) {
        console.log("index is error!")
        return;
      }
      let current = head;
      for (let i = 0; i < index - 1; i++) {
        current = current.next
      }
      current.next = current.next.next;
      length -= 1;
    }
元素key查找
    // key查元素(第一个)
  LinkList.prototype.indexOf = function(ele) {
    let current = head;
    for (let i = 0; i < length; i++) {
      if (current.data === ele) {
        return i;
      }
      current = current.next;
    }
    return -1
  }
下标查找
    // 查下标元素
  LinkList.prototype.get = function(index) {
      if (index > length - 1) {
        return;
      }
      let current = head;
      for (let i = 0; i < index; i++) {
        current = current.next;
      }
      return current.data
    }
指定下标删
    //指定位置删
  LinkList.prototype.removeAt = function(index) {
      if (index < 0 || index > length - 1) {
        console.log("index is error!")
        return;
      }
      let current = head;
      for (let i = 0; i < index - 1; i++) {
        current = current.next
      }
      current.next = current.next.next;
      length -= 1;
    }
指定元素删除

思路:既然已经实现了通过属性查下标和通过下标删元素,那么把它们结合封装起来就是通过元素关键字删节点

    //指定元素删
  LinkList.prototype.remove = (element) => {
      //直接调用内部方法组合实现
      const index = this.indexOf(element)
      this.removeAt(index)
    }
修改(思路基本和查找相同)
    //指定下标改
  LinkList.prototype.updateAt = function(position, element) {
      if (position > length - 1) {
        return
      }
      let current = head;
      for (let i = 0; i < position; i++) {
        current = current.next;
      }
      current.data = element;
    }
    //指定内容改
  LinkList.prototype.update = function(oldEle, newEle) {
      if (length == 0) {
        return
      }
      let current = head;
      while (current) {
        if (current.data === oldEle) {
          current.data = newEle
        }
        current = current.next
      }

    }

总结

链表优势是增删数据块,因为只需要改动相邻的元素,而不像数组可能会涉及到大批数据的复制改动地址。

而链表的劣势也很明显,因为不是连续的内存地址,所以查找的内部实现并不像数组那样直接通过地址访问效率高。

全部源码及测试

function LinkList() {
  //节点内部类
  function Node(data) {
    this.data = data;
    this.next = null;
  }
  let head = null;
  let length = 0;
  //末尾插入
  LinkList.prototype.append = function(element) {
      const newEle = new Node(element);
      if (length == 0) { //第一个节点
        head = newEle;
      } else { //不是第一个节点
        let current = head;
        while (current.next) {
          current = current.next;
        }
        current.next = newEle;
      }
      length += 1;
    }
    //指定位置插入
  LinkList.prototype.insert = function(element, position = length) {
      const newEle = new Node(element);
      let current = head;
      if (position <= 0) {
        newEle.next = head;
        head = newEle;
      } else if (position >= length) {
        //增加健壮性 直接插入最后
        position = length;
        while (current.next) {
          current = current.next
        }
        current.next = newEle
      } else {
        let current = head;
        for (let i = 0; i < position - 1; i++) {
          current = current.next;
        }
        newEle.next = current.next;
        current.next = newEle;
      }
      length += 1
    }
    //指定位置删
  LinkList.prototype.removeAt = function(index) {
      if (index < 0 || index > length - 1) {
        console.log("index is error!")
        return;
      }
      let current = head;
      for (let i = 0; i < index - 1; i++) {
        current = current.next
      }
      current.next = current.next.next;
      length -= 1;
    }
    //指定元素删
  LinkList.prototype.remove = (element) => {
      //直接调用内部方法组合实现
      const index = this.indexOf(element)
      this.removeAt(index)
    }
    //指定下标改
  LinkList.prototype.updateAt = function(position, element) {
      if (position > length - 1) {
        return
      }
      let current = head;
      for (let i = 0; i < position; i++) {
        current = current.next;
      }
      current.data = element;
    }
    //指定内容改
  LinkList.prototype.update = function(oldEle, newEle) {
      if (length == 0) {
        return
      }
      let current = head;
      while (current) {
        if (current.data === oldEle) {
          current.data = newEle
        }
        current = current.next
      }

    }
    // 查下标元素
  LinkList.prototype.get = function(index) {
      if (index > length - 1) {
        return;
      }
      let current = head;
      for (let i = 0; i < index; i++) {
        current = current.next;
      }
      return current.data
    }
    //查元素(第一个)
  LinkList.prototype.indexOf = function(ele) {
    let current = head;
    for (let i = 0; i < length; i++) {
      if (current.data === ele) {
        return i;
      }
      current = current.next;
    }
    return -1
  }



  //长度
  LinkList.prototype.size = function() {
      return length;
    }
    //显示
  LinkList.prototype.toString = function() {
    let outString = "";
    let current = head;
    while (current) {
      outString += current.data + " ";
      current = current.next;
    }
    return outString
  }
}

const LL = new LinkList();
LL.append("adsf")
LL.append("让我去")
LL.append("个")
LL.append("华双方都")
LL.append("函数")
  // console.log(LL.toString())
  // console.log(LL.size())
LL.insert("发送到发")
  // console.log(LL.toString())
LL.removeAt(3);
console.log(LL.toString())
LL.remove("发送到发")
console.log(LL.toString())
LL.append("函数")
LL.append("寒")
LL.updateAt(0, "a")
  // console.log(LL.toString())
LL.update("函数", "数组")
  // console.log(LL.toString())
  // console.log(LL.get(5))
  // console.log(LL.indexOf("数组1"))