Vla*_*eev 6 algorithm hash ropes data-structures
给定一个绳子,假设我们需要知道它的哈希值(通过一些哈希函数传递所有叶子的连接)。
现在,当一个绳叶发生变化时,再次重新计算整条绳子的哈希值的有效方法是什么?即类似 O(log n) 而不是 O(n) 的东西。
一种方法是使用Merkle 树。但是,这会导致诸如...
有没有更好的算法呢?散列函数不需要加密安全,只要足够好以避免可能的冲突。
正如绳索的任何节点存储左子树(如果它是叶子,则存储其自身)的大小一样,任何节点都可以另外存储与左子树(或如果它是叶子,则其自身)对应的字符串的多项式哈希。
当重新计算节点的权重时,也会重新计算该节点的哈希值,具有相同的渐近复杂度。
例如,假设节点及其中的值是:
left right string weight
1: abcd 4
2: 1 4 4
3: ef 2
4: 3 5 2
5: ghi 3
Run Code Online (Sandbox Code Playgroud)
具有一些固定常数 p 和 q 的多项式哈希为:
h (s[0] s[1] ... s[n-1]) = (s[0] * p^(n-1) + s[1] * p^(n-2) + .. .+ s[n-1] * p^0) mod q。
因此,我们存储了以下哈希值,全部以 q 为模:
hash
1: a*p^3 + b*p^2 + c*p^1 + d*p^0
2: a*p^3 + b*p^2 + c*p^1 + d*p^0
3: e*p^1 + f*p^0
4: e*p^1 + f*p^0
5: g*p^2 + h*p^1 + i*p^0
Run Code Online (Sandbox Code Playgroud)
关于计算模 q 的注释。此处和下文中,所有加法和乘法均以 q 为模进行。换句话说,我们在以 q 为模的整数环中进行运算。我们利用这样一个事实
(a ? b) mod q = ((a mod q) ? (b mod q)) mod q
为了 ?运算有加法、减法和乘法。因此,每次我们执行其中一项操作时,我们都会立即附加 amod q以保持数字较小。例如,如果 p 和 q 小于 2 30 = 1,073,741,824,则可以在 32 位整数类型中进行加法和减法,并且可以在中间 64 位整数类型中进行乘法。每次相乘后,我们立即对结果取模 q,使其再次适合 32 位整数。
现在,我们如何获取根的哈希值 - 例如,使其成为某个节点的左子节点,或者只是获取整个字符串的哈希值?
我们从根向右,我们必须添加权重并合并哈希值。事实证明我们可以这样做(记住一切都是以 q 为模):
({ a*p^3 + b*p^2 + c*p^1 + d*p^0} * p^2 + { e*p^1 + f*p^0}) * p^3 + { g*p^2 + h*p^1 +i *p^0}
大括号中的值是存储在 out 节点中的值。我们向右递归。起床时,我们记住到目前为止收集到的权重,将左侧散列乘以 p 的权重次方(这就是 p^3 和 p^(3+2=5) 的来源),然后添加累积的值右侧哈希。
结果值等于整个字符串的哈希值:
a*p^8 + b*p^7 + c*p^6 + d*p^5 + e*p^4 + f*p^3 + g*p^2 + h*p^1 + i*p^0
这里有一些注意事项。
我们必须预先计算(也许是懒惰的)p 模 q 的幂,以便能够快速乘以它们。
如果我们将整个子树的哈希值(而不仅仅是左子树的哈希值)存储在节点中,整个结构可能会变得更加清晰。然而,这样,我们可能会失去绳索结构所具有的 O(1) 连接可能性,使其降至通常的 O(log n),因此我们可能只使用常规的陷阱而不是绳索。即使没有,将整个子树的哈希值缓存在一个节点中也绝对是可能的。
如果我们反转哈希多项式中的幂顺序,使其成为
h (s[0] s[1] ... s[n-1]) = (s[0] * p^0 + s[1] * p^1 + ... + s[n-1] * p^(n-1)) mod q,
数学类似,但是可以迭代而不是递归地从节点的所有右后代收集哈希。