Hash Tables
1 hash functions 散列函数
- 用index(0、1、2……)代表key
- 优秀的散列方法要保持一致性(等价键产生相同散列值)、高效性(计算简便)、均匀性(均匀地散列所有键)
- 因为string是不能变的,因此string的hash code也不变,只需计算一次
package Chapter03;
//自定义类型中hashcode()方法的实现
public final class Transaction implements Comparable<Transaction>{
private final String who;
private final Date when;
private final double amount;
public Transaction(String who, Date when, double amount) {
if (Double.isNaN(amount) || Double.isInfinite(amount))
throw new IllegalArgumentException("Amount cannot be NaN or infinite");
this.who = who;
this.when = when;
this.amount = amount;
}
public boolean equals(Object y){
if (y == this) return true;
if (y == null) return false;
if (y.getClass() != this.getClass()) return false;//保证比较的两者在同一个类中
Transaction that = (Transaction) y;
return (this.amount == that.amount) && (this.who.equals(that.who))
&& (this.when.equals(that.when));
}
//计算hashcode,返回一个32-bit value
public int hashCode(){
int hash = 17;//自定义一个nonzero constant(small prime number)
//for reference type, use hashCode()
hash = 31 * hash + who.hashCode();//small prime number --> 31
hash = 31 * hash + when.hashCode();
//for primitive type, use hashCode() of wrapper type 括号包装
hash = 31 * hash + ((Double) amount).hashCode();
return hash;
}
@Override
public int compareTo(Transaction o) {
return 0;
}
}
- 将hashCode()的返回值转化为一个数组索引,因为需要的是数组的索引而不是一个32位的整数
- 将默认的hashCode()和除留余数法结合,产生一个0~M-1的整数
- 将符号位屏蔽(将1个32位整数变为一个31位非负整数)
- M要选质数,以充分利用原散列值的所有位
2 separate chaining 基于拉链法的散列表
package Chapter03;
public class SeparateChainingHashST<Key, Value>{
private int M = 97; //number of chains
private Node[] st = new Node[M];//array of chains
private static class Node{
private Object key;
private Object val;
private Node next;
//有参构造
public Node(Object key, Object val, Node next) {
this.key = key;
this.val = val;
this.next = next;
}
}
private int hash(Key key){
return (key.hashCode() & 0x7fffffff) % M;//返回0到M-1
}
public Value get(Key key){
int i = hash(key);
for (Node x = st[i]; x != null ; x = x.next) {//st[i]是一条链
if (key.equals(x.key)) return (Value) x.val;
}
return null;
}
public void put(Key key, Value val){
int i = hash(key);
for (Node x = st[i]; x != null ; x = x.next) {//st[i]是一条链
if (key.equals(x.key)) {
x.val = val;
return;
}
}
st[i] = new Node(key, val, st[i]);//没有找到,则在原链表的最前面新建一个node
}
}
3 linear probing 基于线性探测法的散列表
- Insert
- 数组M必须比键值对的数量N大
- Search
package Chapter03;
public class LinearProbingHashST<Key, Value> {
private int M = 30001;//线性探测表的大小
private Value[] vals = (Value[]) new Object[M];
private Key[] keys = (Key[]) new Object[M];
private int hash(Key key){
return (key.hashCode() & 0x7fffffff) % M;//返回0到M-1的整数
}
public void put(Key key, Value val){
int i;
for (i = hash(key); keys[i] != null ; i = (i+1) % M) {//%取余 当被除数比除数小,余数为除数
if (keys[i].equals(key)){
break;//立即终止离他最近的那个循环语句 不能在if语句中使用break和continue,此处的break是对for循环起作用,因此可以使用
}
}
keys[i] = key;
vals[i] = val;
}
public Value get(Key key){
for (int i = hash(key); keys[i] != null ; i = (i+1) % M) {
if (key.equals(keys[i])){
return vals[i];
}
}
return null;
}
}
- mean displacement平均位移距离
4 context 散列表的文本应用