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

码路工人 CoderMonkey

转载请注明作者与出处

1. 认识双向链表

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

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

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

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

封装双向链表类

  1. /**
  2. * 链表:双向链表
  3. */
  4. function DoublyLinkedList() {
  5. // 记录链表首元素
  6. this.__head = null
  7. // 记录链表尾元素
  8. this.__tail = null
  9. // 记录链表元素个数
  10. this.__count = 0
  11. // 用Node表示链表内部元素
  12. function Node(data) {
  13. this.data = data
  14. this.prev = null // 指向前元素
  15. this.next = null // 指向后元素
  16. Node.prototype.toString = function () {
  17. return this.data.toString()
  18. }
  19. }
  20. }

3.1 append(data)

向链表添加元素

  1. DoublyLinkedList.prototype.append = function (data) {
  2. var newNode = new Node(data)
  3. // 1.判断链表是否为空
  4. if (this.__count === 0) {
  5. // 新元素既是首也是尾
  6. this.__head = newNode
  7. this.__tail = newNode
  8. }
  9. // 2.非空链表时,添加到尾部
  10. else {
  11. this.__tail.next = newNode
  12. newNode.prev = this.__tail
  13. this.__tail = newNode
  14. }
  15. // 3.计数加1
  16. this.__count += 1
  17. }

3.2 insert(position, data)

向链表中插入元素

  1. DoublyLinkedList.prototype.insert = function (position, data) {
  2. // 1.边界检查(插入位置)
  3. if (position < 0 || position > this.__count) return false
  4. var newNode = new Node(data)
  5. // 2.插入元素时链表为空
  6. if (this.__count === 0) {
  7. this.__head = newNode
  8. this.__tail = newNode
  9. }
  10. // 3.链表非空
  11. else {
  12. // 3.1插入到链表头部
  13. if (position === 0) {
  14. newNode.next = this.__head
  15. this.__head.prev = newNode
  16. this.__head = newNode
  17. }
  18. // 3.2插入到链表尾部
  19. else if (position === this.__count) {
  20. this.__tail.next = newNode
  21. newNode.prev = this.__tail
  22. this.__tail = newNode
  23. }
  24. // 3.3以外
  25. else {
  26. var current = this.__head
  27. var index = 0
  28. while (index < position) {
  29. current = current.next
  30. index++
  31. }
  32. current.prev.next = newNode
  33. newNode.prev = current.prev
  34. newNode.next = current
  35. current.prev = newNode
  36. }
  37. }
  38. // 4.计数加1
  39. this.__count += 1
  40. return true
  41. }

3.3 removeAt(position)

删除指定位置元素

  1. DoublyLinkedList.prototype.removeAt = function (position) {
  2. // 1.边界检查
  3. if (position < 0 || position >= this.__count) return null
  4. var current = this.__head
  5. // 2.只有一个元素
  6. if (this.size() === 1) {
  7. this.__head = null
  8. this.__tail = null
  9. }
  10. // 3.多个元素的情况
  11. else {
  12. // 3.1 删首
  13. if (position === 0) {
  14. current = this.__head
  15. this.__head = current.next
  16. current.next.prev = null
  17. }
  18. // 3.2 删尾
  19. else if (position === this.__count - 1) {
  20. current = this.__tail
  21. this.__tail = current.prev
  22. this.__tail.next = null
  23. }
  24. // 3.3 以外
  25. else {
  26. var index = 0
  27. var current = this.__head
  28. while (index < position) {
  29. current = current.next
  30. index += 1
  31. }
  32. current.prev.next = current.next
  33. current.next.prev = current.prev
  34. }
  35. }
  36. // 4.计数减1
  37. this.__count -= 1
  38. return current.data
  39. }

3.4 remove(data)

删除指定数据的元素

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

3.5 update(position, data)

更新指定位置元素的数据

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

3.6 getItem(position)

获取指定位置的元素数据

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

