赞
踩
你受的苦,吃的亏,担的责,扛的罪,忍的痛,到最后都会变成光,照亮你的路。
哈希表(Hash table,散列),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做哈希函数,存放记录的数组叫做哈希表。
举个栗子:
一个班有30名学生,他们的学号是1-30的,我们用数组来存储这些学生:
学号 | 数组下标 |
---|---|
1 | 0 |
2 | 1 |
3 | 2 |
… | … |
30 | 29 |
事实上,这个数组就是一个哈希表,班里每个学生的学号都对应了数组中的一个下标。
具体的对应关系为 : 下标 = 学号 - 1
而 f(key) = value,给定一个键值,计算得到一个地址,这样的关系函数就是哈希函数。
在这个具体的例子中, 下标 = 学号 - 1 就是哈希函数,给定一个学号,就能知道这个学生在数组中的存储位置。这样的话,想要查询一个学生的信息只需要O(1)的时间复杂度!
当然这是一种最为简单的哈希表,想要使用哈希表的方法进行查找,就必须解决下面两个问题:
所谓的Hash冲突是指不同的key通过Hash函数所求的地址值value相同,则这些key就产生了Hash冲突
哈希表之所以能达到O(1)的复杂度,本质上是以空间换时间。如果空间足够大,相应的我们就能在O(1)的复杂度下完成各项操作,而如果只有O(1)的空间,那么就需要O(n) (线性表)的时间复杂度。
Hash表就是时间和空间之间的平衡,因此Hash函数的构建是非常重要的。通常应该遵循下列原则:
在上一个学号的例子中,我们直接以 下标 = 学号 - 1 的方式,很快的完成了Hash函数的构建,但事实上不是所有的问题都可以如此简单的构建出来,下面就来讨论更复杂的情况下如何构建Hash函数。
下面给出了大整数在lwr-upr之间的最佳取模的素数:或者点击这个链接–>最佳取模的素数
之所以模以一个素数,是因为模以一个素数可以减少hash冲突并且能较为充分地利用到大整数的每部分数据。
比如:
在模以4时产生了严重的Hash冲突,而模以素数7在这组数据中没有发生Hash冲突。
根据实际需求,我们也可以将字符串转化成B进制的整数。那么hash函数就是这样的:
上面三个hash函数是等价的,只是在第一个hash函数下简化了计算而已。
由于具体问题的复杂性,Hash冲突不可避免的存在,因此就需要对Hash冲突进行处理,通常较好的方式是:链地址法。
例如通过Hash函数计算得到 k1的地址为4,k2的地址为1,分别插入后又来了一个k3且地址也是1,此时k1和k3发生了冲突,如何处理呢?
链地址法就是让发生冲突的元素以链表插在前一个元素后面:
事实上发生冲突时并不一定要构成一个链表,只要是查找表就行,也就是说我们完全可以链接一个AVL树或者红黑树。
在Java中HashMap就是TreeMap的数组;HashSet是TreeSet的数组。
package cn.boom.hash; import java.util.Arrays; import java.util.TreeMap; public class HashTable<K, V> { //取模的素数 private static int[] prime = {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; //平均hash冲突上界 private static final int lowerTol = 2; //平均hash冲突下界 private TreeMap<K,V>[] hashTable; private int capacity; private int size; private int capacityIndex; public HashTable(){ this.size = 0; this.capacityIndex = 0; this.capacity = prime[capacityIndex]; this.hashTable = new TreeMap[capacity]; for (int i = 0; i < capacity; i++) { hashTable[i] = new TreeMap<K, V>(); } } public int getSize() { return size; } public boolean contains(K key) { int address = hash(key); return hashTable[address].containsKey(key); } //hash函数 private int hash(K key) { return key.hashCode() & 0x7fffffff % capacity;//取key hashCode的正值并计算hash值 } private void resize(int newCapacity){ TreeMap<K, V>[] newHashTable = new TreeMap[newCapacity]; for(int i = 0 ; i < newCapacity ; i ++) newHashTable[i] = new TreeMap<K,V>(); int oldCapacity = this.capacity; this.capacity = newCapacity; for(int i = 0 ; i < oldCapacity ; i ++) for(K key: hashTable[i].keySet()) newHashTable[hash(key)].put(key, hashTable[i].get(key)); this.hashTable = newHashTable; } public void add(K key, V value) { int address = hash(key); if (hashTable[address].containsKey(key)) { hashTable[address].put(key, value); } else { hashTable[address].put(key, value); this.size++; if (this.size >= this.capacity * upperTol && capacity+1 < prime.length) { resize(prime[(capacityIndex++)]); } } } public V remove(K key) { int address = hash(key); if (!hashTable[address].containsKey(key)) { throw new IllegalArgumentException(key + " doesn't exist ! "); } V ret = hashTable[address].remove(key); size--; if (this.size < this.capacity * lowerTol && capacityIndex - 1 >= 0) { resize(prime[(capacityIndex--)]); } return ret; } @Override public String toString() { return "HashTable{" + "hashTable=" + Arrays.toString(hashTable) + ", capacity=" + capacity + ", size=" + size + ", capacityIndex=" + capacityIndex + '}'; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。