String Hashing 如何工作?

字符串哈希是一种将字符串转换为哈希数字的技术。我们为什么需要这样做?有时我们需要比较超长字符串,此时我们可以比较生成的哈希,而不是比较字符串,从而提升效率。

假设你需要比较下面的两个字符串:

const A = "imagine that this is a string of 200 characters"
const B = "imagine that this is a string of 200 characterz"

假设两个字符串都有 200 个字符。一个暴力的方法是依次比较每个字符,看它们是否匹配。像下面这样:

const A = "imagine that this is a string of 200 characters"

const B = "imagine that this is a string of 200 characterz"

function equal(A, B) {
  let i;
  for(i = 0; i < A.length; i++){
    if (A[i] !== B[i]) {
      return false;
    }
  }
  return true;
}

console.log(equal(A,B));

对于超长字符串来说,这并不是最佳选择,因为它的性能是 O(min(A, B))。

当然,我们可以通过添加一个比较 A 大小和 B 大小的条件来使其成为 O(n)。像下面这样:

function equal(A, B) {
  if (A.lenght !== B.length) return false;
  let i;
  for(i = 0; i < A.length; i++){
    if (A[i] !== B[i]) {
      return false;
    }
  }
  return true;
}

正如我所说,最坏的情况是 O(n),但在实际生产环境中,我们需要比较一个真正的超长字符串。

字符串哈希可以将我们的字符串转换成一个整数,这将作为一个哈希数字。因此,我们比较两个哈希值 hash(A)==hash(B) ,性能会是 O(1)。这是我们比较两个字符串的最佳选择。

String Hash 公式

我们规定 p 和 m 是素数,s[0], s[1], s[2]… 是每个字符的值,在这里是字符编码。

p: 素数,大致等于所使用的不同字符的数量。例如,如果我们要计算一个只包括英文小写字母的单词的哈希值,31 会是个不错的数字。然而,如果我们还会使用大写字母的话,那么 53 是一个合适的选择。

m: 这个数字越大,两个随机字符串出现碰撞的概率就越小。这个变量也应该是素数。

10 ^ 9 + 9 是个常用的选择。

比如我们使用下面一组:

p = 31
m = 1e9 + 9
word = 'apple'

我们想要得到 apple 的哈希,因此我们使用 String Hash 公式:

进一步:

字符编码为:

"a" = 97
"p" = 112
"p" = 112
"l" = 108
"e" = 101

在公式中进行替换:

然后我们化简得到公式:

最终:

我们最终得到 apple 的哈希为 96604250。

以下是 JavaScript 的实现:

function hash(word) {
  var p = 31;
  var m = 1e9 + 9;
  var hash_value = 0;
  for(var i = 0; i < word.length; i++) {
      var letter = word[i];
      var charCode = letter.charCodeAt();
      hash_value = hash_value + (charCode * Math.pow(p, i))
  }
  return hash_value % m;
}

console.log(hash("apple"));

This is a version from Jorge Chávez.