3.7 indexOf(data)

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

  1. DoublyLinkedList.prototype.indexOf = function (data) {
  2. var current = this.__head
  3. var index = 0
  4. // 查找指定数据的节点
  5. while (current) {
  6. if (current.data == data) {
  7. return index
  8. }
  9. current = current.next
  10. index += 1
  11. }
  12. // 没有找到
  13. return -1
  14. }

3.8 backwardTraverse(cb)

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

  1. // Backward: Tail -> Head
  2. DoublyLinkedList.prototype.backwardTraverse = function (cb) {
  3. // TODO: cb 参数检查(回调函数)
  4. var current = this.__tail
  5. while (current) {
  6. cb(current.data)
  7. current = current.prev
  8. }
  9. }

3.9 forwardTraverse(cb)

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

  1. // Forward: Head -> Tail
  2. DoublyLinkedList.prototype.forwardTraverse = function (cb) {
  3. // TODO: cb 参数检查(回调函数)
  4. var current = this.__head
  5. while (current) {
  6. cb(current.data)
  7. current = current.next
  8. }
  9. }

3.10 traverse(cb, reversal)

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

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

3.11 getHead()

获取首元素数据

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

3.12 getTail()

获取尾元素数据

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

3.13 size()

查看元素个数方法

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

3.14 isEmpty()

判空方法

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

3.15 clear()

实现分析:

Head、Tail指向全都置空

计数清零

  1. DoublyLinkedList.prototype.clear = function () {
  2. this.__head = null
  3. this.__tail = null
  4. this.__count = 0
  5. }

3.16 toString()

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

  1. DoublyLinkedList.prototype.toString = function () {
  2. var str = '[HEAD]'
  3. var current = this.__head
  4. while (current) {
  5. str += ' -> ' + current.data
  6. current = current.next
  7. }
  8. str += str == '[HEAD]' ?
  9. ' -> Null <- [TAIL]' :
  10. ' <- [TAIL]'
  11. return str
  12. }

