从二叉树中删除重复的子树

时间:2012-02-29 22:20:47

标签: algorithm tree compression binary-tree

我必须在额外的作业下设计算法。该算法必须通过删除重复子树并将所有这些连接重定向到一个左原始子树来将二进制树转换为DAG来压缩二进制树。例如,我有一棵树(我给节点预订):

1 2 1 3 2 1 3

算法必须删除1(根)的右连接(右子树,意思是2 1 3)并将其重定向到左连接(因为这些子节是相同的,左边是先预订,所以我们只留下左边)

我看到它的方式:我正在通过树预订。对于当前节点'w',我开始递归,必须检测(如果存在)原始子树等于具有根'w'的子树。如果我找到相同的子树(并且我做了必须做的事情),或者当我在找到相同的子树递归时得到“w”时,我正在削减递归。当然,我预测一些小的改进,比如只比较具有相同节点数的子树。

如果我没错,它会给出复杂度O(n ^ 2),其中n是给定二叉树的节点数。有没有机会更快地完成它(我认为是)。线性算法可能吗?


可惜我的算法最终有复杂度O(n ^ 3)。一段时间后,你对哈希的回答对我来说可能会非常有用,届时我会知道更多......现在对我来说太难了..

最后一个问题。是否有机会使用基本技术(非散列)在O(n ^ 2)中进行?

3 个答案:

答案 0 :(得分:4)

构建oBDD时会发生这种情况。想法是:将树放入规范形式,并构造一个哈希表,每个节点都有一个条目。散列函数是节点的函数+左/右子节点的散列函数。复杂性是O(N),但前提是可以依赖哈希值是唯一的。对于递归子树< - >,最终比较(例如,对于解决冲突)仍将花费o(N * N)。子树比较。 More on BDDsthe original Bryant paper

我目前使用的哈希函数:

#define SHUFFLE(x,n) (((x) << (n))|((x) >>(32-(n))))
/* a node's hashvalue is based on its value
 * and (recursively) on it's children's hashvalues.
 */
#define NODE_HASH2(l,r) ((SHUFFLE((l),5)^SHUFFLE((r),9)))
#define NODE_HASH3(v,l,r) ((0x54321u*(v) ^ NODE_HASH2((l),(r))))

典型用法:

void node_sethash(NodeNum num)
{
if (NODE_IS_NULL(num)) return;

if (NODE_IS_TERMINAL(num)) switch (nodes[num].var) {
        case 0: nodes[num].hash.hash= HASH_FALSE; break;
        case 1: nodes[num].hash.hash= HASH_TRUE; break;
        case 2: nodes[num].hash.hash= HASH_FALSE^HASH_TRUE; break;
        }
else if (NODE_IS_NAMED(num)) {
        NodeNum f,t;
        f = nodes[num].negative;
        t = nodes[num].positive;
        nodes[num].hash.hash = NODE_HASH3 (nodes[num].var, nodes[f].hash.hash, nodes[t].hash.hash);
        }
return ;
}

搜索哈希表:

NodeNum *hash_hnd(NodeNum num, int want_exact)
{
unsigned slot;
NodeNum *ptr, this;
if (NODE_IS_NULL(num)) return NULL;

slot = nodes[num].hash.hash % COUNTOF(hash_nodes);

for (ptr = &hash_nodes[slot]; !NODE_IS_NULL(this= *ptr); ptr = &nodes[this].hash.link) {
        if (this == num) break;
        if (want_exact) continue;
        if (nodes[this].hash.hash != nodes[num].hash.hash) continue;
        if (nodes[this].var != nodes[num].var) continue;
        if (node_compare( nodes[this].negative , nodes[num].negative)) continue;
        if (node_compare( nodes[this].positive , nodes[num].positive)) continue;
                /* duplicate node := same var+same children */
        break;
        }
return ptr;
}

递归比较函数:

int node_compare(NodeNum one, NodeNum two)
{
int rc;

if (one == two) return 0;

if (NODE_IS_NULL(one) && NODE_IS_NULL(two)) return 0;
if (NODE_IS_NULL(one) && !NODE_IS_NULL(two)) return -1;
if (!NODE_IS_NULL(one) && NODE_IS_NULL(two)) return 1;

if (NODE_IS_TERMINAL(one) && !NODE_IS_TERMINAL(two)) return -1;
if (!NODE_IS_TERMINAL(one) && NODE_IS_TERMINAL(two)) return 1;

if (VAR_RANK(nodes[one].var)  < VAR_RANK(nodes[two].var) ) return -1;
if (VAR_RANK(nodes[one].var)  > VAR_RANK(nodes[two].var) ) return 1;


rc = node_compare(nodes[one].negative,nodes[two].negative);
if (rc) return rc;
rc = node_compare(nodes[one].positive,nodes[two].positive);
if (rc) return rc;

return 0;
}

答案 1 :(得分:2)

这是在编程语言中进行常见的子表达式消除时常见的问题。

方法如下(并且很容易推广到节点中的2个以上子节点):

算法(假设可变树结构;您可以沿途轻松构建新树):

MakeDAG(tree):

    HASH = a new hash-table-based dictionary

    foreach subtree NODE in the tree // traverse this however you like

        if NODE is in HASH
            replace NODE with HASH[NODE]
        else
            HASH[NODE] = N // insert the current node, N, in the dictionary

要计算节点的哈希码,您需要递归计算哈希节点,直到到达树的叶子。

简单地计算这些哈希码会使你的运行时增加到O(n ^ 2)。

将结果存储在树下以避免重复的递归调用并将运行时改进为O(n)至关重要。

答案 2 :(得分:1)

我会采用哈希方法。

叶子的哈希是其值mod P_1。节点的哈希值为(value+hash(left_son)*P_2+hash(right_son)*P_2^2) mod P_1,其中P_1,P_2为素数。如果你计算至少5个不同的大素数对的哈希值(通过大我意味着接近10 ^ 8-10 ^ 9,所以你可以在不溢出的情况下进行数学运算),你可以放心地假设具有相同哈希值的节点是相同的

然后你可以走树,检查儿子,先做变换。这将在O(n)时间内有效。

注意您可以使用其他哈希函数,例如(value + hash(left_son)*P_2 + hash(right_son)*P_3) mod P_1等。