JavaScript 数据结构与算法(六)单向链表
认识链表
链表和数组
链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同。
数组
存储多个元素,数组(或列表)可能是最常用的数据结构。
几乎每一种编程语言都有默认实现数组结构,提供了一个便利的 []
语法来访问数组元素。
数组缺点:
数组的创建需要申请一段连续的内存空间(一整块内存),并且大小是固定的,当前数组不能满足容量需求时,需要扩容。 (一般情况下是申请一个更大的数组,比如 2 倍,然后将原数组中的元素复制过去)
在数组开头或中间位置插入数据的成本很高,需要进行大量元素的位移。
链表
存储多个元素,另外一个选择就是使用链表。
不同于数组,链表中的元素在内存中不必是连续的空间。
链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有些语言称为指针)组成。
链表优点:
内存空间不必是连续的,可以充分利用计算机的内存,实现灵活的内存动态管理。
链表不必在创建时就确定大小,并且大小可以无限延伸下去。
链表在插入和删除数据时,时间复杂度可以达到 O(1),相对数组效率高很多。
链表缺点:
访问任何一个位置的元素时,需要从头开始访问。(无法跳过第一个元素访问任何一个元素)
无法通过下标值直接访问元素,需要从头开始一个个访问,直到找到对应的元素。
虽然可以轻松地到达下一个节点,但是回到前一个节点是很难的。
单向链表
单向链表类似于火车,有一个火车头,火车头会连接一个节点,节点上有乘客,并且这个节点会连接下一个节点,以此类推。
链表中的常见操作
append(element)
向链表尾部添加一个新的项。
insert(position, element)
向链表的特定位置插入一个新的项。
get(position)
获取对应位置的元素。
indexOf(element)
返回元素在链表中的索引。如果链表中没有该元素就返回-1。
update(position, element)
修改某个位置的元素。
removeAt(position)
从链表的特定位置移除一项。
remove(element)
从链表中移除一项。
isEmpty()
如果链表中不包含任何元素,返回 trun,如果链表长度大于 0 则返回 false。
size()
返回链表包含的元素个数,与数组的 length 属性类似。
toString()
由于链表项使用了 Node 类,就需要重写继承自 JavaScript 对象默认的 toString 方法,让其只输出元素的值。
单向链表的封装
创建单向链表类
先创建单向链表类 LinkedList,添加基本属性,再逐步实现单向链表的常用方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class LinkedList { length = 0;
head = null;
Node = class { data; next = null; constructor(data) { this.data = data; } }; }
|
实现 append() 方法
代码实现
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
| append(data) {
const newNode = new this.Node(data);
if (this.length === 0) {
this.head = newNode;
} else { let currentNode = this.head;
while (currentNode.next !== null) { currentNode = currentNode.next; }
currentNode.next = newNode; }
this.length++;
}
|
过程图解
代码测试
1 2 3 4 5 6
| const linkedList = new LinkedList();
linkedList.append("A"); linkedList.append("B"); linkedList.append("C"); console.log(linkedList);
|
实现 toString() 方法
代码实现
1 2 3 4 5 6 7 8 9 10 11 12
| toString() { let currentNode = this.head; let result = '';
while (currentNode) { result += currentNode.data + ' '; currentNode = currentNode.next; }
return result; }
|
代码测试
1 2
| console.log(linkedList.toString());
|
实现 insert() 方法
代码实现
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
| insert(position, data) {
if (position < 0 || position > this.length) return false;
const newNode = new this.Node(data);
if (position === 0) { newNode.next = this.head;
this.head = newNode; } else {
let currentNode = this.head; let previousNode = null; let index = 0;
while (index++ < position) { previousNode = currentNode; currentNode = currentNode.next; }
newNode.next = currentNode; previousNode.next = newNode; }
this.length++; return newNode; }
|
代码测试
1 2 3 4
| linkedList.insert(0, "123"); linkedList.insert(2, "456"); console.log(linkedList.toString());
|
实现 getData() 方法
获取指定位置(position)的 data。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| getData(position) { if (position < 0 || position >= this.length) return null;
let currentNode = this.head; let index = 0;
while (index++ < position) { currentNode = currentNode.next; } return currentNode.data; }
|
代码测试
1 2 3
| console.log(linkedList.getData(0)); console.log(linkedList.getData(1));
|
实现 indexOf() 方法
indexOf(data) 返回指定 data 的 index,如果没有,返回 -1。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| indexOf(data) {
let currentNode = this.head; let index = 0;
while (currentNode) { if (currentNode.data === data) { return index; } currentNode = currentNode.next; index++; }
return -1; }
|
代码测试
1 2 3
| console.log(linkedList.indexOf("AA")); console.log(linkedList.indexOf("ABC"));
|
实现 update() 方法
update(position, data) 修改指定位置节点的 data。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| update(position, data) { if (position < 0 || position >= this.length) return false;
let currentNode = this.head; let index = 0; while (index++ < position) { currentNode = currentNode.next; }
currentNode.data = data;
return currentNode; }
|
代码测试
1 2 3 4 5
| linkedList.update(0, "12345"); console.log(linkedList.toString()); linkedList.update(1, "54321"); console.log(linkedList.toString());
|
实现 removeAt() 方法
removeAt(position) 删除指定位置的节点。
代码实现
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
| removeAt(position) { if (position < 0 || position >= this.length) return null;
let currentNode = this.head; if (position === 0) { this.head = this.head.next;
} else {
let previousNode = null; let index = 0;
while (index++ < position) { previousNode = currentNode; currentNode = currentNode.next; }
previousNode.next = currentNode.next; }
this.length--;
return currentNode; }
|
代码测试
1 2 3
| linkedList.removeAt(3); console.log(linkedList.toString());
|
实现 remove() 方法
remove(data) 删除指定 data 所在的节点。
代码实现
1 2 3
| remove(data) { this.removeAt(this.indexOf(data)); }
|
代码测试
1 2 3
| linkedList.remove("CC"); console.log(linkedList.toString());
|
实现 isEmpty() 方法
isEmpty() 判断链表是否为空。
代码实现
1 2 3
| isEmpty() { return this.length === 0; }
|
代码测试
1 2
| console.log(linkedList.isEmpty());
|
实现 size() 方法
size() 获取链表的长度。
代码实现
1 2 3
| size() { return this.length; }
|
代码测试
1 2
| console.log(linkedList.size());
|
完整实现
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
| class LinkedList { length = 0;
head = null;
Node = class { data; next = null;
constructor(data) { this.data = data; } };
append(data) { const newNode = new this.Node(data);
if (this.length === 0) { this.head = newNode; } else { let currentNode = this.head;
while (currentNode.next !== null) { currentNode = currentNode.next; }
currentNode.next = newNode; }
this.length++; }
insert(position, data) {
if (position < 0 || position > this.length) return false;
const newNode = new this.Node(data);
if (position === 0) { newNode.next = this.head;
this.head = newNode; } else {
let currentNode = this.head; let previousNode = null; let index = 0;
while (index++ < position) { previousNode = currentNode; currentNode = currentNode.next; }
newNode.next = currentNode; previousNode.next = newNode; }
this.length++; return newNode; }
getData(position) { if (position < 0 || position >= this.length) return null;
let currentNode = this.head; let index = 0;
while (index++ < position) { currentNode = currentNode.next; }
return currentNode.data; }
indexOf(data) { let currentNode = this.head; let index = 0;
while (currentNode) { if (currentNode.data === data) { return index; } currentNode = currentNode.next; index++; }
return -1; }
update(position, data) { if (position < 0 || position >= this.length) return false;
let currentNode = this.head; let index = 0; while (index++ < position) { currentNode = currentNode.next; }
currentNode.data = data;
return currentNode; }
removeAt(position) { if (position < 0 || position >= this.length) return null;
let currentNode = this.head; if (position === 0) { this.head = this.head.next; } else {
let previousNode = null; let index = 0;
while (index++ < position) { previousNode = currentNode; currentNode = currentNode.next; }
previousNode.next = currentNode.next; }
this.length--;
return currentNode; }
remove(data) { this.removeAt(this.indexOf(data)); }
isEmpty() { return this.length === 0; }
size() { return this.length; }
toString() { let currentNode = this.head; let result = "";
while (currentNode) { result += currentNode.data + " "; currentNode = currentNode.next; }
return result; } }
|