综述:
散列是一种码位一体化搜索解决方案。
它将元素关键码(key)映射到实际存储位置附近,实现了事实上的分治。搜索复杂度接近O(1)。
只要选择好合适的散列函数和冲突解决办法,散列就快速,高效,优雅。
散列函数以除数留余法最佳,冲突解决方法以开散列法最好。
客:散列背后的思想是什么?
主:其他搜索解决方案都是从搜索树出发,尽可能减少搜索树的深度。散列与它们截然不同!
散列是借鉴了数组的思想。数组为什么查找高效?因为它有索引,直接标志了存储位置。
那么如果我们把元素的关键码作为索引,也就能取得和数组一样的搜索速度。
这也就是我说的码位一体化。
当然实际实现要解决很多问题,比如冲突,不过散列很多情况下都是最高效的搜索数据结构。
客:散列有什么作用?
主:散列是一种码位一体化解决方案。为了解决快速查找与插入元素的问题。
客:它为什么快?
主:散列将待查找元素的标志码(key)映射到具体的存储空间附近(一般是数组),把搜索整个存储空间变为搜索冲突区域,
由于冲突区域远小于存储空间,这样就实现了事实上的分治。散列的快速也来源于此。实际上,只要冲突不多,散列的查找复杂度接近O(1)。
客:什么是散列函数?
主:hash在英文中是“弄乱”意思。散列函数就是把元素的关键码,均匀的映射到存储空间,以利于查找。
客:常见散列函数有哪些?
主:除留余数法,数字分析法,平方取中法,折叠法。其中除留余数法最好。
除留余数法也就是选一个小于等于存储空间大小的最大质数,然后将关键码对其求余(%),将余数作为存储空间。
客:什么是冲突?
主:由于映射不可能均匀,总会出现将两个不同元素映射到同一存储位置的情况出现,这就是冲突。
客:怎么解决冲突?
主:这要根据实际存储实现来定。闭散列存储实现(也就是用数组)解决冲突方法就是继续查看下一存储位置,如果空就插入,不空就继续查看。
所以当散列很满时,映射位置往往不是实际存储位置。这也是我为什么说实际存储位置在映射位置附近的原因。不过如果冲突探测方法均匀的话,冲突区域远小于整体存储空间,散列仍然高效。
因此,闭散列的冲突探测函数就是想把探测位置尽可能分散,避免某位置冲突多而对整体造成影响。
开散列方法是数组链表混合存储。数组存放每个映射位置的起始地址,链表存放冲突元素。
开散列方法是最好的冲突解决方法,因为它实现了事实上的冲突映射均匀分布。
客:闭散列的冲突探测函数有哪些?
主:有线性探测法,二次探测法,双散列法等。整体来说,解决冲突实际上进行的是二次散列映射,映射的越均匀,效能越好。
这样是开散列为什么比闭散列好的原因。开散列实现了事实上最好的冲突映射,虽然它是从存储层次解决的。
客:散列搜索性能与什么有关?
主:只与装载因子有关。装载因子就是衡量表是否满的标志。取值在0-1之间,0表示空,1表示满。
装载因子越小,搜索性能越好。闭散列搜索因子最好不要超过0.5,不过如果开散列实现的话,即使装载因子接近1,也能保证很好的搜索性能。
下面给出闭散列线性探测法的一个实现:
class Data implements Comparable<Data>{ int key; int value; public Data(){} public Data(int key, int value){ this.key = key; this.value = value; } @Override public int compareTo(Data data) { return this.key - data.key; } } /** * closed hashTable: stored by cycle array. * it is just an example. * Note that closed hashTable is not a good implement of hashTable, comparing to open hashTable. */ public class MyHashSet { private int setSize; private int elementNum; private String[] setInfo; private Data[] hashSet; public MyHashSet(){ this.setSize = 6; // the size is small, because it is easy to test :) this.elementNum = 0; this.hashSet = new Data[this.setSize]; this.setInfo = new String[this.setSize]; //"empty", "deleted" or "active" for (int i = 0; i < setInfo.length; i++) { setInfo[i] = "empty"; } } private int hash(int key){ return key % 5; } /** * Find the storage position of element * This is a private element. because it cannot directly reflect the real position of element. * @param element * @return position */ private int searchPos(Data element){ int pos = this.hash(element.key); int j = pos; do { if (setInfo[j].equals("empty") || setInfo[j].equals("active") && hashSet[j].key == element.key){ break; } j = (j + 1) % this.setSize; } while (j != pos); return j; } /** * Find the position of element.if find, return position; else, return -1; * @param element * @return */ public int search(Data element){ int pos = this.searchPos(element); if (setInfo[pos].equals("active") && hashSet[pos].key == element.key){ return pos; }else { return -1; } } /** * insert a new element into hashSet * @param element * @return */ public boolean insert(Data element){ if (this.elementNum == this.setSize){ System.err.println("The Set is FULL!"); return false; } int pos = searchPos(element); if (!setInfo[pos].equals("active")){ setInfo[pos] = "active"; hashSet[pos] = element; this.elementNum++; return true; }else { if (hashSet[pos].key == element.key){ System.err.println("Already have!"); return false; }else { System.err.println("The Set is logically full!"); return false; } } } /** * delete the element of hashSet * @param element * @return if delete successfully , return true; else, false. */ public boolean delete(Data element){ if (this.elementNum == 0){ System.err.println("The Set is Empty!"); return false; } int pos = searchPos(element); if (setInfo[pos].equals("active") && hashSet[pos].key == element.key){ setInfo[pos] = "deleted"; this.elementNum--; return true; }else { System.err.println("Not have!"); return false; } } public static void main(String[] args){ MyHashSet hashSet = new MyHashSet(); hashSet.insert(new Data(1,11)); hashSet.insert(new Data(13,12)); hashSet.insert(new Data(3,13)); hashSet.insert(new Data(4,14)); hashSet.insert(new Data(16,15)); hashSet.insert(new Data(5,16)); for (Data d : hashSet.hashSet){ System.out.println(d.value); } System.out.println(hashSet.elementNum); hashSet.insert(new Data(14,4)); } }