我知道这是我的第二个答案,但我认为我在这里提供的解释可能比上一个答案更好。但请注意,即使那个答案也是正确的。
内存高效链表更常称为XOR 链表,因为这完全取决于XOR逻辑门及其属性。
它与双向链表有什么不同?
是的,是的。它实际上做的工作几乎与双向链表相同,但有所不同。
双向链表存储两个指针,分别指向下一个节点和前一个节点。基本上,如果你想回去,你就去back
指针指向的地址。如果你想往前走,你就去next
指针指向的地址。就像是:
内存效率高的链表,即XOR 链表只有一个指针,而不是两个。这存储上一个地址 (addr (prev)) XOR (^) 下一个地址 (addr (next))。当你想移动到下一个节点时,你做一些计算,找到下一个节点的地址。转到上一个节点也是如此。就像:
它是如何工作的?
XOR Linked List ,从它的名字可以看出,高度依赖于逻辑门XOR (^) 和它的属性。
它的属性是:
|-------------|------------|------------|
| Name | Formula | Result |
|-------------|------------|------------|
| Commutative | A ^ B | B ^ A |
|-------------|------------|------------|
| Associative | A ^ (B ^ C)| (A ^ B) ^ C|
|-------------|------------|------------|
| None (1) | A ^ 0 | A |
|-------------|------------|------------|
| None (2) | A ^ A | 0 |
|-------------|------------|------------|
| None (3) | (A ^ B) ^ A| B |
|-------------|------------|------------|
现在让我们把它放在一边,看看每个节点存储了什么:
第一个节点或head存储0 ^ addr (next)
,因为没有先前的节点或地址。看起来像:
然后第二个节点存储addr (prev) ^ addr (next)
。看起来像:
上图显示了节点 B,即第二个节点。A 和 C 是第三个和第一个节点的地址。除了头部和尾部之外的所有节点都和上面的一样。
列表的尾部,没有任何下一个节点,所以它存储addr (prev) ^ 0
. 看起来像:
在看我们如何移动之前,让我们再次看一下 XOR Linked List 的表示:
当你看到
这显然意味着有一个链接字段,您可以使用它前后移动。
另外,要注意,当使用异或链表时,你需要有一个临时变量(不在节点中),它存储你之前所在节点的地址。当您移动到下一个节点时,您会丢弃旧值,并存储您之前所在节点的地址。
从头移动到下一个节点
假设您现在在第一个节点或节点 A。现在您想在节点 B 移动。这是这样做的公式:
Address of Next Node = Address of Previous Node ^ pointer in the current Node
所以这将是:
addr (next) = addr (prev) ^ (0 ^ addr (next))
由于这是头部,前一个地址将简单地为 0,所以:
addr (next) = 0 ^ (0 ^ addr (next))
我们可以去掉括号:
addr (next) = 0 ^ 0 addr (next)
使用该none (2)
属性,我们可以说它0 ^ 0
始终为 0:
addr (next) = 0 ^ addr (next)
使用该none (1)
属性,我们可以将其简化为:
addr (next) = addr (next)
你得到了下一个节点的地址!
从一个节点移动到下一个节点
现在假设我们在一个中间节点,它有一个前一个节点和下一个节点。
让我们应用公式:
Address of Next Node = Address of Previous Node ^ pointer in the current Node
现在替换值:
addr (next) = addr (prev) ^ (addr (prev) ^ addr (next))
删除括号:
addr (next) = addr (prev) ^ addr (prev) ^ addr (next)
使用该none (2)
属性,我们可以简化:
addr (next) = 0 ^ addr (next)
使用该none (1)
属性,我们可以简化:
addr (next) = addr (next)
你明白了!
从一个节点移动到您之前所在的节点
如果你不理解标题,这基本上意味着如果你在节点 X,现在已经移动到节点 Y,你想回到之前访问的节点,或者基本上是节点 X。
这不是一项繁琐的任务。请记住,我在上面提到过,您将所在的地址存储在一个临时变量中。因此,您要访问的节点的地址位于一个变量中:
addr (prev) = temp_addr
从一个节点移动到前一个节点
这和上面提到的不一样。我的意思是说,你在节点 Z,现在你在节点 Y,想去节点 X。
这与从一个节点移动到下一个节点几乎相同。只是这是它的反之亦然。当您编写程序时,您将使用我在从一个节点移动到下一个节点时提到的相同步骤,只是您在列表中查找较早的元素而不是查找下一个元素。
我不认为我需要解释这一点。
XOR 链表的优点
XOR链表的缺点
这实现起来有点棘手。它有更高的失败机会,并且调试它非常困难。
所有转换(在 int 的情况下)都必须发生到/从uintptr_t
您不能只获取节点的地址,然后从那里开始遍历(或其他)。您必须始终从头部或尾部开始。
你不能去跳跃或跳过节点。你必须一一去。
移动需要更多的操作。
很难调试使用 XOR 链接列表的程序。调试双向链表要容易得多。
参考