什么是链表
单链表是示意一系列节点的数据结构,个中每一个节点指向链表中的下一个节点。 相反,双向链表具有指向其前后元素的节点。
与数组差别,链表不供应对链表表中特定索引接见。 因而,假如须要链表表中的第三个元素,则必需遍历第一个和第二个节点才到获得它。
链表的一个优点是能够在牢固的时间内从链表的开首和末端增添和删除项。
这些都是在手艺口试中常常被问到的数据结构,所以让我们最先吧。
别的,能够对链表举行排序。 这意味着当每一个节点增添到链表中时,它将被安排在相关于其他节点的恰当位置。
节点
链表只是一系列节点,所以让我们从 Node 对象最先。
一个节点有两条信息
- 指向链表中下一项的指针或援用(关于单链表)
- 节点的值
关于我们的节点,我们只须要建立一个函数,该函数接收一个值,并返回一个具有上面两个信息的对象:指向下一个节点的指针和该节点的值。
注重,我们能够只声明
value
而不是
value: value
。这是由于变量称号雷同(ES6 语法)
节点链表
如今,让我们深入研究 NodeList 类,以下就是节点链表模样。
节点链表将包括五个要领:
- push(value): 将值增添到链表的末端
- pop() :弹出链表中的末了一个值
- get(index):返回给定索引中的项
- delete(index):从给定索引中删除项
- isEmpty(): 返回一个布尔值,指导链表是不是为空
printList():不是链表的原生要领,它将打印出我们的链表,重要用于调试
组织函数
组织函数中须要三个信息:
- head:对链表开首节点的援用
- tail:对链表末端节点的援用
- length:链表中有若干节点
class LinkedList {
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
}
IsEmpty
isEmpty()
要领是一个协助函数,假如链表为空,则返回true
。
isEmpty() {
return this.length === 0;
}
printList
这个实用程序要领用于打印链表中的节点,仅用于调试目标。
printList () {
const nodes = [];
let current = this.head;
while (current) {
nodes.push(current.value);
current = current.next;
}
return nodes.join(' -> ');
}
Push
在增添新节点之前,push
要领须要搜检链表是不是为空。怎样晓得链表是不是为空? 两种体式格局:
-
isEmpty()
要领返回true
(链表的长度为零) -
head
指针为空
关于这个例子,我们运用 head
是不是为null
来推断链表是不是为空。
假如链表中没有项,我们能够简朴地将head
指针和tail
指针都设置为新节点并更新链表的长度。
if (this.head === null) {
this.head = node;
this.tail = node;
this.length++;
return node;
}
假如链表不是空的,我们必需实行以下操纵:
- 将
tail.next
指向新节点 - 将
tail
指向新节点 - 更新链表长度
以下是完全的 push
要领:
push(value) {
const node = Node(value);
// The list is empty
if (this.head === null) {
this.head = node;
this.tail = node;
this.length++;
return node;
}
this.tail.next = node;
this.tail = node;
this.length++;
}
Pop
在删除链表中的末了一项之前,我们的pop
要领须要搜检以下两项内容:
- 搜检链表是不是为空
- 搜检链表中是不是只要一项
能够运用isEmpty
要领搜检链表是不是包括节点。
if (this.isEmpty()) {
return null;
}
我们怎样晓得链表中只要一个节点? 假如 head
和tail
指向同一个节点。然则在这类状况下我们须要做什么呢? 删除唯一的节点意味着我们实际上要从新设置链表。
if (this.head === this.tail) {
this.head = null;
this.tail = null;
this.length--;
return nodeToRemove;
}
假如链表中有多个元素,我们能够实行以下操纵
当链表中有节点时,
假如链表中的下一个节点是 tail
更新 tail 指向当前节点
当前节点设置为 null,
更新链表的长度
返回前一个 tail 元素
它看起来像如许:
1 let currentNode = this.head;
2 let secondToLastNode;
3
4 //从前面最先并迭代直到找到倒数第二个节点
5
6 while (currentNode) {
7 if (currentNode.next === this.tail) {
8 // 将第二个节点的指针挪动到末了一个节点
9 secondToLastNode = currentNode;
10 break;
11 }
12 currentNode = currentNode.next;
13 }
14 // 弹出该节点
15 secondToLastNode.next = null;
16 // 将 tail 挪动到倒数第二个节点
17 this.tail = secondToLastNode;
18 this.length--;
19
20 // 初始化 this.tail
21 return nodeToRemove;
假如你没法设想这一点,那末让我们来看看它。
第6-10行:假如链表中的下一个节点是末了一个项,那末这个当前项目就是新tail
,因而我们须要保留它的援用。
if (currentNode.next === this.tail) {
secondToLastNode = currentNode;
}
第15行:将secondToLastNode
更新为null
,这是从链表中“弹出”末了一个元素的行动。
secondToLastNode.next = null;
第17行:更新tail
以指向secondToLastNode
。
this.tail = secondToLastNode;
第18行:更新链表的长度,由于我们刚删除了一个节点。
第21行:返回方才弹出的节点。
以下是完全的pop
要领:
pop() {
if (this.isEmpty()) {
return null;
}
const nodeToRemove = this.tail;
// There's only one node!
if (this.head === this.tail) {
this.head = null;
this.tail = null;
this.length--;
return nodeToRemove;
}
let currentNode = this.head;
let secondToLastNode;
// Start at the front and iterate until
// we find the second to last node
while (currentNode) {
if (currentNode.next === this.tail) {
// Move the pointer for the second to last node
secondToLastNode = currentNode;
break;
}
currentNode = currentNode.next;
}
// Pop off that node
secondToLastNode.next = null;
// Move the tail to the second to last node
this.tail = secondToLastNode;
this.length--;
// Initialized to this.tail
return nodeToRemove;
}
Get
get
要领必需搜检三种状况:
- 索引是不是超出了链表的局限
- 链表是不是为空
- 查询第一个元素
假如链表中不存在要求的索引,则返回null
。
// Index is outside the bounds of the list
if (index < 0 || index > this.length) {
return null;
}
假如链表为空,则返回null
。你能够把这些if
语句组合起来,然则为了坚持清楚,我把它们分开了。
if (this.isEmpty()) {
return null;
}
假如我们要求第一个元素,返回 head
。
// We're at the head!
if (index === 0 ) {
return this.head;
}
不然,我们只是一个一个地遍历链表,直到找到要查找的索引。
let current = this.head;
let iterator = 0;
while (iterator < index) {
iterator++;
current = current.next;
}
return current;
以下是完全的get(index)
要领:
get(index) {
// Index is outside the bounds of the list
if (index < 0 || index > this.length) {
return null;
}
if (this.isEmpty()) {
return null;
}
// We're at the head!
if (index === 0 ) {
return this.head;
}
let current = this.head;
let iterator = 0;
while (iterator < index) {
iterator++;
current = current.next;
}
return current;
}
Delete
delete
要领须要考虑到三个处所
- 删除的索引超出了链表的局限
- 链表是不是为空
- 我们想要删除
head
假如链表中不存在我们要删除的索引,则返回 null
。
// Index is outside the bounds of the list
if (index < 0 || index > this.length) {
return null;
}
假如我们想删除head
,将head
设置为链表中的下一个值,减小长度,并返回我们方才删除的值。
if (index === 0) {
const nodeToDelete = this.head;
this.head = this.head.next;
this.length--;
return nodeToDelete;
}
假如以上都 不是,则删除节点的逻辑以下:
轮回遍历正在查找的索引
增添索引值
将前一个和当前指针向上挪动一个
将当前值保留为要删除的节点
更新上一个节点的指针以指向下一个节点
假以下一个值为 `null`
将`tail`设置为新的末了一个节点
更新链表长度
返回已删除的节点
假如你须要可视化图片,请参考Pop
部份中的图表。
以下是完全的 delete
要领:
delete(index) {
// Index is outside the bounds of the list
if (index < 0 || index > this.length - 1) {
return null;
}
if (this.isEmpty()) {
return null;
}
if (index === 0) {
const nodeToDelete = this.head;
this.head = this.head.next;
this.length--;
return nodeToDelete;
}
let current = this.head;
let previous;
let iterator = 0;
while (iterator < index) {
iterator++;
previous = current;
current = current.next;
}
const nodeToDelete = current;
// Re-direct pointer to skip the element we're deleting
previous.next = current.next;
// We're at the end
if(previous.next === null) {
this.tail = previous;
}
this.length--;
return nodeToDelete;
}
你的点赞是我延续分享好东西的动力,迎接点赞!