文章目录
- 哈希表概述
- 哈希函数的设计
- 整型
- 浮点型
- 字符串
- 模运算性质
- 复合类型
- 哈希冲突的处理
- 链地址法
- 开放地址法
- 其他哈希冲突的处理方法
- 哈希表的复杂度分析
- 完整的Java代码
哈希表概述
- 哈希表充分体现了算法设计领域的经典思想:空间换时间
- 哈希表是时间和空间之间的平衡
- 哈希函数的设计很重要
- “键”通过哈希函数得到的“索引”分布越均匀越好
哈希表:均摊复杂度为O(1),牺牲了其顺序性
哈希函数的设计
“键”通过哈希函数得到的“索引”分布越均匀越好。
整型
小范围正整型
- 小范围正整数直接使用
- 小范围负整数进行偏移 -100 ~ 100
大范围正整型
一个简单的解决办法: 模一个素数。
浮点型
字符串
看成多少进制的整型可以自定义的。
-
M
:取得模,相应的数组有多大的空间。
模运算性质
若转化成这样,取模运算的代码实现将比较简易。
复合类型
转成整型处理 ,并不是唯一的方法,原则如下:
1. 一致性: 如果a==b, 则hash(a) == hash(b)
2. 高效性: 计算高效简便
3. 均匀性: 哈希值均匀分布
哈希冲突的处理
链地址法
查找表可以是红黑树。当数据量少,冲突比较小时查找表是链表比较好
哈希表是一个
动态数组
,空间随着N的改变进行自适应,需要resize
。
平均每个地址承载的元素多
过一定程度,即扩容
:
平均每个地址承载的元素少
过一定程度,即缩容
:
private static final int upperTol = 10;
private static final int lowerTol = 2;
private static final int initCapacity = 7;
// if (size >= upperTol * M)
// resize(2*M);
// if (size < lowerTol*M && M/2 >= initCapacity)
// resize(M / 2);
private void resize(int newM) {
TreeMap<K,V>[] newHashTable = new TreeMap[newM];
for (int i = 0; i < newM; i++)
newHashTable[i] = new TreeMap<>();
int oldM = M;
this.M = newM; //hash()函数中将M换成改变了的newM
for (int i = 0; i < oldM; i++) {
TreeMap<K, V> map = hashtable[i];
for(K key: map.keySet())
newHashTable[hash(key)].put(key, map.get(key));
}
this.hashtable = newHashTable;
}
开放地址法
- 线性探测, 遇到哈希冲突 +1
- 平方探测, 遇到哈希冲突 +1 +4 +9 +16
- 二次哈希法, 遇到哈希冲突 +hash(key)
其他哈希冲突的处理方法
- 再哈希法 Rehashing
- Coalesced Hasing ,综合了Seperate Chaining 和 Open Addressing
哈希表的复杂度分析
回忆动态数组的均摊复杂度分析 ,平均复杂度O(1)
对于哈希表来说,元素数从N
增加到;地址空间倍增, 平均复杂度 O(1)
- 每个操作在
O(lowerTol)
~O(upperTol)
->O(1)
缩容同扩容原理一样。
扩容 ,但扩容的
private final int[] capacity
= {53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469,
12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741};
private int capacityIndex = 0;
public HashTable() {
this.M = capacity[capacityIndex];
size = 0;
hashtable = new TreeMap[M];
for (int i = 0; i < M; i++) {
hashtable[i] = new TreeMap<>();
}
}
// if (size >= upperTol * M && capacityIndex+1 < capacity.length) {
// capacityIndex ++;
// resize(capacity[capacityIndex]);
// }
// if (size < lowerTol*M && capacityIndex-1 >= 0) {
// capacityIndex --;
// resize(capacity[capacityIndex]);
// }
初始时为链表,当冲突达到一定程度时转为红黑树。
完整的Java代码
import java.util.TreeMap; //底层就是一个红黑树
public class HashTable<K,V>{
private final int[] capacity
= {53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469,
12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741};
private static final int upperTol = 10;
private static final int lowerTol = 2;
private int capacityIndex = 0;
private TreeMap<K, V>[] hashtable;
private int M; //合适的素数
private int size;
public HashTable() {
this.M = capacity[capacityIndex];
size = 0;
hashtable = new TreeMap[M];
for (int i = 0; i < M; i++) {
hashtable[i] = new TreeMap<>();
}
}
private int hash(K key) {
return (key.hashCode() & 0x7fffffff) % M;
}
public int getSize() {
return size;
}
public void add(K key, V value) {
TreeMap<K, V> map = hashtable[hash(key)];
if(map.containsKey(key))
map.put(key,value);
else {
map.put(key, value);
size++;
if (size >= upperTol * M && capacityIndex+1 < capacity.length) {
capacityIndex ++;
resize(capacity[capacityIndex]);
}
}
}
public V remove(K key) {
TreeMap<K, V> map = hashtable[hash(key)];
V ret = null;
if(map.containsKey(key)) {
ret = map.remove(key);
size --;
if (size < lowerTol*M && capacityIndex-1 >= 0) {
capacityIndex --;
resize(capacity[capacityIndex]);
}
}
return ret;
}
public void set(K key, V value) {
TreeMap<K, V> map = hashtable[hash(key)];
if(!map.containsKey(key))
throw new IllegalArgumentException(key + "doesn't exist!");
map.put(key,value);
}
public boolean contains(K key) {
return hashtable[hash(key)].containsKey(key);
}
public V get(K key) {
return hashtable[hash(key)].get(key);
}
private void resize(int newM) {
TreeMap<K,V>[] newHashTable = new TreeMap[newM];
for (int i = 0; i < newM; i++)
newHashTable[i] = new TreeMap<>();
int oldM = M;
this.M = newM; //hash()函数中将M换成改变了的newM
for (int i = 0; i < oldM; i++) {
TreeMap<K, V> map = hashtable[i];
for(K key: map.keySet())
newHashTable[hash(key)].put(key, map.get(key));
}
this.hashtable = newHashTable;
}
}