计算Tree哈希值的最佳方法是什么?
我需要比较 O(1)中几棵树之间的相似性。现在,我想预先计算哈希值并在需要时进行比较。但后来我意识到,散列树不同于散列序列。我无法想出一个好的哈希函数。
计算树的哈希值的最佳方法是什么?
注意:我将在c / c ++中实现该功能
答案 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来实现。