散列表

109 阅读2分钟

什么是散列表 ?

  • 散列表是拓展用数组来实现的无序符号表,它拓展后可以处理更为复杂的键。而后将键转化为数组索引来访问数组中的键值对。

散列查找算法

  1. 第一步,用散列函数将被查找的键转化为数组的一个索引。在理想状态下,不同的键转化成不同的索引值。
  2. 第二步,在两个或两个以上的键转化为同一个索引值时,就需要处理碰撞冲突。主要有:拉链法线性探测法 两种方法。

散列函数

  • 散列函数是经过计算后将键转化为数组索引的函数。对于每种类型的键,我们都需要一个与之一一对应的散列函数。
  • 键的主要类型有:1、正整数。2、浮点数。3、字符串。4、组合键。其中相对重要的是后两种。
  • 散列字符串键 转化为索引值。R 可以取一个较小的素数(如 31)并且要在区间[0,M - 1] 上。M 为数组的长度。
public int hash(String s) {
    int hash = 0;
    for (int i = 0; i < s.length(); i++) {
        hash = (R * hash + s.charAt(i)) % M;
    }
    return hash;
}
  • 多整型变量 转化为索引值。例如 Date。
public int hash(int day, int month, int year) {
    int hash = (((day * R + month) % M) * R + year) % M;
    return hash;
}
  • 返回值转化为数组索引,数组的索引不是一个 32 位的整数,所以默认将 hashCode() 方法和除留余数结合产生一个 0 到 M - 1 的整数。在这里 M 应取为素数,才能够从分利用散列值的所有位。
public int hash(Key k) {
    return (k.hashCode() & 0x7fffffff) % M;
}
  • 自定义 hashCode() 示例
private final String who;
private final Date when;
private final double amount;

public int hashCode() {
    int hash = 17;
    hash = 31 * hash + who.hashCode();
    hash = 31 * hash + when.hashCode();
    hash = 31 * hash + ((Double)amount).hashCode();
    return hash;
}

软缓存

  • 散列值的计算是比较耗时的,所以我们将每个键的散列值缓存起来,只有在第一次调用时计算。每一个键用一个 hash 变量保存它返回的 hashCode() 值。
  • 健壮的散列方法需满足下列条件:
  1. 一致性。 -- 等价的键必然产生相等的散列值。
  2. 高效性。 -- 计算简便。
  3. 均匀性。 -- 均匀地散列所有的键。