3.17 完整代码

  1. /**
  2. * 链表:双向链表
  3. */
  4. function DoublyLinkedList() {
  5. // 记录链表首元素
  6. this.__head = null
  7. // 记录链表尾元素
  8. this.__tail = null
  9. // 记录链表元素个数
  10. this.__count = 0
  11. // 用Node表示链表内部元素
  12. function Node(data) {
  13. this.data = data
  14. this.prev = null // 指向前元素
  15. this.next = null // 指向后元素
  16. Node.prototype.toString = function () {
  17. return this.data.toString()
  18. }
  19. }
  20. // 添加节点
  21. DoublyLinkedList.prototype.append = function (data) {
  22. var newNode = new Node(data)
  23. // 1.判断链表是否为空
  24. if (this.__count === 0) {
  25. // 新元素既是首也是尾
  26. this.__head = newNode
  27. this.__tail = newNode
  28. }
  29. // 2.非空链表时,添加到尾部
  30. else {
  31. this.__tail.next = newNode
  32. newNode.prev = this.__tail
  33. this.__tail = newNode
  34. }
  35. // 3.计数加1
  36. this.__count += 1
  37. }
  38. // 插入节点
  39. DoublyLinkedList.prototype.insert = function (position, data) {
  40. // 1.边界检查(插入位置)
  41. if (position < 0 || position > this.__count) return false
  42. var newNode = new Node(data)
  43. // 2.插入元素时链表为空
  44. if (this.__count === 0) {
  45. this.__head = newNode
  46. this.__tail = newNode
  47. }
  48. // 3.链表非空
  49. else {
  50. // 3.1插入到链表头部
  51. if (position === 0) {
  52. newNode.next = this.__head
  53. this.__head.prev = newNode
  54. this.__head = newNode
  55. }
  56. // 3.2插入到链表尾部
  57. else if (position === this.__count) {
  58. this.__tail.next = newNode
  59. newNode.prev = this.__tail
  60. this.__tail = newNode
  61. }
  62. // 3.3以外
  63. else {
  64. var current = this.__head
  65. var index = 0
  66. while (index < position) {
  67. current = current.next
  68. index++
  69. }
  70. current.prev.next = newNode
  71. newNode.prev = current.prev
  72. newNode.next = current
  73. current.prev = newNode
  74. }
  75. }
  76. // 4.计数加1
  77. this.__count += 1
  78. return true
  79. }
  80. // 删除指定位置节点
  81. DoublyLinkedList.prototype.removeAt = function (position) {
  82. // 1.边界检查
  83. if (position < 0 || position >= this.__count) return null
  84. var current = this.__head
  85. // 2.只有一个元素
  86. if (this.size() === 1) {
  87. this.__head = null
  88. this.__tail = null
  89. }
  90. // 3.多个元素的情况
  91. else {
  92. // 3.1 删首
  93. if (position === 0) {
  94. current = this.__head
  95. this.__head = current.next
  96. current.next.prev = null
  97. }
  98. // 3.2 删尾
  99. else if (position === this.__count - 1) {
  100. current = this.__tail
  101. this.__tail = current.prev
  102. this.__tail.next = null
  103. }
  104. // 3.3 以外
  105. else {
  106. var index = 0
  107. var current = this.__head
  108. while (index < position) {
  109. current = current.next
  110. index += 1
  111. }
  112. current.prev.next = current.next
  113. current.next.prev = current.prev
  114. }
  115. }
  116. // 4.计数减1
  117. this.__count -= 1
  118. return current.data
  119. }
  120. // 删除节点
  121. DoublyLinkedList.prototype.remove = function (data) {
  122. // 根据指定数据取得下标值
  123. var index = this.indexOf(data)
  124. // 检查下标值是否正常取到
  125. if (index === -1) return null
  126. // 根据取到的下标,调用 removeAt 方法进行删除
  127. return this.removeAt(index)
  128. }
  129. // 更新节点数据
  130. DoublyLinkedList.prototype.update = function (position, data) {
  131. // 1.边界检查
  132. if (position < 0 || position >= this.__count) return false
  133. var current = this.__head
  134. var index = 0
  135. // 2.找到指定下标位置元素
  136. while (index < position) {
  137. current = current.next
  138. }
  139. // 3.修改数据
  140. current.data = data
  141. return true
  142. }
  143. // 获取节点数据
  144. DoublyLinkedList.prototype.getItem = function (position) {
  145. // 1.边界检查
  146. if (position < 0 || position >= this.__count) return
  147. var current = this.__head
  148. var index = 0
  149. // 2.找到指定下标位置元素
  150. // => TODO:改善:根据position所在位置选择从Head还是从Tail开始查找
  151. while (index++ < position) {
  152. current = current.next
  153. }
  154. return current.data
  155. }
  156. // 获取下标
  157. DoublyLinkedList.prototype.indexOf = function (data) {
  158. var current = this.__head
  159. var index = 0
  160. // 查找指定数据的节点
  161. while (current) {
  162. if (current.data == data) {
  163. return index
  164. }
  165. current = current.next
  166. index += 1
  167. }
  168. // 没有找到
  169. return -1
  170. }
  171. DoublyLinkedList.prototype.traverse = function (cb, reversal) {
  172. if (!reversal) return this.forwardTraverse(cb)
  173. return backwardTraverse(cb)
  174. }
  175. // Backward: Tail -> Head
  176. DoublyLinkedList.prototype.backwardTraverse = function (cb) {
  177. // TODO: cb 参数检查(回调函数)
  178. var current = this.__tail
  179. while (current) {
  180. cb(current.data)
  181. current = current.prev
  182. }
  183. }
  184. // Forward: Head -> Tail
  185. DoublyLinkedList.prototype.forwardTraverse = function (cb) {
  186. // TODO: cb 参数检查(回调函数)
  187. var current = this.__head
  188. while (current) {
  189. cb(current.data)
  190. current = current.next
  191. }
  192. }
  193. DoublyLinkedList.prototype.getHead = function () {
  194. if (this.__head == null) return null
  195. return this.__head.data
  196. }
  197. DoublyLinkedList.prototype.getTail = function () {
  198. if (this.__tail == null) return null
  199. return this.__tail.data
  200. }
  201. DoublyLinkedList.prototype.size = function () {
  202. return this.__count
  203. }
  204. DoublyLinkedList.prototype.isEmpty = function () {
  205. return this.__count === 0
  206. }
  207. DoublyLinkedList.prototype.clear = function () {
  208. this.__head = null
  209. this.__tail = null
  210. this.__count = 0
  211. }
  212. DoublyLinkedList.prototype.toString = function () {
  213. var str = '[HEAD]'
  214. var current = this.__head
  215. while (current) {
  216. str += ' -> ' + current.data
  217. current = current.next
  218. }
  219. str += str == '[HEAD]' ?
  220. ' -> Null <- [TAIL]' :
  221. ' <- [TAIL]'
  222. return str
  223. }
  224. }

