1.算法汇总
首先,来看一张各种字符串查找算法的汇总。前面的文章已经介绍过二叉树查找和红黑树查找。这里不在介绍。
本文重点介绍后面三种查找算法:线性探测法、R向单词查找树和三向单词查找树。
2.线性探测法
实现散列表的另一种方式是用大小为M的数组保存N个键值对,其中M>N。依靠数据中的空位解决碰撞冲突。基于这种策略的所有方法都统称为开放地址散列表。其中最简单的方法叫做线性探测法:当碰撞发生时,直接检查散列表的下一个位置(索引加1),可能产生三种结果:
- 命中,该位置的键和被查找的键相同;
- 未命中,键为空(该位置没有键);
- 继续查找,该位置的键和被查找的键不同。
其核心思想是与其将内存用作链表,不如将它们作为散列表的空元素。即用散列函数找到索引,检查其中的键和被查找的键是否相同。如果不同则继续查找(增加索引,到达数组结尾后再折回数组开头),直到找到该键或者遇到一个空元素。过程如下图所示:
在基于线性探测法的散列表中执行删除操作比较复杂,如果将该键所在位置为为null是不行的。需要将簇中被删除键的右侧的所有键重新插入散列表。
代码实现:
//基于线性探测的符号表
public class LinearProbingHashST<Key,Value>
{
private static final int INIT_CAPACITY = 16;
private int n;// 键值对数量
private int m;// 散列表的大小
private Key[] keys;// 保存键的数组
private Value[] vals;// 保存值的数组
public LinearProbingHashST()
{
this(INIT_CAPACITY);
}
@SuppressWarnings("unchecked")
public LinearProbingHashST(int capacity)
{
this.m = capacity;
keys = (Key[]) new Object[capacity];
vals = (Value[]) new Object[capacity];
}
public int hash(Key key)
{
return (key.hashCode() & 0x7fffffff) % m;
}
public void put(Key key, Value val)
{
if(key == null)
{
throw new NullPointerException("key is null");
}
if(val == null)
{
delete(key);
return;
}
// TODO扩容
if(n >= m/2)
{
resize(2*m);
}
int i = hash(key);
for (; keys[i] != null; i = (i + 1) % m)
{
if(key.equals(keys[i]))
{
vals[i] = val;
return;
}
}
keys[i] = key;
vals[i] = val;
n++;
}
public void delete(Key key)
{
if(key == null)
{
throw new NullPointerExcepti