使用树遍历具有相同父/子关系的单独元素集

时间:2011-03-15 14:10:01

标签: algorithm data-structures

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

假设我们已经构建了一个 Prototype 树,它包含树结构的所有信息以及每个节点的通用描述。现在,我们要使用包含额外唯一数据的元素创建此树的实例。我们称之为 Concrete 树。

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

是否可以遍历Concrete树?

特别是,给定Concrete树中的起始节点和通过Prototype树的路径,是否可以有效地获取Concrete树中的相应节点?可以有许多 Concrete树,因此无法从Prototype树返回链接。

即使我可能不需要在我的代码中对事物进行优化,但这仍然是一个有趣的问题!

提前致谢!

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


额外的ramblings / ideas:

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

作为第一个想法,也许路径可能以某种方式融入到int或者紧凑地标识元素的东西(不是字符串,因为它太大了),然后用于查找每个混凝土的哈希中的具体元素树?

5 个答案:

答案 0 :(得分:5)

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

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

答案 1 :(得分:0)

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

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

通常,您无法在int中唯一地编码路径。

答案 2 :(得分:0)

只需将父子关系存储在具体树中并忘记它。充其量只是一个指针值,最差的是两个指针值。无论如何,您至少需要保留原型树和具体树之间的链接。

答案 3 :(得分:0)

当可能存在已知的节点地址之间的依赖关系时,它是可能的 两棵树。基本上,这意味着节点必须是固定大小并分配 一次全部。 当然,它也可以使用哈希表来映射第一棵树的地址 节点到第二个树节点,但是这样的哈希表必须至少多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" );
  }

}

答案 4 :(得分:0)

我认为你可以做你所描述的,但我认为它不构成优化(因为@Dave引用的原因类型)。这样做的关键在于将指针绑定回原型,使它们也充当标识符。此外,需要预先计算通过原型树的主要遍历 - 先行宽度和深度先行遍历。

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

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