数据结构 - 双向链表(JavaScript实现)
源码地址
双向链表 (DoublyLinkList)
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
初始化
1 2 3 4 5 6 7 8 9 10 11 12 13
| class DoublyLinkList { constructor(...arg){ this.head = null this.rear = null } node(data){ return { data, next: null, prev: null } } }
|
基本操作
插入
头部插入
- 如果头节点为空,直接插入,并指定 head rear
- 取出头节点 temp = head 初始化新节点 node
- node.next 指向 temp
- temp.prev 指向 node
- head 指向 node
1 2 3
| 0->1->2 插入 -1 ,取出 0 -1->0->1->2
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| prepend(data){ let node = this.node(data) if(!this.head){ this.head = node this.rear = node return node.data } let temp = this.head node.next = temp temp.prev = node this.head = node return node.data }
|
尾部插入
- 如果头节点为空,直接插入,并指定 head rear
- 取出 temp = rear 初始化节点 node
- temp.next 指向 node
- node.prev 指向 temp
- rear 指向 node
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| append(data){ let node = this.node(data) if(!this.rear){ this.head = node this.rear = node return node.data } let temp = this.rear temp.next = node node.prev = temp this.rear = node return node.data }
|
指定位置插入
- 查找出插入位置 curr,初始化 node,temp = curr.next
- curr.next 指向 node
- node.prev 指向 curr
- node.next 指向 curr.next
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| add(el, data){ let curr = this.contains(el) if(curr == this.rear) return this.append(data) if(!curr) throw null let node = this.node(data) let temp = curr.next curr.next = node node.prev = curr node.next = temp return node.data }
|
删除
头部删除
- 取出头节点 temp = head ,如果头节点不存在,直接返回null
- 如果 temp.next 不存在,证明只有一个节点,把头节点和尾节点一同置为 null
- 把头节点指针指向 temp.next
- temp.next.prev 置为 null,temp.next 置为 null
1 2 3
| 1->2->3 // head 1 rear 3 取出 1, 2 2->3 // 1.next = null; 2.prev = null; head = 2
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| removeHead(){ if(!this.head) return null let temp = this.head if(!temp) return null if(!temp.next) { this.rear = null this.head = null return temp.data } temp.next.prev = null this.head = temp.next temp.next = null return temp.data }
|
尾部删除
- 取出 curr = rear.prev,rear
- rear.prev 置为 null
- curr.next 置为 null
- rear 指针指向 curr
1 2 3
| 1->2->3 取出 3 ,3.prev -> 2 1->2
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| removeRear(){ if(!this.rear) return null let curr = this.rear.prev let rear = this.rear rear.prev = null if(curr) curr.next = null this.rear = curr return rear.data }
|
指定位置删除
- 如果头节点不存在,返回null
- 如果值等于头节点,直接删除头节点
- 查找出值,如果值等于尾节点,直接删除尾节点
- 取出当前值的前置节点 prevNode, 后置节点 nextNode
- prevNode.next 指向 nextNode
- nextNode.prev 指向 prevNode
- curr.next, curr.prev 置为 null
1 2 3
| 1->2->3 找出 2 ,通过 2 取出 2.prev -> 1 2.next -> 3 1->3
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| remove(el){ if(!this.head) return null if(this.head.data === el) return this.removeHead() let curr = this.contains(el) if(!curr) return null if(curr == this.rear) return this.removeRear() let nextNode = curr.next let prevNode = curr.prev prevNode.next = nextNode nextNode.prev = prevNode return curr.data }
|
查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| contains(el){ if(!this.head) return null let curr = this.head while (curr){ if(curr.data === el) break curr = curr.next } return curr }
|
遍历
正序遍历
- 从头节点开始,通过后继指针,循环遍历,直到 null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| traverse(callback=(item)=>item){ let res = [] if(!this.head) return res let curr = this.head while (curr){ res.push(callback(curr.data)) curr = curr.next } return res }
|
反序遍历
- 由于每个非头节点都有前置节点,可以直接通过前置节点往前遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| reverseTraversal(callback=(item)=>item){ let res = [] if(!this.head) return res let curr = this.rear while (curr){ res.push(callback(curr.data)) curr = curr.prev } return res }
|
反转
- 相比链表的反转,需要注意 prev 的指向问题
- 在循环中需要指定,循环结束后给头节点的 prev 置 null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| reverse() { if(!this.head) return null let curr = this.head let next = null let prev = null while(curr){ next = curr.next curr.next = prev if(prev) prev.prev = curr prev = curr curr = next } this.rear = this.head this.head = prev this.head.prev = null return this }
|
取出中间节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| findMid(){ if(!this.head) return null let search = this.head let mid = this.head while(search.next){ if(search.next.next != null){ search = search.next.next mid = mid.next }else{ search = search.next } } return mid }
|