5分钟图解数据结构之链表!

479 阅读3分钟

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

1 什么是链表?

  • 链表:链表是物理存储单元上非连续的、非顺序的存储结构

链表分类:单链表、双链表、双向链表、循环链表、有序链表。

  • 单链表:单链表节点有两部分:节点数据域节点指针域(指向下一个节点)。

image.png

2 单链表

// 单链表节点结构
class LinkNode{
    constructor(val,next){
      this.value = val;
      this.next = next;// 指向后一个节点指针域
    }
}
  • 单链表代码实现
  • 初始化节点:创建一个虚拟头节点。

为什么需要添加size链表长度、链表头head、链表的尾巴tail?都是为了后面方便处理链表的长度边界情况的分析,以及对链表的尾部处理。

var MyLinkedList = function() {
   this.size = 0;
   this.tail = null;
   this.head = null;
};
  • 获取节点:先创建一个虚拟空节点,从开头遍历链表获取当前节点,为什么传的是index索引,传索引为了后面while遍历链表时,index代表循环的次数,也就是遍历index次改变指针域,直到找到当前节点为止,通俗地,我们也可以把index当作链表索引

image.png

MyLinkedList.prototype.getNode = function(index) {
   if(index<0||index>=this.size) return null;
   let curNode = new LinkNode(0, this.head);// 创建虚拟空节点
   while(index-->=0){
       curNode = curNode.next;// 遍历获取当前节点
   }
   return curNode;// 返回当前索引的节点
};
  • 添加节点:创建一个节点,从链表头或链表中或链表尾添加节点。

image.png

MyLinkedList.prototype.addAtHead = function(val) {
   const node = new LinkNode(val,this.head);// 创建节点
   this.head = node;// 当前链表的头节点为刚刚新增的
   this.size++;
   if(!this.tail){// 如果链表尾节点不存在,也就是当前链表只有一个节点
       this.tail = node;
   }
};

image.png

MyLinkedList.prototype.addAtTail = function(val) {
   const node = new LinkNode(val,null);// 创建一个指向null的尾节点
   this.size++;
   if(this.tail){//当前链表的尾节点不为空
       this.tail.next = node;// 先更新尾节点的指针域
       this.tail = node;// 更新尾节点
       return;
   }
   this.head = node;// 当链表为空时
   this.tail = node;// 当链表为空时
};

image.png

MyLinkedList.prototype.addAtIndex = function(index, val) {
  if(index>this.size) return;
  if(index<=0){
    this.addAtHead(val);// 执行头节点的添加
    return;
  }else if(index===this.size){
    this.addAtTail(val);// 执行尾节点添加
    return;
  }
  const node = this.getNode(index-1);// 查找当前索引的上一个节点
  node.next = new LinkNode(val,node.next);// 创建一个指向当前索引的后一个节点的节点
  this.size++;
};
  • 删除节点:删除链表头或链表中或链表尾节点。当删除链表头元素地时候,直接更新head地指针域指向。当删除链表尾或链表中的节点时,直接更新tail的前一个节点指针域指向。
MyLinkedList.prototype.deleteAtIndex = function(index) {
   if(index<0||index>=this.size) return;
   if(index===0){
       this.head = this.head.next;
       if(index===this.size-1){
           this.tail = this.head;
       }
       this.size--;
       return;
   }
   const preNode = this.getNode(index-1);;
   preNode.next = preNode.next.next;
   if(index===this.size-1){
     this.tail = preNode;
   }
   this.size--;
};

3 双链表

  • 双链表:既可以从头遍历到尾,也可以从尾巴遍历到头。也就是既有向前连接的引用,也有一个向后连接的引用。双向链表的结构:节点的数据域节点的指针域

    image.png

    // 双链表节点结构
    class DoubleLinkNode{
        constructor(val,prev,next){
          this.prev = prev;// 指向前一个节点指针域
          this.value = val;
          this.next = next;// 指向后一个节点指针域
        }
    }
    // 初始化链表
    var MyDoubleLink = function () {
        this.head = null;
        this.tail = null;
        this.size = 0;
    }
    
    
  • 双链表的代码实现:双链表区别于单链表的就是对prev指针域的处理,在增删查改的操作当中都需要考虑节点的prev的指向。

  • 获取节点:区别单链表主要是对边界的处理(双链表是index > this.size,而单链表可以等于),因为要考虑下一个节点

// 区别于单链表的getNode
if (index < 0 || index > this.size) return null;
let node = new DoubleLinkNode(0, null, this.head);
  • 在链表头添加元素:与单链表不不一样的是定义节点部分。

    let node = new DoubleLinkNode(val, null, this.head);
    
  • 在链表尾添加元素:与单链表不一样的也是定义节点部分。

    let node = new DoubleLinkNode(val, this.tail, null);
    
  • 在链表中添加元素:关键在于非链表头尾元素的添加,需要获取要添加元素位置的上一个节点以及当前节点,控制当前节点的prev指针域和控制上一个节点next指针域。

image.png

MyDoubleLink.prototype.addAtIndex = function (index, val) {
    if (index > this.size) return;
    if (index <= 0) {
        this.addAtHead(val);
        return;
    } else if (index === this.size) {
        this.addAtTail(val);
        return;
    }
    let node = this.getNode(index - 1);// 获取上一个节点
    let addNode = new DoubleLinkNode(val, node, node.next);// 创建节点
    const nextNode = this.getNode(index);// 获取当前节点
    if (nextNode) {// 如果存在
        nextNode.prev = addNode;
    }
    node.next = addNode;
}
  • 删除链表元素:关键在于非链表头尾元素的删除,需要获取要删除元素位置的上一个节点以及下一个节点,控制当前节点的上一节点的next指针域当前节点的下一个节点prev指针域的指向。

image.png

MyDoubleLink.prototype.deleteAtIndex = function (index) {
    if (index < 0 || index >= this.size) return;
    if (index === 0) {
        this.head = this.head.next;
        if (index === this.size - 1) {
            this.tail = this.head;
        }
        this.size--;
        return;
    }
    let preNode = this.getNode(index - 1);// 获取当前链表的上一个节点
    let nextNode = this.getNode(index+1);// 获取当前链表的下一个节点
    preNode.next = preNode.next.next;
    if (nextNode) {
        nextNode.prev = preNode.next.prev;
    }

    if (index === this.size - 1) {
        this.tail = preNode;
    }
    this.size--;
};

参考资料

「代码随想录」带你搞定链表!707. 设计链表:【链表基础题目】详解