3 链表
++链表通常由一连串节点组成,每个节点包含任意的实例数据和1个或者2个用来指向上一个或者下一个节点位置的链接++
一个node节点通常长这个样子
node |
data数据 |
next指针 |
previous指针 |
3.1 单向链表
单向链表是链表结构中最简单的,一个单链表的节点(node)分为两部分,第一部分(data)保存和显示节点的相关信息,另一个部分(next)存储下一个节点的地址。最后一个节点存储地址部分指向空值(null)。
在链表中头节点(header)比较特殊,头节点data部分不存储值,next指向第一个存储值的节点。
1 2
| graph LR header-->nodeA; nodeA --> nodeB; nodeB--> nodeC
|
3.2 单向链表的方法和属性
链表的方法和属性 |
含义 |
element(属性) |
链表的元素(可以是任意类型) |
next(属性) |
链表节点指向下一元素的指针 |
find(方法) |
查找链表中对应的节点 |
findPrevious(方法) |
获取需要查找节点的上一个节点 |
insert(方法) |
在链表中插入某个节点 |
remove(方法) |
在链表中删除某个节点 |
display(方法) |
显示整个链表中的数据部分 |
3.2.1 插入节点
在链表中插入新节点就需要明确指出需要在哪个节点前面或者后面插入。我们以在某个节点后面插入一个节点为例。
在某个节点(节点A)后插入节点,需要把节点A的next指针指向新节点,同时把新节点的next指针指向节点A的后一个节点
插入前
1 2
| graph LR header-->nodeA;nodeA --> nodeB; nodeB-->nodeC
|
插入后
1 2
| graph LR header-->nodeA;nodeA-->新节点;新节点-->nodeB;nodeB --> nodeC
|
3.2.2 删除节点
在链表中删除节点时,需要找到待删除节点的前面节点。找到这个节点后,修改它的next指针,使其不再指向待删除的节点,而是指向待删除节点的下一个节点。比如删除新节点.
删除前
1 2
| graph LR header-->nodeA;nodeA-->新节点;新节点-->nodeB;nodeB --> nodeC
|
删除后
1 2
| graph LR header-->nodeA;nodeA --> nodeB; nodeB-->nodeC
|
3.3 实现单向链表
书中的代码比较老旧,也有很多地方有错误。我稍加更正后代码如下
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| class Node { constructor (ele) { this.element = ele this.next = null } } class singleLinkedList { constructor () { this.head = new Node() } find (ele) { let curNode = this.head if (ele) { while (curNode.element != ele && curNode.next) { curNode = curNode.next } } else { while (curNode.next) { curNode = curNode.next } } return curNode } insert (newEle, ele) { let newNode = new Node(newEle) let current = this.find(ele) newNode.next = current.next current.next = newNode } findPrevious (ele) { let curNode = this.head if (ele) { while (curNode.next && curNode.next.element != ele) { curNode = curNode.next } } else { while (curNode.next) { curNode = curNode.next } }
return curNode } remove (ele) { let preNode = this.findPrevious(ele) if (preNode.next) { preNode.next = preNode.next.next } else { preNode.next = null } } display () { let curNode = this.head let str = '' while (curNode.next) { str += curNode.next.element curNode = curNode.next } console.log(str) } }
|
测试
1 2 3 4 5 6 7 8 9 10 11
| let sList = new singleLinkedList() sList.insert('a') sList.insert('b') sList.insert('c') sList.display() sList.insert('d', 'a') sList.display() sList.remove('b') sList.display()
|
3.4 双向链表
单向链表从头节点遍历到尾节点比较简单,但是从后向前遍历就比较麻烦。我们可以给node节点增加一个属性,该属性存储指向前驱节点的链接。这样就容易很多.
双向链表示意图
1 2 3 4 5 6 7 8 9
| graph LR header --> |prev|null; header-->|next|nodeA; nodeA-->|prev|header; nodeA-->|next|nodeB; nodeB-->|prev|nodeA; nodeB-->|next|nodeC; nodeC-->|prev|nodeB; nodeC-->|next|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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| class Node { constructor (ele) { this.element = ele this.next = null this.prev = null } } class twoWayLinkedList { constructor () { this.head = new Node() } find (ele) { let curNode = this.head if (ele) { while (curNode.element != ele && curNode.next) { curNode = curNode.next } } else { while (curNode.next) { curNode = curNode.next } } return curNode } insert (newEle, ele) { let newNode = new Node(newEle) let current = this.find(ele) newNode.next = current.next if (current.next) { current.next.prev = newNode } newNode.prev = current current.next = newNode }
remove (ele) { let curNode = this.find(ele) if (curNode != this.head) { if (curNode.next) { curNode.next.prev = curNode.prev } curNode.prev.next = curNode.next curNode.prev = null curNode.next = null } } display () { let curNode = this.head let str = '' while (curNode.next) { str += curNode.next.element curNode = curNode.next } console.log(str) } }
|
对双向链表进行测试
1 2 3 4 5 6 7 8 9
| let sList = new twoWayLinkedList() sList.insert('a') sList.insert('b') sList.insert('c') sList.display() sList.insert('d', 'a') sList.display() sList.remove('b') sList.display()
|
3.5 循环链表
循环链表和单向链表相似,节点类型都是一样的。唯一的区别是创建循环链表的时候头部节点head的next指向的是自身。
循环链表示意图
1 2 3 4 5
| graph LR header-->|next|nodeA; nodeA -->|next|nodeB; nodeB -->|next|nodeC nodeC -->|next|header
|
循环链表的程序实现
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| class Node { constructor (ele) { this.element = ele this.next = null } } class loopLinkedList { constructor () { this.head = new Node() this.head.next = this.head } find (ele) { let headNode = this.head let curNode = this.head if (ele) { while (curNode.element != ele && curNode.next != headNode) { curNode = curNode.next } } else { while (curNode.next != headNode) { curNode = curNode.next } } return curNode } findPrevious (ele) { let headNode = this.head let curNode = this.head if (ele) { while (curNode.next.element != ele && curNode.next != headNode) { curNode = curNode.next } } else { while (curNode.next != headNode) { curNode = curNode.next } }
return curNode } insert (newEle, ele) { let newNode = new Node(newEle) let current = this.find(ele) newNode.next = current.next current.next = newNode }
remove (ele) { let prevNode = this.findPrevious(ele)
if (prevNode.next != this.head) { prevNode.next = prevNode.next.next } } display () { let headNode = this.head let curNode = this.head let str = '' while (curNode.next != headNode) { str += curNode.next.element curNode = curNode.next } console.log(str) } }
|
测试
1 2 3 4 5 6 7 8 9
| let sList = new loopLinkedList() sList.insert('a') sList.insert('b') sList.insert('c') sList.display() sList.insert('d', 'c') sList.display() sList.remove('d') sList.display()
|
自此链表所有的知识点都讲解完成了。
个人总结:这一章知识点都讲道理,就是作者的代码写的比较乱,而且漏洞很多。明显作者没有经过测试。不过也有一些地方,我刚开始以为作者错了,可是等到自己写代码实现的时候才发现是对的