JavaScript实现双向链表

一、双向链表简介

双向链表:既可以从头遍历到尾,又可以从尾遍历到头。也就是说链表连接的过程是双向的,它的实现原理是:一个节点既有向前连接的引用,也有一个向后连接的引用。

双向链表的缺点:

  • 每次在插入或删除某个节点时,都需要处理四个引用,而不是两个,实现起来会困难些;
  • 相对于单向链表,所占内存空间更大一些;
  • 但是,相对于双向链表的便利性而言,这些缺点微不足道。

双向链表的结构:
在这里插入图片描述

  • 双向链表不仅有head指针指向第一个节点,而且有tail指针指向最后一个节点;
  • 每一个节点由三部分组成:item储存数据prev指向前一个节点next指向后一个节点
  • 双向链表的第一个节点的prev指向null
  • 双向链表的最后一个节点的next指向null

双向链表常见的操作(方法):

  • append(element):向链表尾部添加一个新的项;
  • insert(position,element):向链表的特定位置插入一个新的项;
  • get(element):获取对应位置的元素;
  • indexOf(element):返回元素在链表中的索引,如果链表中没有元素就返回-1;
  • update(position,element):修改某个位置的元素;
  • removeAt(position):从链表的特定位置移除一项;
  • isEmpty():如果链表中不包含任何元素,返回trun,如果链表长度大于0则返回false
  • size():返回链表包含的元素个数,与数组的length属性类似;
  • toString():由于链表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法,让其只输出元素的值;
  • forwardString():返回正向遍历节点字符串形式;
  • backwordString():返回反向遍历的节点的字符串形式;

二、封装双向链表类

2.0 创建双向链表类

先创建双向链表类DoubleLinklist,并添加基本属性,再实现双向链表的常用方法:

    // 封装双向链表类
    function DoubleLinkedList() {
      // 封装内部类:节点类
      function Node(data) {
        this.data = data
        this.pre = null
        this.next = null
      }

      // 属性
      this.head = null
      this.tail = null
      this.length = 0
    }

2.1 append(element)

代码实现:

      // 常见的方法
      // 1.append方法
      DoubleLinkedList.prototype.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
      }

过程详解:

添加节点时分为多种情况:

  • 情况1:添加的是第一个节点:只需要让head和tail都指向新节点即可;
    在这里插入图片描述
  • 情况2:添加的不是第一个节点,如下图所示:只需要改变相关引用的指向即可。
  • 通过:newNode.prev = this.tail:建立指向1;
  • 通过:this.tail.next = newNode:建立指向2;
  • 通过:this.tail = newNode:建立指向3

要注意改变变量指向的顺序,最后修改tail指向,这样未修改前tail始终指向原链表的最后一个节点。
在这里插入图片描述
在这里插入图片描述

2.2 toString()汇总

代码实现:

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

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

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

过程详解:

三种获取字符串的方法:toString()forwardString()、**backwardString()**实现原理相似,仅以backWardString方法为例:

  • 定义current变量记录当前指向的节点。首先让current指向第一个节点,然后通过 current = current.next 依次向后遍历。在while循环中以(current)作为条件遍历链表,只要current != null就一直遍历,由此可获取链表所有节点的数据。
    在这里插入图片描述

2.3 insert(position,element)

代码实现:

      // 3.insert方法
      DoubleLinkedList.prototype.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.prev = 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表示插入成功
      }

过程详解:

插入节点可分为多种情况:

当原链表为空时:

  • 情况1:插入的新节点是链表的第一个节点;只需要让head和tail都指向newNode即可。
    在这里插入图片描述

当原链表不为空时:

  • 情况2:当position == 0,即在链表的首部添加节点:如下图所示:
    在这里插入图片描述
  • 首先,通过:this.head.prev = newNode,改变指向1;
  • 然后,通过:newNode.next = this.head,改变指向2;
  • 最后,通过:this.head = newNode,改变指向3;
    在这里插入图片描述
  • 情况3:position == this.length,即在链表的尾部添加节点,如下图所示:
    在这里插入图片描述
  • 首先,通过:this.tail.next = newNode,改变指向1;(注意这里使用this.tail指向原链表最后一个节点,而不是this.head。因为当length>1时,this.head != this.tail。)
  • 然后,通过:newNode.prev = this.tail,改变指向2;
  • 最后,通过:this.tail = newNode,改变指向3;
    在这里插入图片描述
  • 情况4:0 < position < this.length,即在链表的中间插入新节点,假设在position = 1的位置插入,如下图所示:
    在这里插入图片描述
    首先,需要定义变量current按照之前的思路,通过while循环找到position位置的后一个节点,循环结束后index = position

如下图所示:当position = 1时,current就指向了Node2。这样操作current就等同于间接地操作Node2,还可以通过current.prev间接获取Node1。得到了newNode的前一个节点和后一个节点就可以通过改变它们的prev和next变量的指向来插入newNode了。

在这里插入图片描述

  • 通过:newNode.next = current,改变指向1;
  • 通过:newNode.prev = current.prev,改变指向2;
  • 通过:current.prev.next = newNode,改变指向3;
  • 通过:current.prev = current,改变指向4;

注意必须最后才修改current.prev的指向,不然就无法通过current.prev获取需要操作的Node1了。
在这里插入图片描述

2.4 get(position)

代码实现:

      // 3.insert方法
      DoubleLinkedList.prototype.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方法
      DoubleLinkedList.prototype.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
      }

过程详解:

