zoukankan      html  css  js  c++  java
  • 【JavaScript数据结构系列】06-双向链表DoublyLinkedList

    【JavaScript数据结构系列】06-双向链表DoublyLinkedList

    码路工人 CoderMonkey
    转载请注明作者与出处

    1. 认识双向链表

    • 不同于普通链表/单向链表,双向链表最突出的区别就是,

    每一个元素节点上,除了保存数据,还有两个地址引用的指针,
    一个指向前一个元素,一个指向后一个元素。

    • 我们比上一节还增加了一个 TAIL 的属性(末尾)
    • 能够以 HEAD -> TAIL 的方向遍历
    • 也能以 TAIL -> HEAD 的方向遍历

    DoublyLinkedList.png

    2. 常用方法

    双向链表与单向链表非常相似,常用方法中除了方向遍历,其它都大致相同。

    方法 描述
    append(data) 向链表添加元素
    insert(position, data) 向指定位置插入元素
    remove(data) 删除元素
    removeAt(position) 删除指定位置元素
    update(position, data) 更新指定位置元素
    getItem(position) 查找指定位置元素
    indexOf(data) 获取元素位置
    forwardTraverse(cb) 向Head方向遍历
    backwardTraverse(cb) 向Tail方向遍历
    traverse(cb, reversal) 指定方向遍历
    getHead() 获取首元素数据
    getTail() 获取尾元素数据
    size() 获取链表大小
    isEmpty() 判断链表是否为空
    clear() 清空链表
    toString() 字符串化

    3. 代码实现

    注:
    ES6 版的代码实现请查看 npm 包 data-struct-js 代码
    Github/Gitee 上都能找到

    npm install data-struct-js

    封装双向链表类

    /**
     * 链表:双向链表
     */
    function DoublyLinkedList() {
      // 记录链表首元素
      this.__head = null
      // 记录链表尾元素
      this.__tail = null
      // 记录链表元素个数
      this.__count = 0
    
      // 用Node表示链表内部元素
      function Node(data) {
        this.data = data
        this.prev = null	// 指向前元素
        this.next = null	// 指向后元素
    
        Node.prototype.toString = function () {
          return this.data.toString()
        }
      }
    }
    

    3.1 append(data)

    向链表添加元素

    DoublyLinkedList.prototype.append = function (data) {
      var newNode = new Node(data)
    
      // 1.判断链表是否为空
      if (this.__count === 0) {
        // 新元素既是首也是尾
        this.__head = newNode
        this.__tail = newNode
      }
      // 2.非空链表时,添加到尾部
      else {
        this.__tail.next = newNode
        newNode.prev = this.__tail
        this.__tail = newNode
      }
    
      // 3.计数加1
      this.__count += 1
    }
    

    3.2 insert(position, data)

    向链表中插入元素

    DoublyLinkedList.prototype.insert = function (position, data) {
      // 1.边界检查(插入位置)
      if (position < 0 || position > this.__count) return false
    
      var newNode = new Node(data)
    
      // 2.插入元素时链表为空
      if (this.__count === 0) {
        this.__head = newNode
        this.__tail = newNode
      }
      // 3.链表非空
      else {
        // 3.1插入到链表头部
        if (position === 0) {
          newNode.next = this.__head
          this.__head.prev = newNode
          this.__head = newNode
        }
        // 3.2插入到链表尾部
        else if (position === this.__count) {
          this.__tail.next = newNode
          newNode.prev = this.__tail
          this.__tail = newNode
        }
        // 3.3以外
        else {
          var current = this.__head
          var index = 0
          while (index < position) {
            current = current.next
            index++
          }
          current.prev.next = newNode
          newNode.prev = current.prev
          newNode.next = current
          current.prev = newNode
        }
      }
    
      // 4.计数加1
      this.__count += 1
    
      return true
    }
    

    3.3 removeAt(position)

    删除指定位置元素

    DoublyLinkedList.prototype.removeAt = function (position) {
      // 1.边界检查
      if (position < 0 || position >= this.__count) return null
    
      var current = this.__head
    
      // 2.只有一个元素
      if (this.size() === 1) {
        this.__head = null
        this.__tail = null
      }
      // 3.多个元素的情况
      else {
        // 3.1 删首
        if (position === 0) {
          current = this.__head
    
          this.__head = current.next
          current.next.prev = null
        }
        // 3.2 删尾
        else if (position === this.__count - 1) {
          current = this.__tail
          this.__tail = current.prev
          this.__tail.next = null
        }
        // 3.3 以外
        else {
          var index = 0
          var current = this.__head
          while (index < position) {
            current = current.next
            index += 1
          }
          current.prev.next = current.next
          current.next.prev = current.prev
        }
      }
    
      // 4.计数减1
      this.__count -= 1
    
      return current.data
    }
    

    3.4 remove(data)

    删除指定数据的元素

    DoublyLinkedList.prototype.remove = function (data) {
      // 根据指定数据取得下标值
      var index = this.indexOf(data)
    
      // 检查下标值是否正常取到
      if (index === -1) return null
    
      // 根据取到的下标,调用 removeAt 方法进行删除
      return this.removeAt(index)
    }
    

    3.5 update(position, data)

    更新指定位置元素的数据

    DoublyLinkedList.prototype.update = function (position, data) {
      // 1.边界检查
      if (position < 0 || position >= this.__count) return false
    
      var current = this.__head
      var index = 0
    
      // 2.找到指定下标位置元素
      while (index < position) {
        current = current.next
      }
    
      // 3.修改数据
      current.data = data
    
      return true
    }
    

    3.6 getItem(position)

    获取指定位置的元素数据

    DoublyLinkedList.prototype.getItem = function (position) {
      // 1.边界检查
      if (position < 0 || position >= this.__count) return
    
      var current = this.__head
      var index = 0
    
      // 2.找到指定下标位置元素
      // => TODO:改善:根据position所在位置选择从Head还是从Tail开始查找
      while (index++ < position) {
        current = current.next
      }
      return current.data
    }
    

    3.7 indexOf(data)

    获取指定数据的元素位置(下标)

    DoublyLinkedList.prototype.indexOf = function (data) {
      var current = this.__head
      var index = 0
    
      // 查找指定数据的节点
      while (current) {
        if (current.data == data) {
          return index
        }
        current = current.next
        index += 1
      }
      // 没有找到
      return -1
    }
    

    3.8 backwardTraverse(cb)

    向Head方向遍历,通过回调函数传出每一个元素数据

    // Backward: Tail -> Head
    DoublyLinkedList.prototype.backwardTraverse = function (cb) {
      // TODO: cb 参数检查(回调函数)
    
      var current = this.__tail
      while (current) {
        cb(current.data)
        current = current.prev
      }
    }
    

    3.9 forwardTraverse(cb)

    向Tail方向遍历,通过回调函数传出每一个元素数据

    // Forward: Head -> Tail
    DoublyLinkedList.prototype.forwardTraverse = function (cb) {
      // TODO: cb 参数检查(回调函数)
    
      var current = this.__head
      while (current) {
        cb(current.data)
        current = current.next
      }
    }
    

    3.10 traverse(cb, reversal)

    指定遍历方向,调用上面的两个单独的方法进行遍历

    DoublyLinkedList.prototype.traverse = function (cb, reversal) {
      if (!reversal) return this.forwardTraverse(cb)
      return backwardTraverse(cb)
    }
    

    3.11 getHead()

    获取首元素数据

    DoublyLinkedList.prototype.getHead = function () {
      if (this.__head == null) return null
      return this.__head.data
    }
    

    3.12 getTail()

    获取尾元素数据

    DoublyLinkedList.prototype.getTail = function () {
      if (this.__tail == null) return null
      return this.__tail.data
    }
    

    3.13 size()

    查看元素个数方法

    DoublyLinkedList.prototype.size = function () {
      return this.__count
    }
    

    3.14 isEmpty()

    判空方法

    DoublyLinkedList.prototype.isEmpty = function () {
      return this.__count === 0
    }
    

    3.15 clear()

    实现分析:
    Head、Tail指向全都置空
    计数清零

    DoublyLinkedList.prototype.clear = function () {
      this.__head = null
      this.__tail = null
      this.__count = 0
    }
    

    3.16 toString()

    为了方便查看实现的字符串化方法

    DoublyLinkedList.prototype.toString = function () {
      var str = '[HEAD]'
      var current = this.__head
      while (current) {
        str += ' -> ' + current.data
        current = current.next
      }
      str += str == '[HEAD]' ?
        ' -> Null <- [TAIL]' :
      ' <- [TAIL]'
      return str
    }
    

    3.17 完整代码

    /**
     * 链表:双向链表
     */
    function DoublyLinkedList() {
      // 记录链表首元素
      this.__head = null
      // 记录链表尾元素
      this.__tail = null
      // 记录链表元素个数
      this.__count = 0
    
      // 用Node表示链表内部元素
      function Node(data) {
        this.data = data
        this.prev = null // 指向前元素
        this.next = null // 指向后元素
    
        Node.prototype.toString = function () {
          return this.data.toString()
        }
      }
    
      // 添加节点
      DoublyLinkedList.prototype.append = function (data) {
        var newNode = new Node(data)
    
        // 1.判断链表是否为空
        if (this.__count === 0) {
          // 新元素既是首也是尾
          this.__head = newNode
          this.__tail = newNode
        }
        // 2.非空链表时,添加到尾部
        else {
          this.__tail.next = newNode
          newNode.prev = this.__tail
          this.__tail = newNode
        }
    
        // 3.计数加1
        this.__count += 1
      }
    
      // 插入节点
      DoublyLinkedList.prototype.insert = function (position, data) {
        // 1.边界检查(插入位置)
        if (position < 0 || position > this.__count) return false
    
        var newNode = new Node(data)
    
        // 2.插入元素时链表为空
        if (this.__count === 0) {
          this.__head = newNode
          this.__tail = newNode
        }
        // 3.链表非空
        else {
          // 3.1插入到链表头部
          if (position === 0) {
            newNode.next = this.__head
            this.__head.prev = newNode
            this.__head = newNode
          }
          // 3.2插入到链表尾部
          else if (position === this.__count) {
            this.__tail.next = newNode
            newNode.prev = this.__tail
            this.__tail = newNode
          }
          // 3.3以外
          else {
            var current = this.__head
            var index = 0
            while (index < position) {
              current = current.next
              index++
            }
            current.prev.next = newNode
            newNode.prev = current.prev
            newNode.next = current
            current.prev = newNode
          }
        }
    
        // 4.计数加1
        this.__count += 1
    
        return true
      }
    
      // 删除指定位置节点
      DoublyLinkedList.prototype.removeAt = function (position) {
        // 1.边界检查
        if (position < 0 || position >= this.__count) return null
    
        var current = this.__head
    
        // 2.只有一个元素
        if (this.size() === 1) {
          this.__head = null
          this.__tail = null
        }
        // 3.多个元素的情况
        else {
          // 3.1 删首
          if (position === 0) {
            current = this.__head
    
            this.__head = current.next
            current.next.prev = null
          }
          // 3.2 删尾
          else if (position === this.__count - 1) {
            current = this.__tail
            this.__tail = current.prev
            this.__tail.next = null
          }
          // 3.3 以外
          else {
            var index = 0
            var current = this.__head
            while (index < position) {
              current = current.next
              index += 1
            }
            current.prev.next = current.next
            current.next.prev = current.prev
          }
        }
    
        // 4.计数减1
        this.__count -= 1
    
        return current.data
      }
    
      // 删除节点
      DoublyLinkedList.prototype.remove = function (data) {
        // 根据指定数据取得下标值
        var index = this.indexOf(data)
    
        // 检查下标值是否正常取到
        if (index === -1) return null
    
        // 根据取到的下标,调用 removeAt 方法进行删除
        return this.removeAt(index)
      }
    
      // 更新节点数据
      DoublyLinkedList.prototype.update = function (position, data) {
        // 1.边界检查
        if (position < 0 || position >= this.__count) return false
    
        var current = this.__head
        var index = 0
    
        // 2.找到指定下标位置元素
        while (index < position) {
          current = current.next
        }
    
        // 3.修改数据
        current.data = data
    
        return true
      }
    
      // 获取节点数据
      DoublyLinkedList.prototype.getItem = function (position) {
        // 1.边界检查
        if (position < 0 || position >= this.__count) return
    
        var current = this.__head
        var index = 0
    
        // 2.找到指定下标位置元素
        // => TODO:改善:根据position所在位置选择从Head还是从Tail开始查找
        while (index++ < position) {
          current = current.next
        }
        return current.data
      }
    
      // 获取下标
      DoublyLinkedList.prototype.indexOf = function (data) {
        var current = this.__head
        var index = 0
    
        // 查找指定数据的节点
        while (current) {
          if (current.data == data) {
            return index
          }
          current = current.next
          index += 1
        }
        // 没有找到
        return -1
      }
    
      DoublyLinkedList.prototype.traverse = function (cb, reversal) {
        if (!reversal) return this.forwardTraverse(cb)
        return backwardTraverse(cb)
      }
    
      // Backward: Tail -> Head
      DoublyLinkedList.prototype.backwardTraverse = function (cb) {
        // TODO: cb 参数检查(回调函数)
    
        var current = this.__tail
        while (current) {
          cb(current.data)
          current = current.prev
        }
      }
    
      // Forward: Head -> Tail
      DoublyLinkedList.prototype.forwardTraverse = function (cb) {
        // TODO: cb 参数检查(回调函数)
    
        var current = this.__head
        while (current) {
          cb(current.data)
          current = current.next
        }
      }
    
      DoublyLinkedList.prototype.getHead = function () {
        if (this.__head == null) return null
        return this.__head.data
      }
    
      DoublyLinkedList.prototype.getTail = function () {
        if (this.__tail == null) return null
        return this.__tail.data
      }
    
      DoublyLinkedList.prototype.size = function () {
        return this.__count
      }
    
      DoublyLinkedList.prototype.isEmpty = function () {
        return this.__count === 0
      }
    
      DoublyLinkedList.prototype.clear = function () {
        this.__head = null
        this.__tail = null
        this.__count = 0
      }
    
      DoublyLinkedList.prototype.toString = function () {
        var str = '[HEAD]'
        var current = this.__head
        while (current) {
          str += ' -> ' + current.data
          current = current.next
        }
        str += str == '[HEAD]' ?
          ' -> Null <- [TAIL]' :
        ' <- [TAIL]'
        return str
      }
    }
    

    4. 测试一下

    // ---------------------------------------------
    // Test: DoublyLinkedList
    // ---------------------------------------------
    console.log('----Test: DoublyLinkedList----')
    
    var dLst = new DoublyLinkedList()
    
    dLst.append('a')
    dLst.append('b')
    dLst.append('c')
    dLst.forwardTraverse(function (val) {
        console.log('forward-traversing: ', val)
    })
    dLst.backwardTraverse(function (val) {
        console.log('backward-traversing: ', val)
    })
    dLst.insert(0, 'Insert-Index=0')
    dLst.insert(3, 'Insert-Index=3')
    dLst.insert(5, 'Insert-Index=Count')
    console.log(dLst.toString())
    console.log('getItem(5) => ', dLst.getItem(5))
    console.log('remove("c") => ', dLst.remove('c'))
    console.log('removeAt(3) => ', dLst.removeAt(3))
    console.log('Result ↓ 
    ', dLst.toString())
    dLst.clear()
    console.log('After Clear : ', dLst.toString())
    

    查看结果:

    ----Test: DoublyLinkedList----
    forward-traversing:  c
    forward-traversing:  b
    forward-traversing:  a
    backward-traversing:  a
    backward-traversing:  b
    backward-traversing:  c
    [HEAD] -> Insert-Index=0 -> a -> b -> Insert-Index=3 -> c -> Insert-Index=Count <- [TAIL]
    getItem(5) =>  Insert-Index=Count
    remove("c") =>  c
    removeAt(3) =>  Insert-Index=3
    [removeAt(3)]--Result ↓ 
     [HEAD] -> Insert-Index=0 -> a -> b -> Insert-Index=Count <- [TAIL]
    After Clear :  [HEAD] -> Null <- [TAIL]
    

    确认无误,收工。


    做了一份 npm 工具包 data-struct-js
    基于 ES6 实现的 JavaScript 数据结构,
    虽然这个小轮子很少会被使用,
    也许对于初学者学习 JavaScript 会有点帮助。
    只要简单 install 一下即可,感兴趣的话还可以去
    GitHub / Gitee 看源码。(Star 表支持~)

    npm install data-struct-js --save-dev
    

    https://github.com/CoderMonkie/data-struct-js
    https://gitee.com/coder-monkey/data-struct-js

    最后,感谢您的阅读和支持~


    -end-
  • 相关阅读:
    isteven-multi-select
    javascript学习之js对象和json对象(obj=JSON.parse(stu))
    jquery学习之路之元素类的切换toggle
    jquery学习之路之测验错题集
    jquery学习之路之核心函数
    jquery学习之路之选择过滤
    较完整的angularjs+bootstrap应用程序接口controller
    angularjs+requeirjs模块化的应用程序接口
    SIP协议参数详情
    SIP初步认识
  • 原文地址:https://www.cnblogs.com/CoderMonkie/p/js-data-struct-doublylinkedlist.html
Copyright © 2011-2022 走看看