给定rope,假设我们需要知道它的哈希值(通过一些哈希函数传递所有叶子的串联)。
现在,当一根绳子叶子发生变化时,再次重新计算整根绳子的有效方法是什么?即像O(log n)而不是O(n)。
一种方法是使用Merkle tree。但是,这会导致诸如......
之类的问题有更好的算法吗?哈希函数不需要加密安全,只是足以避免可能的冲突。
答案 0 :(得分:3)
正如绳索的任何节点存储左子树的大小(或者如果它是叶子一样),任何节点都可以另外存储对应于左子树的字符串的多项式哈希(或者如果它是叶)。
当重新计算节点的权重时,也会为该节点重新计算哈希值,并具有相同的渐近复杂度。
例如,让节点及其中的值为:
left right string weight
1: abcd 4
2: 1 4 4
3: ef 2
4: 3 5 2
5: ghi 3
多项式散列是一些固定常数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
关于计算模q的说明。 在这里和下面,所有的加法和乘法都是以模q进行的。 换句话说,我们以ring of integers模q运算。 我们使用的事实是
(a?b)mod q =((a mod q)?(b mod q))mod q
为?操作是加法,减法和乘法。
因此,每次我们执行其中一个操作时,我们会立即附加mod 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 + {{ 1}} * p ^ 0}
大括号中的值是存储在out节点中的值。 我们向右前进。 起床时,我们记住到目前为止收集的重量,将左侧散列乘以p乘以该重量的幂(其中p ^ 3和p ^(3 + 2 = 5)来自),并添加累积的右侧哈希。
结果值只等于整个字符串的哈希值:
i
* p ^ 8 + a
* p ^ 7 + b
* p ^ 6 + c
* p ^ 5 + d
* p ^ 4 + e
* p ^ 3 + f
* p ^ 2 + g
* p ^ 1 + h
* p ^ 0
这里有几点说明。
我们必须预先计算,或许是懒惰,p modulo q的幂能够快速乘以它们。
如果我们在节点中存储整个子树的哈希值,而不仅仅是左子树的哈希值,整个结构可能会变得更加清晰。但是,通过这种方式,我们可能会失去绳索结构所具有的O(1)连接可能性,使其降低到通常的O(log n),因此我们可能只使用了常规的treap一根绳子即使不是,也可以在节点中缓存整个子树的哈希值。
如果我们反转哈希多项式中的幂次序,则将其设为
h(s [0] s [1] ... s [n-1])=(s [0] * p ^ 0 + s [1] * p ^ 1 + ... + s [n-1] * p ^(n-1))mod q,
数学是相似的,但是从节点的所有正确后代收集哈希可以迭代而不是递归地完成。