【问题标题】:How to implement a double linked list with only one pointer?如何实现只有一个指针的双链表?
【发布时间】:2009-11-18 10:29:01
【问题描述】:

如何实现只有一个指针的双链表?

找到上一个和下一个节点需要 O(1) 时间。

struct Node
{
   int val;
   Node* p;
};

【问题讨论】:

  • 将列表限制为最多两个节点:P
  • 双向链表?你的意思是应该能够通过一个指向 any 节点的外部指针来双向遍历它?
  • 这个有趣的问题也出现在“Programming Perls”一书的第 9 节(列)中。虽然经常提出 XOR 基本解决方案(可能是因为在那个时代,指针算术非常普遍),但我发现下面由 'Anna' 给出的解决方案非常有趣,尤其是当指针算术不可行时。

标签: algorithm data-structures


【解决方案1】:

这听起来好像不可能,就像它所说的那样。一般来说,你不能只使用一个指针来实现两个指针。

您也许可以将两个 16 位偏移量压缩到单个(假定为 32 位)指针使用的空间中,或者其他一些“聪明的技巧”,但总的来说这听起来是不可能的。

This article 描述了一个基于 XOR 的技巧:指针值,但我认为这是一种 hack(它对指针值进行按位运算)。

【讨论】:

  • “异或”技巧几乎是我所知道的唯一方法。如果您正在处理的系统内存有限,以至于每个节点保存一个指针是值得的,它就有一些合法用途。不过,让调试列表代码变得有趣。
  • 鉴于这是一个面试问题,而不是一个实际问题,我大约 99% 确定这是一个常识性问题。面试官想知道被面试者是否知道 XOR 技巧。
【解决方案2】:

有一个经典的技巧:存储 2 个指针(Prev 和 Next)的 XOR,并且在遍历列表时,您手头总是有 1 个指针(您刚从那里来),您可以将其与存储的值进行 XOR获取另一个指针。

不用说,这在 GC 环境中是行不通的。

【讨论】:

    【解决方案3】:

    也许通过使用XOR linked list

    【讨论】:

      【解决方案4】:

      已经提出的一种解决方案是XOR solution

      另一种解决方案是“翻转双方”解决方案: 如果您的问题表述如下:

      给你一个指向第一个元素的指针,你想:

      1. 在链表中前进 i 步 O(i)
      2. 在 O(i) 中返回链表 i 步
      3. 在 O(1) 的当前位置添加或删除项目

      所以总是只有一个指向链表的指针,并且只有一个入口点(只是前后移动,就像在 1 和 2 中一样),您可以执行以下操作:

      • 保存两个指针:p1p2
      • 从第一个指针 p1 可以后退,从第二个指针 p2 可以前进。
      • p1之前的链表项指向后,p2之后的项指向前。

      所以你的列表看起来像这样:

                        p1 p2
                        |  |
                        V  V
      i1 <- i2 <- i3 <- i4 i5 -> i6 -> i7
      

      p1 指向当前元素,p2 指向下一个元素,i1 ... i7 是列表中的项目

      前进是在 O(1) 中完成的,后退也是如此,通过翻转指针:

      Forward one step:
                              p1 p2
                              |  |
                              V  V
      i1 <- i2 <- i3 <- i4 <- i5 i6 -> i7
      
      
      Backward one step: 
                  p1 p2
                  |  |
                  V  V
      i1 <- i2 <- i3 i4 -> i5 -> i6 -> i7
      

      该解决方案在可读性方面优于 XOR 解决方案,并且更易于人类理解。缺点是你的链表不能有多个入口点。

      【讨论】:

        【解决方案5】:

        如果sizeof(int) == sizeof(Node *) 则有一个包含反向指针的中间节点。

        例如

        (real node) -&gt; (intermediate node) -&gt; (read node) -&gt; (etc)

        其中(real node) 包含一个值和一个指向下一个(intermediate node) 的指针,(intermediate node) 在 val 中包含一个指向前一个中间节点的后向指针,在 pa 中包含指向下一个 (read node) 的前向指针。

        顺便说一句,这是一个愚蠢的问题。我看不出它教了什么有价值的东西。

        【讨论】:

          【解决方案6】:

          我最近在 Niklaus Wirth 的一本书(“算法 + 数据结构 = 程序”)中偶然发现了一个很好的方法来解决这个问题。它是这样的......你有一个节点,类似于你建议的那个,除了它没有聚合指向下一个(也不是前一个)Node 的指针。相反,您有一个成员link,它表示从链中的前一个节点(Node* pPrev 指向)到下一个节点(Node* pNext 指向)的距离(例如,以sizeof(Node) 为单位)在链中:

          size_t link = pNext - pPrev;
          

          所以节点可能看起来像这样:

          struct Node {
              int val;
              size_t link;
          }
          

          然后,从当前节点pCurrent,结合前一个节点pPrev,到下一个Node* pNext,写作:

          pNext = pPrev + pCurrent->link;
          

          同样,你可以通过重新排列这个等式来向相反的方向遍历:

          pPrev = pNext - pCurrent->link;
          

          但是,这种方法在某种程度上受到 C/C++ 指针算法的限制,因为只有当两个指针都指向同一个内存块内部时,它们的差异才能得到很好的定义。所以本质上,你的所有节点都必须包含在一个巨大的Nodes 数组中。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-04-28
            • 2013-10-29
            • 2012-01-01
            • 1970-01-01
            • 2015-07-16
            • 2015-09-09
            • 1970-01-01
            • 2012-04-20
            相关资源
            最近更新 更多