我们也可以用类似的方法把表示单词唯一的数压缩成数组的下标:
arrayIndex = largerNumber % smallRange
这也就是哈希函数。它把一个大范围的数字哈希(转化)成一个小范围的数字,这个小范围的数对应着数组的下标。使用哈希函数向数组插入数据后,这个数组就是哈希表。
2、冲突把巨大的数字范围压缩到较小的数字范围,那么肯定会有几个不同的单词哈希化到同一个数组下标,即产生了冲突。
冲突可能会导致哈希化方案无法实施,前面我们说指定的数组范围大小是实际存储数据的两倍,因此可能有一半的空间是空着的,所以,当冲突产生时,一个方法是通过系统的方法找到数组的一个空位,并把这个单词填入,而不再用哈希函数得到数组的下标,这种方法称为开放地址法。比如加入单词 cats 哈希化的结果为5421,但是它的位置已经被单词parsnip占用了,那么我们会考虑将单词 cats 存放在parsnip后面的一个位置 5422 上。
另一种方法,前面我们也提到过,就是数组的每个数据项都创建一个子链表或子数组,那么数组内不直接存放单词,当产生冲突时,新的数据项直接存放到这个数组下标表示的链表中,这种方法称为链地址法。
3、开放地址法开发地址法中,若数据项不能直接存放在由哈希函数所计算出来的数组下标时,就要寻找其他的位置。分别有三种方法:线性探测、二次探测以及再哈希法。
①、线性探测在线性探测中,它会线性的查找空白单元。比如如果 5421 是要插入数据的位置,但是它已经被占用了,那么就使用5422,如果5422也被占用了,那么使用5423,以此类推,数组下标依次递增,直到找到空白的位置。这就叫做线性探测,因为它沿着数组下标一步一步顺序的查找空白单元。
完整代码:
package com.ys.hash; public class MyHashTable { private DataItem[] hashArray; //DataItem类,表示每个数据项信息 private int arraySize;//数组的初始大小 private int itemNum;//数组实际存储了多少项数据 private DataItem nonItem;//用于删除数据项 public MyHashTable(int arraySize){ this.arraySize = arraySize; hashArray = new DataItem[arraySize]; nonItem = new DataItem(-1);//删除的数据项下标为-1 } //判断数组是否存储满了 public boolean isFull(){ return (itemNum == arraySize); } //判断数组是否为空 public boolean isEmpty(){ return (itemNum == 0); } //打印数组内容 public void display(){ System.out.println("Table:"); for(int j = 0 ; j < arraySize ; j++){ if(hashArray[j] != null){ System.out.print(hashArray[j].getKey() + " "); }else{ System.out.print("** "); } } } //通过哈希函数转换得到数组下标 public int hashFunction(int key){ return key%arraySize; } //插入数据项 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为-1 itemNum--; 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; } public static class DataItem{ private int iData; public DataItem(int iData){ this.iData = iData; } public int getKey(){ return iData; } } }