做者:Roberto Hernandez
译者:前端小智
来源:dev
点赞再看,养成习惯本文
GitHub
https://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了不少个人文档,和教程资料。欢迎Star和完善,你们面试能够参照考点复习,但愿咱们一块儿有点东西。javascript
单链表是表示一系列节点的数据结构,其中每一个节点指向链表中的下一个节点。 相反,双向链表具备指向其先后元素的节点。前端
与数组不一样,链表不提供对链表表中特定索引访问。 所以,若是须要链表表中的第三个元素,则必须遍历第一个和第二个节点才能到获得它。java
链表的一个好处是可以在固定的时间内从链表的开头和结尾添加和删除项。node
这些都是在技术面试中常常被问到的数据结构,因此让咱们开始吧。git
另外,能够对链表进行排序。 这意味着当每一个节点添加到链表中时,它将被放置在相对于其余节点的适当位置。github
链表只是一系列节点,因此让咱们从 Node 对象开始。面试
一个节点有两条信息segmentfault
对于咱们的节点,咱们只须要建立一个函数,该函数接受一个值,并返回一个具备上面两个信息的对象:指向下一个节点的指针和该节点的值。数组
注意,咱们能够只声明value
而不是value: value
。这是由于变量名称相同(ES6 语法)
如今,让咱们深刻研究 NodeList 类,如下就是节点链表样子。数据结构
节点链表将包含五个方法:
printList():不是链表的原生方法,它将打印出咱们的链表,主要用于调试
构造函数中须要三个信息:
class LinkedList { constructor() { this.head = null; this.tail = null; this.length = 0; } }
isEmpty()
方法是一个帮助函数,若是链表为空,则返回true
。
isEmpty() { return this.length === 0; }
这个实用程序方法用于打印链表中的节点,仅用于调试目的。
printList () { const nodes = []; let current = this.head; while (current) { nodes.push(current.value); current = current.next; } return nodes.join(' -> '); }
在添加新节点以前,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
方法须要检查如下两项内容:
可使用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
方法必须检查三种状况:
若是链表中不存在请求的索引,则返回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
方法须要考虑到三个地方
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; }
代码部署后可能存在的BUG无法实时知道,过后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具 Fundebug。
原文:
https://dev.to/macmacky/70-ja...
干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。
https://github.com/qq44924588...
我是小智,公众号「大迁世界」做者,对前端技术保持学习爱好者。我会常常分享本身所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,便可看到福利,你懂的。