定义两个变量current和index,按照之前的思路通过while循环遍历分别获取当前节点和对应的索引值index,直到找到需要获取的position位置后的一个节点,此时index = pos =x,然后return current.data即可。

如果链表的节点数量很多时,这种查找方式效率不高,改进方法为:

一定要通过this.length来获取链表的节点数否则就会报错。
  • this.length / 2 > position:从头(head)开始遍历;
  • 当this.length / 2 < position:从尾(tail)开始遍历;
    在这里插入图片描述

2.5 indexOf(element)

代码实现:

      // 5.indexOf方法
      DoubleLinkedList.prototype.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
      }

过程详解:

以(current)作为条件,通过while循环遍历链表中的所有节点(停止条件为current = null)。在遍历每个节点时将current指向的当前节点的data和传入的data进行比较即可。

2.7 update(position,element)

代码实现:

      // 6.update方法
      DoubleLinkedList.prototype.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//表示成功修改
      }

过程详解:

以(index++ < position)为条件,通过while循环遍历链表中的节点(停止条件为index = position)。循环结束后,current指向需要修改的节点。

2.8 removeAt(position)

代码实现:

      // 7.removeAt方法
      DoubleLinkedList.prototype.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 // 返回被删除节点的数据
      }

过程详解:

删除节点时有多种情况:

当链表的length = 1时:

  • 情况1:删除链表中的所有节点:只需要让链表的head和tail指向null即可。

在这里插入图片描述
当链表的length > 1时:

  • 情况2:删除链表中的第一个节点:

通过:this.head.next.prev = null,改变指向1;

通过:this.head = this.head.next,改变指向2;

虽然Node1有引用指向其它节点,但是没有引用指向Node1,那么Node1会被自动回收。
在这里插入图片描述

  • 情况3:删除链表中的最后一个节点:

通过:this.tail.prev.next = null,修改指向1;

通过:this.tail = this.tail.prev,修改指向2;

在这里插入图片描述

  • 情况4:删除链表中间的节点:

通过while循环找到需要删除的节点,比如position = x,那么需要删除的节点就是Node(x+1),如下图所示:
在这里插入图片描述
通过:current.next.prev = current.prev,修改指向1;

通过:current.prev.next = current.next,修改指向2;

这样就没有引用指向Node(x+1)了(current虽指向Node(x+1),但current时临时变量,该方法执行完就会被销毁),随后Node(x+1)就会被自动删除。

在这里插入图片描述

2.9 其他方法

其他方法包括:remove(element)isEmpty()size()getHead()、getTail()

代码实现:

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

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

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

2.10 完整实现

  <script>
    // 封装双向链表类
    function DoubleLinkedList() {
      // 封装内部类:节点类
      function Node(data) {
        this.data = data
        this.pre = null
        this.next = null
      }

      // 属性
      this.head = null
      this.tail = null
      this.length = 0

      // 常见的方法
      // 1.append方法
      DoubleLinkedList.prototype.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方法
      DoubleLinkedList.prototype.toString = () => {
        return this.backwardString()
      }
      // 2.2 forwardString方法
      DoubleLinkedList.prototype.forwardString = () => {
        // 1.定义变量
        let current = this.tail
        let resStr = ''

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

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

      // 3.insert方法
      DoubleLinkedList.prototype.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方法
      DoubleLinkedList.prototype.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方法
      DoubleLinkedList.prototype.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方法
      DoubleLinkedList.prototype.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方法
      DoubleLinkedList.prototype.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方法
      DoubleLinkedList.prototype.remove = data => {
        // 1.根据data获取下标值
        let index = this.indexOf(data)

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

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

    // 测试代码
    // 1.创建双向链表
    let list = new DoubleLinkedList()

    // // 2.测试append方法
    // list.append('aaa')
    // list.append('bbb')
    // list.append('ccc')
    // list.append('ddd')
    // console.log(list);

    // // 3.测试字符串方法   
    // console.log(list.toString());
    // console.log(list.forwardString());
    // console.log(list.backwardString());

    // // 4.测试insert方法
    // list.insert(0, '插入链表的第一个元素')
    // list.insert(0, '在链表首部插入元素')
    // list.insert(1, '在链表中间插入元素')
    // console.log(list);
    // list.insert(3, '在链表尾部插入元素')
    // console.log(list);

    // // 5.测试get方法
    // list.append('a')
    // list.append('b')
    // list.append('b1')
    // list.append('b2')
    // list.append('b3')
    // list.append('b4')
    // list.append('b5')
    // list.append('b6')
    // list.append('b7')
    // console.log(list.get(0));
    // console.log(list.get(7));

    // // 6.测试indexOf方法
    // list.append('a')
    // list.append('b')
    // list.append('c')
    // console.log(list.indexOf('a'));
    // console.log(list.indexOf('c'));

    // // 7.测试update方法
    // list.append('a')
    // list.append('b')
    // console.log(list.update(1, 'c'));
    // console.log(list);

    // // 8.测试removeAt方法
    // list.append('a')
    // list.append('b')
    // list.append('c')
    // console.log(list.removeAt(1));
    // console.log(list);

    /*------------其他方法的测试--------------*/
    list.append('a')
    list.append('b')
    list.append('c')
    list.append('d')
    // remove方法
    console.log(list.remove('a'));
    console.log(list);
    // isEmpty方法
    console.log(list.isEmpty());
    // size方法
    console.log(list.size());
    // getHead方法
    console.log(list.getHead());
    // getTead方法
    console.log(list.getTail());
  </script>
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值