我知道这是我的第二个答案,但我认为我在这里提供的解释可能比上一个答案更好。但请注意,即使那个答案也是正确的。
高效内存链表通常被称为 XOR 链表,因为它完全依赖于 XOR 逻辑门及其属性。
它与双向链表有什么不同吗?
是的,是的。它实际上做的工作几乎与双向链表相同,但有所不同。
双向链表存储了两个指针,分别指向下一个和上一个节点。基本上,如果你想回去,你就去back指针指向的地址。如果你想往前走,你就去next指针指向的地址。就像:
内存效率高的链表,即XOR 链表只有一个指针而不是两个。这将存储上一个地址 (addr (prev)) XOR (^) 下一个地址 (addr (next))。当你想移动到下一个节点时,你做一些计算,找到下一个节点的地址。去上一个节点也是一样,就是这样:
它是如何工作的?
XOR 链表,正如你可以从它的名字中看出的那样,高度依赖于逻辑门 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 是第三个和第一个节点的地址。所有节点,除了头部和尾部,都和上面的一样。
列表的tail,没有下一个节点,所以存储addr (prev) ^ 0。它看起来像:
在看我们如何移动之前,让我们再看一下 XOR Linked List 的表示:
当你看到
这显然意味着有一个链接字段,您可以使用它前后移动。
另外,需要注意的是,当使用异或链表时,你需要有一个临时变量(不在节点中),它存储你之前所在的节点的地址。当您移动到下一个节点时,您会丢弃旧值,并存储您之前所在的节点的地址。
从 Head 移动到下一个节点
假设你现在在第一个节点,或者在节点 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。
这几乎与从一个节点移动到下一个节点相同。只是这是它的反之亦然。当您编写程序时,您将使用我在从一个节点移动到下一个节点时提到的相同步骤,只是您在列表中查找较早的元素而不是查找下一个元素。
我想我不需要解释这个。
异或链表的优点
异或链表的缺点
-
这实现起来有点棘手。失败的几率比较大,调试难度很大。
-
所有转换(在 int 的情况下)都必须发生在 uintptr_t/从uintptr_t
-
你不能只获取一个节点的地址,然后从那里开始遍历(或其他)。您必须始终从头或尾开始。
-
你不能去跳跃,也不能跳过节点。你必须一个一个去。
-
移动需要更多操作。
-
很难调试使用 XOR 链接列表的程序。调试双向链表要容易得多。
参考文献