我目前正在看一个关于单链表的教程,导师对单链表的实现让我对推送方法非常困惑,我不知道发生了什么。
在这里:
class Node {
constructor(val) {
this.val = val;
this.next = null;
}
}
class SinglyLinkedList {
constructor() {
this.length = 0;
this.head = null;
this.tail = null;
}
push(val) {
let node = new Node(val);
if (!this.head) {
this.head = node;
this.tail = this.head;
} else {
this.tail.next = node;
this.tail = node;
}
this.length++;
return this;
}
let list = new SinglyLinkedList();
list.push('hello');
list.push('goodbye');
}
在这段代码中,我很难理解this.tail.next是如何向this.head插入新节点的。对于第一次插入,很明显,头和尾都指向同一个对象,即第一个节点。但在第一次推送后,this.tail被分配了一个新对象,与this.head不同,但this.tail.next仍然将新节点添加到this.head的最后一个下一个属性中这是怎么发生的?
它可能有助于可视化push
方法是如何完成的。
当用let list = new SinglyLinkedList()
构造(空(链表时,我们会遇到这种情况(我省略了length属性,因为它与问题无关(:
list (this)
↓
┌────────────┐
│ head: null │
│ tail: null │
└────────────┘
然后主代码调用list.push('hello')
。这从创建一个节点实例开始,本地变量node
引用它:
list node
↓ ↓
┌────────────┐ ┌──────────────┐
│ head: null │ │ val: "hello" │
│ tail: null │ │ next: null │
└────────────┘ └──────────────┘
由于this.head
是null
,所以执行if
块。此状态下的第一个this.head = node
结果:
list node
↓ ↓
┌────────────┐ ┌──────────────┐
│ head: ────────> │ val: "hello" │
│ tail: null │ │ next: null │
└────────────┘ └──────────────┘
然后将该引用复制到this.tail
:中
list node
↓ ↓
┌────────────┐ ┌──────────────┐
│ head: ────────> │ val: "hello" │
│ tail: ────────> │ next: null │
└────────────┘ └──────────────┘
在更新长度之后,push('hello')
调用完成了它的工作,并且node
变量被释放。然后执行CCD_ 12。我们再次从创建一个节点开始:
list node
↓ ↓
┌────────────┐ ┌──────────────┐ ┌────────────────┐
│ head: ────────> │ val: "hello" │ │ val: "goodbye" │
│ tail: ────────> │ next: null │ │ next: null │
└────────────┘ └──────────────┘ └────────────────┘
现在this.head
不是null
,所以我们进入else
块。执行this.tail.next = node
,因此我们得到(只需按照从this
到tail
到next
的箭头,其中null
被替换为对node
的引用(:
list node
↓ ↓
┌────────────┐ ┌──────────────┐ ┌────────────────┐
│ head: ────────> │ val: "hello" │ │ val: "goodbye" │
│ tail: ────────> │ next: ──────────> │ next: null │
└────────────┘ └──────────────┘ └────────────────┘
然后执行CCD_ 22。这将用另一个引用替换现有引用,恢复list.tail
总是引用最后一个节点的不变量:
list node
↓ ↓
┌────────────┐ ┌──────────────┐ ┌────────────────┐
│ head: ────────> │ val: "hello" │ │ val: "goodbye" │
│ tail: ───────┐ │ next: ──────────> │ next: null │
└────────────┘ │ └──────────────┘┌─> └────────────────┘
└──────────────────┘
同样,node
变量的生命周期在执行结束时结束,因此我们只剩下list
和上面描述的依赖结构。
但在第一次推送后,
this.tail
被分配了一个新对象,与this.head
不同
这是不对的。第一次插入后,this.head
和this.tail
指向同一对象:
if (!this.head) {
this.head = node;
this.tail = this.head;
}
对于任何后续插入,this.tail
都指向先前插入的节点。在将新节点分配给this.tail
:之前,更新this.tail.next
this.tail.next = node;
this.tail = node;
这意味着当第二次插入发生时,this.tail
和this.head
仍然指向同一对象。执行CCD_ 35,从而也更新CCD_。只有在之后的,才会更新this.tail
以指向新节点。