如果您能一步一步地向我解释,我将不胜感激
让我们从插入值 1、2 和 3 之后的状态开始:
head tail
↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ value: 1 │ │ value: 2 │ │ value: 3 │
│ next: ——————→ │ next: ——————→ │ next:null│
└──────────┘ └──────────┘ └──────────┘
当while 循环开始时,我们引入了更多的引用。请注意second 是如何通过first.next 到达那里的,即它引用与第一个对象中的next 引用相同的对象。另外,this.tail 被设置为它的最终值,所以我们不必再考虑这个了:
first
head
tail second
↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ value: 1 │ │ value: 2 │ │ value: 3 │
│ next: ——————→ │ next: ——————→ │ next:null│
└──────────┘ └──────────┘ └──────────┘
现在让我们想象一下while循环中每个赋值的效果:
第一次迭代:
const temp = second.next; 初始化一个新引用,它等于second 引用的对象中的next 引用:
first
head
tail second temp
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ value: 1 │ │ value: 2 │ │ value: 3 │
│ next: ——————→ │ next: ——————→ │ next:null│
└──────────┘ └──────────┘ └──────────┘
second.next = first; 会将“第二个”对象的next 引用重定向到其前面的 节点:
first
head
tail second temp
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ value: 1 │ │ value: 2 │ │ value: 3 │
│ next: ——————→ │ │ │ next:null│
│ │ ←——— next │ │ │
└──────────┘ └──────────┘ └──────────┘
注意这如何将第三个对象从列表中分离出来。幸运的是,我们仍然可以通过temp 引用它,否则我们将永远失去它。当然,这正是引入 temp 变量的原因。
接下来的两个语句将为下一轮循环准备:first 和 second 引用更新为引用下一个节点。这样循环将能够从头到尾遍历列表。
first = second; 导致此状态:
head first
tail second temp
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ value: 1 │ │ value: 2 │ │ value: 3 │
│ next: ——————→ │ │ │ next:null│
│ │ ←——— next │ │ │
└──────────┘ └──────────┘ └──────────┘
在second = temp; 之后我们有:
head second
tail first temp
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ value: 1 │ │ value: 2 │ │ value: 3 │
│ next: ——————→ │ │ │ next:null│
│ │ ←——— next │ │ │
└──────────┘ └──────────┘ └──────────┘
第二次迭代:
const temp = second.next; 初始化一个新的引用(旧的temp 只存在于上一次迭代中)。但是现在second 指的是列表中的最后一个节点,它的next 属性是null,所以temp 是null:
head
tail first second temp: null
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ value: 1 │ │ value: 2 │ │ value: 3 │
│ next: ——————→ │ │ │ next:null│
│ │ ←——— next │ │ │
└──────────┘ └──────────┘ └──────────┘
second.next = first; 会将“第二个”对象(现在是第三个对象)的 next 引用重定向到它的 preceding 节点,因为这就是 first 所引用的:
head
tail first second temp: null
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ value: 1 │ │ value: 2 │ │ value: 3 │
│ next: ——————→ │ │ │ │
│ │ ←——— next │ ←——— next │
└──────────┘ └──────────┘ └──────────┘
接下来的两个语句将为可能的下一轮循环准备循环:first 和 second 引用再次更新以引用下一个节点:
first = second; 导致此状态:
head first
tail second temp: null
↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ value: 1 │ │ value: 2 │ │ value: 3 │
│ next: ——————→ │ │ │ │
│ │ ←——— next │ ←——— next │
└──────────┘ └──────────┘ └──────────┘
在second = temp; 之后我们有:
head second: null
tail first temp: null
↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ value: 1 │ │ value: 2 │ │ value: 3 │
│ next: ——————→ │ │ │ │
│ │ ←——— next │ ←——— next │
└──────────┘ └──────────┘ └──────────┘
现在循环条件为假,所以我们退出循环。此时我们确定所有的next 引用都被反转了,除了第一个节点中的那个应该变成null。而且我们还需要调整head,使其指向最后一个节点。这正是循环之后剩下的两个赋值执行的,所以我们最终得到:
first second: null
tail head (temp is out of scope)
↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ value: 1 │ │ value: 2 │ │ value: 3 │
│ next:null│ ←——— next │ ←——— next │
└──────────┘ └──────────┘ └──────────┘
现在列表反转了,head 和 tail 都引用了它们应该引用的节点。
算法
最后,该算法维护了三个沿列表移动的引用:first、second 和 temp。只要它们不变成null,它们总是引用列表中的3 个连续节点,并且在每次迭代中它们引用列表下方的一个节点。这三人紧密合作,从“左到右”行走。
second 引用的每个节点都将更新其next 引用,因此它引用其前一个节点。 temp 确保它仍然可以沿着列表的其余部分移动,即使它已被 next 的此更新分离。