4. 测试一下

  1. // ---------------------------------------------
  2. // Test: DoublyLinkedList
  3. // ---------------------------------------------
  4. console.log('----Test: DoublyLinkedList----')
  5. var dLst = new DoublyLinkedList()
  6. dLst.append('a')
  7. dLst.append('b')
  8. dLst.append('c')
  9. dLst.forwardTraverse(function (val) {
  10. console.log('forward-traversing: ', val)
  11. })
  12. dLst.backwardTraverse(function (val) {
  13. console.log('backward-traversing: ', val)
  14. })
  15. dLst.insert(0, 'Insert-Index=0')
  16. dLst.insert(3, 'Insert-Index=3')
  17. dLst.insert(5, 'Insert-Index=Count')
  18. console.log(dLst.toString())
  19. console.log('getItem(5) => ', dLst.getItem(5))
  20. console.log('remove("c") => ', dLst.remove('c'))
  21. console.log('removeAt(3) => ', dLst.removeAt(3))
  22. console.log('Result ↓ \r\n', dLst.toString())
  23. dLst.clear()
  24. console.log('After Clear : ', dLst.toString())

查看结果:

  1. ----Test: DoublyLinkedList----
  2. forward-traversing: c
  3. forward-traversing: b
  4. forward-traversing: a
  5. backward-traversing: a
  6. backward-traversing: b
  7. backward-traversing: c
  8. [HEAD] -> Insert-Index=0 -> a -> b -> Insert-Index=3 -> c -> Insert-Index=Count <- [TAIL]
  9. getItem(5) => Insert-Index=Count
  10. remove("c") => c
  11. removeAt(3) => Insert-Index=3
  12. [removeAt(3)]--Result
  13. [HEAD] -> Insert-Index=0 -> a -> b -> Insert-Index=Count <- [TAIL]
  14. After Clear : [HEAD] -> Null <- [TAIL]

确认无误,收工。


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

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

https://github.com/CoderMonkie/data-struct-js

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

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


-end-

