Java数据结构——哈希表
什么是哈希表?
哈希表是一种根据关键码去寻找值的数据映射结构,该结构通过把关键码映射的位置去寻找存放值的地方。
哈希表充分体现了算法设计领域的经典思想:空间换时间。哈希表是时间和空间之间的平衡。其中的哈希函数是最重要的,“键”通过哈希函数得到的“索引”分布越均匀越好。但是哈希表会失去顺序性。
哈希函数的设计
对于整型
- 小范围正整数直接使用
- 小范围负整数进行偏移 -100~100 ----> 0~200
- 大整数:模一个素数
对于浮点型
转成整型处理
对于字符串
也是转成整型处理
int hash=0; for(int i=0;i<s.length();i++ { hash=(hash*B+s.charAt(i))%M; }
对于复合类型
依然转成整型处理
hash(code)=((((c%M)*B+o)%M*B+d)%M*B+e)%M;
但是,转成整型处理,并不是唯一的方法
哈希函数设计原则
- 一致性:如果a==b,则hash(a)==hash(b),反之不一定成立
- 高效性:计算高效简便
- 均匀性:哈希值分布均匀
哈希冲突的处理
链地址法(O(1))
在jdk7之前是数组+链表
jdk8开始,当链表长度超过一定值后会转换成红黑树
开放地址法
不会形成链,当遇到哈希冲突时,直接往冲突的位置的后面的第一个空的位置里放。当这个哈希表足 够大且元素存的足够多的时候,会导致查找下一个空位的效率变得很低。
实现一个自己的哈希表
import java.util.TreeMap; public class HashTable<K, V> { private TreeMap<K, V>[] hashtable; private int M; private static final int upperTol = 10; private static final int lowerTol = 2; private static final int initCapacity = 7; 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; 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("don't find"); } else { map.put(key, value); } } public boolean contain(K key) { return hashtable[hash(key)].containsKey(key); } public V get(K key) { TreeMap<K, V> map = hashtable[hash(key)]; return map.get(key); } private void resize(int newM) { TreeMap<K, V>[] newTable = new TreeMap[newM]; for (int i = 0; i < newM; i++) { newTable[i] = new TreeMap<>(); } int oldM = M; this.M = newM; for (int i = 0; i < oldM; i++) { TreeMap<K, V> map = hashtable[i]; for (K key : map.keySet()) { newTable[hash(key)].put(key, map.get(key)); } } this.hashtable = newTable; } }