什么是哈希(散列)表 一文读明白它
arrayIndex=hugeNumber%arraySize
那么问题来了,这种方式对不同的关键字,可能得到同一个散列地址,即同一个数组下标,这种现象称为冲突,那么我们该如何去处理冲突呢?一种方法是开放地址法,即通过系统的方法找到数组的另一个空位,把数据填入,而不再用哈希函数得到的数组下标,因为该位置已经有数据了;另一种方法是创建一个存放链表的数组,数组内不直接存储数据,这样当发生冲突时,新的数据项直接接到这个数组下标所指的链表中,这种方法叫做链地址法。下面针对这两种方法进行讨论。
1.开放地址法
1.1 线性探测法
public class HashTable {private DataItem[] hashArray; //DateItem类是数据项,封装数据信息private int arraySize;private int itemNum; //数组中目前存储了多少项private DataItem nonItem; //用于删除项的public HashTable() {arraySize = 13;hashArray = new DataItem[arraySize];nonItem = new DataItem(-1); //deleted item key is -1}public boolean isFull() {return (itemNum == arraySize);}public boolean isEmpty() {return (itemNum == 0);}public void displayTable() {System.out.print("Table:");for(int j = 0; j < arraySize; j++) {if(hashArray[j] != null) {System.out.print(hashArray[j].getKey() + " ");}else {System.out.print("** ");}}System.out.println("");}public int hashFunction(int key) {return key % arraySize; //hash function}public void insert(DataItem item) {if(isFull()) {//扩展哈希表System.out.println("哈希表已满,重新哈希化..");extendHashTable();}int key = item.getKey();int hashVal = hashFunction(key);while(hashArray[hashVal] != null && hashArray[hashVal].getKey() != -1) {++hashVal;hashVal %= arraySize;}hashArray[hashVal] = item;itemNum++;}/** 数组有固定的大小,而且不能扩展,所以扩展哈希表只能另外创建一个更大的数组,然后把旧数组中的数据插到新的数组中。但是哈希表是根据数组大小计算给定数据的位置的,所以这些数据项不能再放在新数组中和老数组相同的位置上,因此不能直接拷贝,需要按顺序遍历老数组,并使用insert方法向新数组中插入每个数据项。这叫重新哈希化。这是一个耗时的过程,但如果数组要进行扩展,这个过程是必须的。*/public void extendHashTable() { //扩展哈希表int num = arraySize;itemNum = 0; //重新记数,因为下面要把原来的数据转移到新的扩张的数组中arraySize *= 2; //数组大小翻倍DataItem[] oldHashArray = hashArray;hashArray = new DataItem[arraySize];for(int i = 0; i < num; i++) {insert(oldHashArray[i]);}}public DataItem delete(int key) {if(isEmpty()) {System.out.println("Hash table is empty!");return null;}int hashVal = hashFunction(key);while(hashArray[hashVal] != null) {if(hashArray[hashVal].getKey() == key) {DataItem temp = hashArray[hashVal];hashArray[hashVal] = nonItem; //nonItem表示空Item,其key为-1itemNum--;return temp;}++hashVal;hashVal %= arraySize;}return null;}public DataItem find(int key) {int hashVal = hashFunction(key);while(hashArray[hashVal] != null) {if(hashArray[hashVal].getKey() == key) {return hashArray[hashVal];}++hashVal;hashVal %= arraySize;}return null;}}class DataItem {private int iData;public DataItem (int data) {iData = data;}public int getKey() {return iData;}}
二次聚集不是一个严重的问题,但是二次探测不会经常使用,因为还有好的解决方法,比如再哈希法。
1.2 再哈希法
- 和第一个哈希函数不同;
- 不能输出0(否则没有步长,每次探索都是原地踏步,算法将进入死循环)。
stepSize=constant-key%constant
其中 constant 是质数,且小于数组容量。
再哈希法要求表的容量是一个质数,假如表长度为15(0-14),非质数,有一个特定关键字映射到0,步长为5,则探测序列是 0,5,10,0,5,10,以此类推一直循环下去。算法只尝试这三个单元,所以不可能找到某些空白单元,最终算法导致崩溃。如果数组容量为13, 质数,探测序列最终会访问所有单元。即 0,5,10,2,7,12,4,9,1,6,11,3,一直下去,只要表中有一个空位,就可以探测到它。下面看看再哈希法的代码:
(左右滑动查看完整代码~)
public class HashDouble {private DataItem[] hashArray;private int arraySize;private int itemNum;private DataItem nonItem;public HashDouble() {arraySize = 13;hashArray = new DataItem[arraySize];nonItem = new DataItem(-1);}public void displayTable() {System.out.print("Table:");for(int i = 0; i < arraySize; i++) {if(hashArray[i] != null) {System.out.print(hashArray[i].getKey() + " ");}else {System.out.print("** ");}}System.out.println("");}public int hashFunction1(int key) { //first hash functionreturn key % arraySize;}public int hashFunction2(int key) { //second hash functionreturn 5 - key % 5;}public boolean isFull() {return (itemNum == arraySize);}public boolean isEmpty() {return (itemNum == 0);}public void insert(DataItem item) {if(isFull()) {System.out.println("哈希表已满,重新哈希化..");extendHashTable();}int key = item.getKey();int hashVal = hashFunction1(key);int stepSize = hashFunction2(key); //用hashFunction2计算探测步数while(hashArray[hashVal] != null && hashArray[hashVal].getKey() != -1) {hashVal += stepSize;hashVal %= arraySize; //以指定的步数向后探测}hashArray[hashVal] = item;itemNum++;}public void extendHashTable() {int num = arraySize;itemNum = 0; //重新记数,因为下面要把原来的数据转移到新的扩张的数组中arraySize *= 2; //数组大小翻倍DataItem[] oldHashArray = hashArray;hashArray = new DataItem[arraySize];for(int i = 0; i < num; i++) {insert(oldHashArray[i]);}}public DataItem delete(int key) {if(isEmpty()) {System.out.println("Hash table is empty!");return null;}int hashVal = hashFunction1(key);int stepSize = hashFunction2(key);while(hashArray[hashVal] != null) {if(hashArray[hashVal].getKey() == key) {DataItem temp = hashArray[hashVal];hashArray[hashVal] = nonItem;itemNum--;return temp;}hashVal += stepSize;hashVal %= arraySize;}return null;}public DataItem find(int key) {int hashVal = hashFunction1(key);int stepSize = hashFunction2(key);while(hashArray[hashVal] != null) {if(hashArray[hashVal].getKey() == key) {return hashArray[hashVal];}hashVal += stepSize;hashVal %= arraySize;}return null;}}
2. 链地址法
public class HashChain {private SortedList[] hashArray; //数组中存放链表private int arraySize;public HashChain(int size) {arraySize = size;hashArray = new SortedList[arraySize];//new出每个空链表初始化数组for(int i = 0; i < arraySize; i++) {hashArray[i] = new SortedList();}}public void displayTable() {for(int i = 0; i < arraySize; i++) {System.out.print(i + ": ");hashArray[i].displayList();}}public int hashFunction(int key) {return key % arraySize;}public void insert(LinkNode node) {int key = node.getKey();int hashVal = hashFunction(key);hashArray[hashVal].insert(node); //直接往链表中添加即可}public LinkNode delete(int key) {int hashVal = hashFunction(key);LinkNode temp = find(key);hashArray[hashVal].delete(key);//从链表中找到要删除的数据项,直接删除return temp;}public LinkNode find(int key) {int hashVal = hashFunction(key);LinkNode node = hashArray[hashVal].find(key);return node;}}
public class SortedList {private LinkNode first;public SortedList() {first = null;}public boolean isEmpty() {return (first == null);}public void insert(LinkNode node) {int key = node.getKey();LinkNode previous = null;LinkNode current = first;while(current != null && current.getKey() < key) {previous = current;current = current.next;}if(previous == null) {first = node;}else {node.next = current;previous.next = node;}}public void delete(int key) {LinkNode previous = null;LinkNode current = first;if(isEmpty()) {System.out.println("chain is empty!");return;}while(current != null && current.getKey() != key) {previous = current;current = current.next;}if(previous == null) {first = first.next;}else {previous.next = current.next;}}public LinkNode find(int key) {LinkNode current = first;while(current != null && current.getKey() <= key) {if(current.getKey() == key) {return current;}current = current.next;}return null;}public void displayList() {System.out.print("List(First->Last):");LinkNode current = first;while(current != null) {current.displayLink();current = current.next;}System.out.println("");}}class LinkNode {private int iData;public LinkNode next;public LinkNode(int data) {iData = data;}public int getKey() {return iData;}public void displayLink() {System.out.print(iData + " ");}}
THE END




