【问题标题】:Using a tree to traverse a separate set of elements with the same parent/child relationship使用树遍历具有相同父/子关系的单独元素集
【发布时间】:2011-03-15 14:10:01
【问题描述】:

这是对相当神秘的标题问题的重述:

假设我们已经构建了一个 Prototype 树,其中包含有关树结构的所有信息以及每个节点的通用描述。现在我们想用包含额外唯一数据的元素创建这棵树的实例。我们称这些混凝土树。

ConcretePrototype 树之间的唯一区别是 Concrete 树的节点中的额外数据。假设 Concrete 树的每个节点都有一个指向 Prototype 树中相应元素的指针/链接,以获取有关该节点的通用信息,但没有自己的父/子信息:

是否可以遍历具体树?

特别是给定Concrete树中的一个起始节点,以及一条通过Prototype树的路径,是否可以高效地得到Concrete树中的对应节点?可以有很多个具体的树,所以从原型树返回的链接是不可能的。

尽管我可能不需要在我的代码中将事情优化到如此程度,但这仍然是一个有趣的问题!

提前致谢!

注意:树的分支因子没有限制——一个节点可以有一个到数百个子节点。


额外的胡言乱语/想法:

我问的原因是,每次创建具体树的新实例时复制父/子信息似乎是一种浪费,因为这种结构与原型树相同。在我的特殊情况下,子节点由字符串名称标识,因此我必须在每个节点上存储一个字符串到指针的哈希值。可以有 许多 个具体树的实例,复制此哈希似乎是对空间的巨大浪费。

作为第一个想法,也许路径可以以某种方式散列为 int 或紧凑标识元素的东西(不是字符串,因为它太大了),然后用于查找每个 Concrete 的散列中的具体元素树?

【问题讨论】:

  • 如果具体的树节点没有父/子信息,那么它就不是真正的树!
  • @Oli 当然,它没有自己的父/子信息,但它仍然通过原型树与其节点保持这种关系。所以在抽象意义上,它本身也是一棵树。

标签: algorithm data-structures


【解决方案1】:

一旦创建,原型树是否会改变(即节点是否会被插入或移除)?

如果没有,您可以考虑使用数组支持的树(即子/父链接由数组索引表示,而不是原始指针),并为您的具体树使用一致的索引。这样一来,从具体映射到原型就很简单了,反之亦然。

【讨论】:

  • +1 用于支持数组的树。真正的插入/删除很昂贵,但它确实为映射提供了 0 个额外的存储空间。
  • 是的,原型树一旦创建就不会改变!我正在研究的还有其他因素,例如混凝土树可能是原型树的子树,但这绝对应该让我开始!谢谢!如果有其他答案,我会再等一会儿再接受。
【解决方案2】:

您可以为每个原型节点创建一个具体的叶子,但是您需要对每棵树进行某种散列(如您的建议)以保持不同的具体树分开。此时,您已经产生了与具有冗余子/父指针的完全独立树相同的存储成本。你肯定想要一个从原型树到具体树的链接。

如果您想对原型树进行结构更改以影响所有链接的具体树,我可以看到这种方法很有用。洗牌节点会立即影响所有混凝土树。您可能会产生额外的费用,因为如果不发送每一棵混凝土树或执行一些extract 操作来撕掉一棵树,就不可能传输一棵混凝土树。

一般来说,您将无法在 int 中对路径进行唯一编码。

【讨论】:

    【解决方案3】:

    只需将父子关系存储在具体树中就可以了。最好的情况是一个指针值,最坏的情况是两个指针值。无论如何,您至少需要这么多才能保持原型树和具体树之间的链接。

    【讨论】:

      【解决方案4】:

      当节点的地址之间存在已知的依赖关系时,这是可能的 两棵树。基本上这意味着节点必须是固定大小并分配的 一次全部。 当然,也可以使用哈希表来映射第一棵树的地址 节点到第二个树节点,但这样的哈希表必须至少有 10 倍以上的节点 比第一棵树,否则映射会太慢。

      #include <stdio.h>
      
      typedef unsigned char byte;
      
      struct Node1 {
        Node1* child[2];
        Node1() { child[0]=child[1]=0; }
      };
      
      struct Node2 {
        int N;
        Node2() { N=0; }
      };
      
      int main( void ) {
      
        int i,j,k,N = 256;
      
        Node1* p = new Node1[2*N];
        Node2* q = new Node2[2*N];
      
        // insert
        for( i=0,k=1; i<N; i++ ) {
          Node1* root = &p[0];
          Node1** r = &root;
          for( j=7;; j-- ) {
            if( r[0]==0 ) r[0]=&p[k++];
            if( j<0 ) break;
            r = &r[0]->child[(i>>j)&1];
          }
          q[r[0]-p].N = byte(i+123);
          // ^^^^^ - mapping from p[] to q[]
        }
      
        // check
        for( i=N-1; i>=0; i-- ) {
          Node1* r = &p[0];
          for( j=7; j>=0; j-- ) r = r->child[(i>>j)&1];
          if( q[r-p].N != byte(i+123) ) printf( "error!\n" );
        }
      
      }
      

      【讨论】:

      • 感谢您的建议,但是,节点可以有任意数量的子节点和许多子类等。我认为 Oli 对数组支持树的建议更简单地封装了这个想法。
      【解决方案5】:

      我认为您可以按照您的描述做,但我不认为它构成优化(出于@Dave 提到的原因类型)。这样做的关键在于将指针绑定回原型,使其也充当标识符。此外,需要预先计算通过原型树的主要遍历——广度优先和深度优先遍历。

      预先计算的遍历可能会使用堆栈或队列,具体取决于特定的遍历。此外,随着遍历的完成,需要按遍历顺序构建索引链表(或者如@Oli 建议的索引数组)。链表中的数据是节点的标识符(见下文)。每个原型树和每个原型节点都需要一个标识符(可以是地址,也可以是任意标识符)。每棵具体的树都有自己的标识符。每个具体节点都被赋予与其在原型树中的对应节点相同的标识符。然后进行部分遍历,在链表中识别节点标识符并将其用作具体节点的标识符。

      本质上,您通过使用标识符的等价性作为指针(一种“幽灵”指针)来创建原型和具体节点之间的链接。它确实需要许多支持机制,这些可能会导致这条路线不是真正的优化。

      【讨论】:

      • 谢谢,我得看看节省空间是否值得所有的麻烦,以及它是否可以使整体速度加快。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-29
      • 1970-01-01
      • 2011-06-11
      • 1970-01-01
      • 1970-01-01
      • 2013-03-31
      • 1970-01-01
      相关资源
      最近更新 更多