散列表

如果所有的键都是小整数,我们可以用一个数组来实现无序的符号表,将键作为数组的索引而数组中键i处存储对应的值。这样我们就可以快速访问任意键的值。
散列表就是这种简易方法的扩展,并能够处理更加复杂的类型的键,我们需要通过算术操作将键转化为数组的索引来访问数组中的键值对。
使用散列表的查找算法分为两步。第一步是用散列函数将被查找的键转化为数组的一个索引;第二步是就是一个处理碰撞冲突的过程。


  • 散列函数
    Java中每种数据类型都继承了一个能够返回32比特整数的hashCode()方法,与除留余数法结合产生一个0到M-1的整数:

    private int hash(Key x){
        return (x.hashCode() && 0x7fffffff) % M;
    }
    
  • 基于拉链法的散列表
    散列函数能将键转化为数组索引,而散列算法的第二步是处理碰撞,也就是两个或多个散列值相同的情况。拉链法将大小为M的每个元素均指向一条链表,链表中的每个结点均存储了散列值为该元素索引的键值对。这个方法的基本思想是选取尽可能大的M以使得所有链都尽可能的短。
    public class SeparateChainingHashST{
    private int N; //键值对总数
    private int M; //散列表大小
    private SequentialSearchST[] st; //存放链接的对象数组
    public SeparateChainingHashST(){
    this(997);
    }
    public SeparateChainingHashST(int M){
    //创建M条链表
    this.M = M;
    st = (SequentialSearchST[]) new SequentialSearchST[M];
    for (int i=0; i st[i] = new SequentialSearchST();
    }
    private int hash(Key key){
    return ( key.hashCode() & 0x7fffffff) %M;
    }
    public Value get(Key key){
    return (Value) st[hash(key)].get(key);
    public void put(Key key, Value val){
    st[hash(key)].put(key, val);
    }
    public Iterable keys()

  • 基于线性探测法的散列表
    实现散列表的另一种方式是用大小为M的数组来存储N个键值对,其中M>N。依靠数组中的空位来解决碰撞冲突。基于这种策略的所有方法被称为开放地址散列表。
    开放地址散列表实现的最简单方法是线性探测法——当碰撞发生时,我们直接检测下一个位置,于是产生三种结果:

    • 命中,该位置的键和被查找的键相同;
    • 未命中,键为空;
    • 继续查找,该位置的键与被查找的键不同。

    开放地址类的散列表的核心思想是与其将内存用作链表,不如将它们作为散列表的空元素。

    public class LinearProbingHashST{
        private int N;
        private int M;
        private Key[] keys;
        private Value[] vals;
        public LinearProbingHashST(){
            keys = (Key[]) new Object[M];
            vals = (Value[]) new Object[M];
        }
        private int hash(Key key){
            return (key.hashCode() & 0x7fffffff ) %M;
        }
        private resize();
        public void put(Key key, Value val){
            if( N >= M/2) resize(2*M);
            int i;
            for( i =hash(key); keys[i] ! = null; i = (i+1)%M){
                if( keys[i].equals(key)){
                    vals[i] = val;
                    return;
                }
                keys[i] = key;
                vals[i] = val;
                N++;
        }
    
        public Value get(Key key){
            for( int i = hash(key); keys[i] != null; i = (i+1)%M)
                if(keys[i].equals(key))
                    return vals[i];
            return null;
        }
    }
    

对于线性探测法的删除操作,直接将该键的位置设置为null是不行的,因为这样将会导致该键位置之后的元素都无法被找到。
public void delete(Key key){
if( !contains(key)) return;
int i = hash(key);
while(!key.equals(keys[i]))
i = (i+1) % M;
keys[i] = null;
vals[i] = null;
i = (i+1) % M;
while( keys[i] != null){
Key keyToRedo = keys[i];
Value valToRedo = vals[i];
keys[i] = null;
vals[i] = null;
N--;
put(keyToRedo, valToRedo);
i = (i+1) % M;
}
N--;
if( N>0 && N <= M/8) resize(M/2);
}

你可能感兴趣的:(散列表)