如何计算树的哈希值

时间:2013-08-24 11:20:14

标签: algorithm hash tree hashtable graph-algorithm

计算Tree哈希值的最佳方法是什么?

我需要比较 O(1)中几棵树之间的相似性。现在,我想预先计算哈希值并在需要时进行比较。但后来我意识到,散列树不同于散列序列。我无法想出一个好的哈希函数。

计算树的哈希值的最佳方法是什么?

注意:我将在c / c ++中实现该功能

4 个答案:

答案 0 :(得分:1)

拥有一棵树意味着以一种独特的方式表示它,这样我们就可以使用简单的表示法或数字将其他树与该树区分开。在普通多项式哈希中,我们使用数字基数转换,我们将字符串或序列转换为特定的素数基数,并使用也是大质数的mod值。现在,使用相同的技术,我们可以对树进行哈希处理。

现在将树的根固定在任何顶点。令root = 1,并且

B =我们要转换的基础。

P [i] = B的i次方(B ^ i)。

level [i] =第i个顶点的深度(与根的距离)。

child [i] =第i个顶点(包括i)的子树中的顶点总数。

degree [i] =顶点i的相邻节点数。

现在第i个顶点在哈希值中的贡献是-

哈希[i] =((P [level [i]] +学位[i])* child [i])%modVal

整个树的哈希值是所有顶点哈希值的总和-

(hash [1] + hash [2] + .... + hash [n])%modVal

答案 1 :(得分:1)

如果我们使用树对等的定义:

  

T1等效于T2 iff   T1的所有叶子路径在T2中仅存在一次,并且   T2的所有叶子路径在T2中只存在一次

散列序列(路径)非常简单。如果h_tree(T)是T的所有路径的散列,其中路径的顺序不会改变结果,那么对于整个T来说,这是一个很好的散列,在某种意义上,等效树将根据上面的等价定义产生相等的哈希值。所以我建议:

h_path(path) = an order-dependent hash of all elements in the path. 
            Requires O(|path|) time to calculate, 
            but child nodes can reuse the calculation of their 
            parent node's h_path in their own calculations.     
h_tree(T) = an order-independent hashing of all its paths-to-leaves. 
            Can be calculated in O(|L|), where L is the number of leaves

在伪C ++中:

struct node {
    int path_hash;  // path-to-root hash; only use for building tree_hash
    int tree_hash;  // takes children into account; use to compare trees
    int content;
    vector<node> children;
    int update_hash(int parent_path_hash = 1) {
       path_hash = parent_path_hash * PRIME1 + content;     // order-dependent
       tree_hash = path_hash;
       for (node n : children) {
            tree_hash += n.update_hash(path_hash) * PRIME2; // order-independent
       }
       return tree_hash;
    }
};

构建两棵树后,更新其哈希并进行比较。等效树应具有相同的哈希,而不同的树则应没有太多。请注意,我使用的路径和树形哈希值非常简单,选择它们的目的是为了易于编程,而不是为了提高抗碰撞性。

答案 2 :(得分:0)

儿童哈希应该连续乘以一个素数&amp;添加。节点本身的哈希值应乘以不同的素数&amp;加入。

整体缓存树的哈希 - 如果我有一个包含AST的包装器对象,我更喜欢将它缓存在AST节点之外。

public class RequirementsExpr {
    protected RequirementsAST ast;
    protected int hash = -1;

    public int hashCode() {
        if (hash == -1)
            this.hash = ast.hashCode();
        return hash;
    }
}

public class RequirementsAST {
    protected int    nodeType;
    protected Object data;
    // -
    protected RequirementsAST down;
    protected RequirementsAST across;

    public int hashCode() {
        int nodeHash = nodeType;
        nodeHash = (nodeHash * 17) + (data != null ? data.hashCode() : 0);
        nodeHash *= 23;            // prime A.

        int childrenHash = 0;
        for (RequirementsAST child = down; child != null; child = child.getAcross()) {
            childrenHash *= 41;    // prime B.
            childrenHash += child.hashCode();
        }
        int result = nodeHash + childrenHash;
        return result;
    }
}

结果是,不同位置的子/后代节点总是乘以不同的因子;并且节点本身总是乘以与任何可能的子/后代节点不同的因子。

请注意,其他素数也应该用于构建节点数据的nodeHash本身。这有助于避免例如。 nodeType的不同值与data的不同值相冲突。

在32位散列的限制范围内,此方案总体上为树结构中的任何差异(例如,转置两个兄弟)或值提供了非常高的唯一性。

一旦计算出(在整个AST中),哈希就非常有效。

答案 3 :(得分:0)

我建议将树转换为规范序列并对序列进行散列。 (转换的细节取决于你的等价定义。例如,如果树是二叉搜索树而等价关系是结构的,那么转换可能是按顺序枚举树,因为二叉搜索树的结构可以从预先列举的枚举中恢复。)

Thomas's answer乍看之下将多变量多项式与每棵树相关联并评估特定位置的多项式。目前,有两个步骤必须依据信仰;第一个是地图不会将不等价的树发送到同一个多项式,第二个是评估方案不会引入太多的冲突。我无法评估目前的第一步,尽管有等价的合理定义允许从双变量多项式进行重构。第二个在理论上并不合理,但可以通过Schwartz - Zippel来实现。