1散列表
用算术操作将键转化为数组的索引来访问数组中的键值对。
散列表的查找算法分两步:
- 用散列函数将键转化为数组的索引
- 处理碰撞冲突(常用的两种方法)
- 拉链法
- 线性探测法
使用散列表:
- 可以实现在一般应用中拥有(均摊后)常数级别的查找和插入操作的符号表。
- 但是散列表不支持有序性操作。
1.1散列函数
将键转化为数组的索引。
严格来说:对于每种类型的键我们都需要一个与之对应的散列函数。
- 正整数:除留余数法。(k对数组大小M取余)
- 浮点数:将键转化为二进制再用除留余数法。
- 字符串:将字符串当做大整数。
- 组合键:将各个键的hash值组合起来。
java的约定:
- 如果需要为自定义的数据类型定义散列函数,就需要同时重写
hashCode()
和equals()
方法。
1.2软缓存
- 第一次调用
hashCode()
方法,计算对象的散列值。 - 但之后调用
hashCode()
方法,直接返回hash
变量的值。
一个优秀的散列函数应该满足以下三个条件:
- 一致性:等价的键必然产生相同的散列值
- 高效性:计算简便
- 均匀性:所有的键均匀分布
2基于拉链法的散列表
/**
* @Author: AZhu
* @Date: 2021/3/7 23:50
*/
public class SeparateChainingHashST<Key, Value> {
private int N;//键值对总数
private int M;//散列表的大小
private SequentialSearchST<Key, Value>[] st;//存放链表对象的数组
public SeparateChainingHashST() {
this(997);
}
public SeparateChainingHashST(int M) {
this.M = M;
st = (SequentialSearchST<Key, Value>[]) new SequentialSearchST[M];
//创建M条链表
for (int i = 0; i < M; i++) {
st[i] = new SequentialSearchST<>();
}
}
private int hash(Key key) {
return (key.hashCode() & 0x7fffffff) % M;
}
/**
* 查找
* @param key
* @return
*/
public Value get(Key key) {
return (Value) st[hash(key)].get(key);
}
/**
* 插入
* @param key
* @param value
*/
public void put(Key key, Value value) {
st[hash(key)].put(key, value);
}
}
3基于线性探测法的散列表
/**
* 基于线性探测法的散列表
* @Author: AZhu
* @Date: 2021/3/8 0:03
*/
public class LinearProbingHashST<Key, Value> {
private int N;//键值对总数
private int M = 16;//线性探测表的大小
private Key[] keys;
private Value[] values;
public LinearProbingHashST() {
keys = (Key[]) new Object[M];
values = (Value[]) new Object[M];
}
public LinearProbingHashST(int m) {
this.M = m;
keys = (Key[]) new Object[M];
values = (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<Key, Value>(cap);
for (int i = 0; i < M; i++) {
if (keys[i] != null) {
t.put(keys[i], values[i]);
}
}
keys = t.keys;
values = t.values;
M = t.M;
}
/**
* 插入
* @param key
* @param value
*/
public void put(Key key, Value value) {
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)) {
values[i] = value;
return;
}
}
keys[i] = key;
values[i] = value;
N++;
}
/**
* 查找
*
* @param key
* @return
*/
public Value get(Key key) {
for (int i = hash(key); keys[i] != null; i = (i + 1) % M) {
if (keys[i].equals(key)) {
return values[i];
}
}
return null;
}
}
4内存使用
符号表的内存使用:
方法 | N个元素所需的内存 |
---|---|
基于拉链法的散列表 | ~48N+32M |
基于线性探测法的散列表 | 在32N和128N之间 |
各种二叉查找树 | ~56N |