使用散列的查找算法分为两步,第一步用散列函数将被查找的键转化为数组的一个索引,理想情况下不同的键都被转化为不同的索引值.而当多个键散列到相同的索引值的情况下,就需要处理碰撞冲突,为此有两种方法,拉链法和线性探测法.
散列函数用于通过键来获取其对应的索引值.好的散列函数应该具有计算简便,等价的键必然产生相等的散列值,均匀的散列所有的键的条件.
一.基于拉链法的散列表.
拉链法对于碰撞处理的解决方法是将大小为M的数组中的每个元素都指向一个链表,链表中的每一个节点都存储了散列值为该元素的索引的键值对.查找分两步:首先根据散列值找到对应的链表,然后沿着链表顺序查找相应的键.用M条链表保存N个键,链表的平均长度为N/M.代码实现如下:
public class SeparateChainingHashST<Key,Value> { private int N; //键值对总数 private int M; //散列表大小 private SequentialSearchST<Key,Value>[] st; public SeparateChainingHashST() { this(997); } public SeparateChainingHashST(int M) { //创建M条链表. this.M=M; st=(SequentialSearchST<Key,Value>[])new SequentialSearchST[M]; for (int i = 0; i < M; 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); } } //链表 class SequentialSearchST<Key,Value> { private Node first; //链表首节点 private class Node { Key key; Value val; Node next; public Node(Key key,Value val,Node next) { this.key=key; this.val=val; this.next=next; } } public Value get(Key key) { for(Node x=first;x!=null;x=x.next) { if(key.equals(x.key)) { return x.val; } } return null; } public void put(Key key,Value val) { for (Node x=first;x!=null;x=x.next) { if(key.equals(x.key)) { x.val=val; return; } first=new Node(key,val,first); } } }
二.基于线性探测法的散列表
实现散列表的第二种形式就是用大小为M的数组保存N个键值对,其中M>N.依靠数组的空位来解决碰撞冲突.当碰撞发生的时候,直接检查散列表的下一个位置,并将索引加1.我们用散列函数找到键在数组的索引,判断键是否和被查找的键相同,如果不同则继续查找,直到找到该键或者遇到一个空元素.代码实现如下(其中需要动态的调整数组的大小以提高查找效率)
public class LinearProbingHashST<Key,Value> { private int N; //符号表中的键值对的总数 private int M=16; //线性探测表的大小 private Key[] keys; //键 private Value[] vals;//值 public LinearProbingHashST() { keys=(Key[]) new Object[M]; vals=(Value[]) new Object[M]; } public LinearProbingHashST(int x) { M=x; keys=(Key[]) new Object[M]; vals=(Value[]) new Object[M]; } private int hash(Key key) { return (key.hashCode()&0x7fffffff)%M; } private void resize(int cap) { LinearProbingHashST<Key, Value> t; t=new LinearProbingHashST<>(cap); for(int i=0;i<M;i++) { if(keys[i]!=null) t.put(keys[i], vals[i]); } keys=t.keys; vals=t.vals; M=t.M; } //实现动态的调整数组大小 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); } public boolean contains(Key key) { for(Key okey:keys) { if(key.equals(okey)) return true; } return false; } }