【JavaScript数据结构系列】06-双向链表DoublyLinkedList的更多相关文章

  1. 【JavaScript数据结构系列】07-循环链表CircleLinkedList

    [JavaScript数据结构系列]07-循环链表CircleLinkedList 码路工人 CoderMonkey 转载请注明作者与出处 1. 认识循环链表 首节点与尾节点相连的,就构成循环链表.其 ...

  2. 【JavaScript数据结构系列】00-开篇

    [JavaScript数据结构系列]00-开篇 码路工人 CoderMonkey 转载请注明作者与出处 ## 0. 开篇[JavaScript数据结构与算法] 大的计划,写以下两部分: 1[JavaS ...

  3. JavaScript进阶系列06,事件委托

    在"JavaScript进阶系列05,事件的执行时机, 使用addEventListener为元素同时注册多个事件,事件参数"中已经有了一个跨浏览器的事件处理机制.现在需要使用这个 ...

  4. 【JavaScript数据结构系列】03-队列Queue

    [JavaScript数据结构系列]03-队列Queue 码路工人 CoderMonkey 转载请注明作者与出处 1. 认识队列Queue结构 队列,跟我们的日常生活非常贴近,我们前面举例了食堂排队打 ...

  5. 【JavaScript数据结构系列】05-链表LinkedList

    [JavaScript数据结构系列]05-链表LinkedList 码路工人 CoderMonkey 转载请注明作者与出处 ## 1. 认识链表结构(单向链表) 链表也是线性结构, 节点相连构成链表 ...

  6. 【JavaScript数据结构系列】04-优先队列PriorityQueue

    [JavaScript数据结构系列]04-优先队列PriorityQueue 码路工人 CoderMonkey 转载请注明作者与出处 ## 1. 认识优先级队列 经典的案例场景: 登机时经济舱的普通队 ...

  7. 【JavaScript数据结构系列】02-栈Stack

    [JavaScript数据结构系列]02-栈Stack 码路工人 CoderMonkey 转载请注明作者与出处 ## 1. 认识栈结构 栈是非常常用的一种数据结构,与数组同属线性数据结构,不同于数组的 ...

  8. 【JavaScript数据结构系列】01-数组Array

    [JavaScript数据结构系列]01-数组Array 码路工人 CoderMonkey 转载请注明作者与出处 # [JavaScript数据结构系列] # 01-数组Array 数组: 是有序的元 ...

  9. JavaScript进阶系列07,鼠标事件

    鼠标事件有Keydown, Keyup, Keypress,但Keypress与Keydown和Keyup不同,如果按ctrl, shift, caps lock......等修饰键,不会触发Keyp ...

随机推荐

  1. 动态规划经典算法--最长公共子序列 LCS

    转移方程 代码: //法一: #include <bits/stdc++.h> using namespace std; //---------------https://lunatic. ...

  2. P1516 青蛙的约会和P2421 [NOI2002]荒岛野人

    洛谷 P1516 青蛙的约会 . 算是手推了一次数论题,以前做的都是看题解,虽然这题很水而且还交了5次才过... 求解方程\(x+am\equiv y+an \pmod l\)中,\(a\)的最小整数 ...

  3. Jmeter 后置处理器

    1.JSON Extractor Json extractor 后置处理器用在返回格式为 Json 的 HTTP 请求中,用来获取返回的 Json 中的某个值.并保存成变量供后面的请求进行调用或断言等 ...

  4. Java中常用七个阻塞队列的总结

    Java队列总结 通过前面文章的学习,我们对Java中常用队列做了介绍.本文,咱们来对队列做个总结吧. 首先,我们介绍了现实生活中的实际场景(排队买票等),来告诉我们为什么需要使用队列. 队列是一种先 ...

  5. git新手使用教程包含各种系统

    Git Tutorial 1.下载客户端 从Git官网下载客户端:   https://git-scm.com/   Windows版下载地址:   https://git-scm.com/downl ...

  6. andorid jar/库源码解析之okhttp3

    目录:andorid jar/库源码解析 Okhttp3: 作用: 用于网络编程(http,https)的快速开发. 栗子: // okHttpClient定义成全局静态,或者单例,不然重复new可能 ...

  7. vue-infinite-scroll------vue的无线滚动插件

    vue-infinite-scroll------vue的无线滚动插件 博客说明 文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢! 说明 V ...

  8. (技能篇)Mysql在linux下的全量热备份

    相关命令: #创建备份目录 mkdir -p /mysqlbackup #进入创建的备份目录中 cd /mysqlbackup #如果mysql运行在mysql用户和用户组下面,root表示用户,my ...

  9. java web 开发之 office(excel、doc等)文件转pdf

    一.开发工具:office 16.jacob-1.18-M2.jboss 1.6 二.开发配置: 1.解压缩---> 2.配置jacob: A C:\Windows\System32 jacob ...

  10. 自己动手在Linux系统实现一个everything程序

    大家好,我是良许. 我们知道,在 Windows 下,有一款非常实用的神器,叫作 Everything ,它可以在极短的时间里,搜索出来你所想要的文件/目录,如下图示: Linux 下也有一些类似于 ...