Algorithm 如何计算树的哈希值

Algorithm 如何计算树的哈希值,algorithm,hash,tree,hashtable,graph-algorithm,Algorithm,Hash,Tree,Hashtable,Graph Algorithm,计算哈希值的最佳方法是什么 我需要比较O(1)中几棵树之间的相似性。现在,我想预先计算散列值,并在需要时比较它们。但后来我意识到,散列一棵树和散列一个序列是不同的。我没能想出一个好的散列函数 计算树的哈希值的最佳方法是什么 注意:我将在c/c++中实现该函数。子哈希值应依次乘以素数并相加。节点本身的散列应该乘以不同的素数&相加 缓存整个树的散列——如果我有一个保存AST的包装器对象,我更喜欢将其缓存在AST节点之外 public class RequirementsExpr { prot

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

我需要比较O(1)中几棵树之间的相似性。现在,我想预先计算散列值,并在需要时比较它们。但后来我意识到,散列一棵树和散列一个序列是不同的。我没能想出一个好的散列函数

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


注意:我将在c/c++

中实现该函数。子哈希值应依次乘以素数并相加。节点本身的散列应该乘以不同的素数&相加

缓存整个树的散列——如果我有一个保存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
时也应使用其他素数。这有助于避免
节点类型的不同值与
数据的不同值发生冲突

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


一旦计算(在整个AST中)散列就非常高效。

我建议将树转换为规范序列并对序列进行散列。(转换的详细信息取决于您对等价的定义。例如,如果树是二元搜索树,且等价关系是结构化的,则转换可以是按预序枚举树,因为二元搜索树的结构可以从预序枚举中恢复。)


乍一看,归结为将多变量多项式与每棵树相关联,并在特定位置计算多项式。有两个步骤,目前,必须采取的信念;第一个是映射不会将不等价的树发送到同一个多项式,第二个是评估方案不会引入太多冲突。我目前无法评估第一步,尽管有合理的等价定义,允许从两变量多项式重建。第二个在理论上并不合理,但可以通过Schwartz--Zippel来实现。

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

现在在任意顶点固定树的根。设root=1和

B=我们要转换的基

p[i]=B(B^i)的第i次方

标高[i]=第i个顶点的深度,其中(到根的距离)

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

度[i]=顶点i的相邻节点数

现在散列值中第i个顶点的贡献为-

散列[i]=((p[level[i]]+度[i])*子[i])%modVal

整个树的散列值是所有顶点散列值的总和-


(散列[1]+散列[2]+..+散列[n])%modVal

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

T1相当于T2 到T1叶的所有路径在T2中只存在一次,并且 到T2叶的所有路径在T2中只存在一次

散列序列(路径)很简单。如果
h_树(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;
    }
};
struct节点{
int path_hash;//根哈希的路径;仅用于构建树\u hash
int tree_hash;//考虑子级;用于比较树
智力内容;
媒介儿童;
int update\u hash(int parent\u path\u hash=1){
path\u hash=parent\u path\u hash*PRIME1+content;//顺序相关
树\u散列=路径\u散列;
用于(节点n:子节点){
tree\u hash+=n.update\u hash(path\u hash)*PRIME2;//顺序独立
}
返回树散列;
}
};

构建两棵树后,更新它们的哈希值并进行比较。等价的树应该有相同的散列,不同的树没有那么多。请注意,我使用的路径和树散列非常简单,选择它们是为了易于编程,而不是为了更好的抗冲突性…

这里您对等效树的定义是什么?类似的树不一定有类似的散列。如果你想检查树的相等性,比较散列是好的,但是大多数散列解不适合相似度的计算。如果对于任何节点R1(R1都属于T1)和R2(R2属于T2),两棵树T1和T2是等价的,如果我们考虑R1的根T1和R2的根T2,其余的树可以这样重新排列:T1和T2彼此同构。@BidhanRoy因此,换句话说,兄弟的顺序并不重要,但T1中所有通向叶子的路径在T2中只存在一次,同样,T1中通向T2叶子的路径也存在一次。