什麼是雜湊表。
雜湊表是一種資料結構,提供快速的插入和尋找操作。
優點:
插入、尋找、刪除的時間級為O(1); 資料項目占雜湊表長的一半,或者三分之二時,雜湊表的效能最好。
缺點: 基於數組,數組建立後難於擴充,某些雜湊表被基本填滿時效能下降的非常嚴重; 沒有一種簡單的方法可以以任何一種順序(如從小到大)遍曆整個資料項目;
用途: 不需要遍曆資料並且可以提前預測資料量的大小,此時雜湊表的速度和易用性無與倫比。 雜湊化
就是把關鍵字轉化為數組的下標,在雜湊表中通過雜湊函數來完成。對於某些關鍵字並不需要雜湊函數進行雜湊化,如員工編號。
雜湊函數:作用是將大的多整數範圍轉化為數組的下標範圍。一般通過取餘%來完成。key%arraySize。
衝突:雜湊函數不能保證每個每個關鍵字都映射到數組的空白元素的位置。解決衝突的方法分為:開放地址法和鏈地址法。 開放地址法
若資料不能直接放在由雜湊Function Compute出來的數組下標所致的單元時,就要尋找數組的其他位置。該方法會發生聚集,那些雜湊化後的落在聚集內的資料項目,都要一步一步的移動,並且插入在聚集的最後,因此使聚集越來越大。聚集越大,它增長的越快。 線性探測
線上性探測中,線性尋找空白單元,如52的位置被佔用了,就找53、54。。。的位置直到找到空位。
聚集:一串聯續的已填充單元叫做以數列填滿。增加越來越多的資料項目時,以數列填滿變的越來越長,這就叫聚集。
如果把雜湊表填的太滿,那麼在表中每填一個資料都要花費很長的時間。
package cn.xyc.dataStructure.hash;/** * * 描述:雜湊表中儲存的資料項目 * * <pre> * HISTORY * **************************************************************************** * ID DATE PERSON REASON * 1 2016年10月2日 80002253 Create * **************************************************************************** * </pre> * * @author 蒙奇·d·許 * @since 1.0 */public class DataItem {private int item;// 資料項目keypublic DataItem(int item) {this.item = item;}public int getKey() {return item;}}
package cn.xyc.dataStructure.hash;/** * * 描述:使用線性探測法實現雜湊表 * * <pre> * HISTORY * **************************************************************************** * ID DATE PERSON REASON * 1 2016年10月2日 80002253 Create * **************************************************************************** * </pre> * * @author 蒙奇·d·許 * @since 1.0 */public class Hash {// 儲存雜湊表中的資料private DataItem[] hashArray;// 雜湊表的長度private int arraySize;// 可以被刪除的雜湊項private DataItem nonItem;/** * 初始化雜湊表 * * @param size */public Hash(int size) {arraySize = size;hashArray = new DataItem[arraySize];nonItem = new DataItem(-1);// 被刪除的項為-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() + "\t");} else {System.out.print("**\t");}}System.out.println("");}// //////////////////////////////////////////////////////** * 雜湊函數 * * @param key * @return */public int hashFunc(int key) {return key % arraySize;}// //////////////////////////////////////////////////////public void insert(DataItem item) {int key = item.getKey();int hashVal = hashFunc(key);while (hashArray[hashVal] != null && hashArray[hashVal].getKey() != -1) {++hashVal;// 直到找到空白元素hashVal %= arraySize;// 保證不越界}// 插入資料hashArray[hashVal] = item;}// //////////////////////////////////////////////public DataItem delete(int key) {int hashVal = hashFunc(key);while (hashArray[hashVal] != null) {if (hashArray[hashVal].getKey() == key) {DataItem temp = hashArray[hashVal];hashArray[hashVal] = nonItem;return temp;}++hashVal;hashVal %= arraySize;}return null;}// ////////////////////////////////////////////public DataItem find(int key) {int hashVal = hashFunc(key);while (hashArray[hashVal] != null) {if (hashArray[hashVal].getKey() == key) {return hashArray[hashVal];} else {++hashVal;hashVal %= arraySize;}}return null;}// ///////////////////////////////////////////////////////** * 得到一個長度為質數的數組長度 * * @param min * @return */@SuppressWarnings("unused")private int getPrime(int min) {for (int i = 0; true; i++) {if (isPrime(i)) {return i;}}}// ////////////////////////////////////** * 判斷一個數是否為質數 * * @param n * @return */private boolean isPrime(int n) {for (int i = 2; i * i < n; i++) {if (n % i == 0) {return false;}}return true;}}
擴充數組
雜湊函數是根據數組的大小來計算給定資料項目的大小的,所以不能簡單的從一個數組向另一個數組拷貝資料。需要按照遍曆老數組,用insert方法向新數組中插入每個資料項目。
二次探測
二次探測試防止聚集產生的一種嘗試,思想史探測相隔較遠的單元,而不是和原始位置相鄰的單元。
步驟是是步數的平方:x+1^2、x+2^2、x+3^2,而不是x+1、x+2。
但是當二次探測的搜尋變長時,好像它變得越來越絕望,因為步長平方級增長,可能會飛出整個空間。造成二次聚集。
再雜湊法
是一種依賴關鍵字的探測序列,而不是每個關鍵字都是一樣的。
二次聚集產生的原因是,二次探測的演算法產生的探測序列步長總是固定的1,4,9,16.
方法是:把關鍵字用不同的雜湊函數再雜湊一遍,用這個結果作為步長,對指定的關鍵字,步長在整個探測中是不變的,不過不同的關鍵字使用不同的步長。
第二個雜湊函數的特點: 和第一個雜湊函數不同。 不能輸出0(否則將沒有步長:每次探測都是原地踏步,演算法將陷入死迴圈)。
專家發現下面形式的雜湊函數工作的非常好:
stepSize=constant-(key%constant);其中constant是質數,且小於數組容量。如stepSize=5-(key%5);
package cn.xyc.dataStructure.hash;/** * * 描述:使用再雜湊法實現雜湊表 <br/> * 要求:表的容量是一個質數,否則再雜湊時可能會取到0導致演算法崩潰。 * * <pre> * HISTORY * **************************************************************************** * ID DATE PERSON REASON * 1 2016年10月2日 80002253 Create * **************************************************************************** * </pre> * * @author 蒙奇·d·許 * @since 1.0 */public class HashDouble {// 儲存雜湊表中的資料private DataItem[] hashArray;// 雜湊表的長度private int arraySize;// 可以被刪除的雜湊項private DataItem nonItem;/** * 初始化雜湊表 * * @param size */public HashDouble(int size) {arraySize = size;hashArray = new DataItem[arraySize];nonItem = new DataItem(-1);// 被刪除的項為-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() + "\t");} else {System.out.print("**\t");}}System.out.println("");}// //////////////////////////////////////////////////////public void insert(DataItem item) {int key = item.getKey();int hashVal = hashFunc1(key);int stepSize = hashFunc2(key);while (hashArray[hashVal] != null && hashArray[hashVal].getKey() != -1) {hashVal += stepSize;// 直到找到空白元素hashVal %= arraySize;// 保證不越界}// 插入資料hashArray[hashVal] = item;}// //////////////////////////////////////////////public DataItem delete(int key) {int hashVal = hashFunc1(key);int stepSize = hashFunc2(key);while (hashArray[hashVal] != null) {if (hashArray[hashVal].getKey() == key) {DataItem temp = hashArray[hashVal];hashArray[hashVal] = nonItem;return temp;}hashVal += stepSize;hashVal %= arraySize;}return null;}// ////////////////////////////////////////////public DataItem find(int key) {int hashVal = hashFunc1(key);int stepSize = hashFunc2(key);while (hashArray[hashVal] != null) {if (hashArray[hashVal].getKey() == key) {return hashArray[hashVal];} else {hashVal += stepSize;hashVal %= arraySize;}}return null;}// ///////////////////////////////////////////////////////** * 得到一個長度為質數的數組長度 * * @param min * @return */@SuppressWarnings("unused")private int getPrime(int min) {for (int i = 0; true; i++) {if (isPrime(i)) {return i;}}}// //////////////////////////////////////////////////////** * 雜湊函數 * * @param key * @return */public int hashFunc1(int key) {return key % arraySize;}// //////////////////////////////////////////////////////** * 再雜湊函數:產生步長 * * @param key * @return */public int hashFunc2(int key) {// 再雜湊的餘數必須是一個小於數組長度,不和hF1一樣// 不能為為零// 是一個質數return 5 - (key % 5);}// ////////////////////////////////////** * 判斷一個數是否為質數 * * @param n * @return */private boolean isPrime(int n) {for (int i = 2; i * i < n; i++) {if (n % i == 0) {return false;}}return true;}}
鏈地址法
在雜湊表中每個單元中設定鏈表。某個資料項目的關鍵字還是像通常一樣映射到雜湊表的單元,而資料項目本身插入到這個單元的鏈表中。其他同樣映射到這個位置的資料項目只需要加入到鏈表中,不需要在原始數組中尋找空位。
優缺點:
有序鏈表不能加快成功的搜尋,但可以減少不成功搜尋中的一半的時間。
刪除的時間級也減少一半
插入的時間延長,因為資料項目不能只插在表頭;插入前必須找到有序表中的正確位置。
package cn.xyc.dataStructure.hash;/** * * 描述:鏈表中的節點 * * <pre> * HISTORY * **************************************************************************** * ID DATE PERSON REASON * 1 2016年10月2日 80002253 Create * **************************************************************************** * </pre> * * @author 蒙奇·D·許 * @since 1.0 */public class LinkItem {private int data;// 鏈表中的資料項目public LinkItem nextLink;// 下一個節點public LinkItem(int data) {this.data = data;}public int getKey() {return data;}public void dispalyLink() {System.out.println(data + "\t");}}
package cn.xyc.dataStructure.hash;/** * * 描述:從小到大的有序鏈表 * * <pre> * HISTORY * **************************************************************************** * ID DATE PERSON REASON * 1 2016年10月2日 80002253 Create * **************************************************************************** * </pre> * * @author 蒙奇·d·許 * @since 1.0 */public class SortLink {// 鏈表的第一個元素private LinkItem first;// ////////////////////////////////public SortLink() {first = null;}// /////////////////////////////////////public void insert(LinkItem theLink) {int key = theLink.getKey();LinkItem previousItem = null;// 從第一個開始LinkItem currentItem = first;while (currentItem != null && key > currentItem.getKey()) {previousItem = currentItem;currentItem = currentItem.nextLink;}if (previousItem == null) {first = theLink;} else {previousItem.nextLink = theLink;theLink.nextLink = currentItem;}}// /////////////////////////////////////////public void delete(int key) {LinkItem previous = null;LinkItem current = first;while (current != null && current.getKey() != key) {previous = current;current = current.nextLink;}if (previous == null) {// 說明是第一個if (first != null) {first = first.nextLink;}} else {previous.nextLink = current.nextLink;}}// ////////////////////////////////////////////////public LinkItem find(int key) {LinkItem currentItem = first;while (currentItem != null && currentItem.getKey() <= key) {if (currentItem.getKey() == key) {return currentItem;}currentItem = currentItem.nextLink;}return null;}///////////////////////////////////////////////public void displayList(){LinkItem currentItem=first;while(currentItem!=null){currentItem.dispalyLink();currentItem=currentItem.nextLink;}System.out.println();}}
package cn.xyc.dataStructure.hash;/** * * 描述:使用鏈地址法實現雜湊表 * * <pre> * HISTORY * **************************************************************************** * ID DATE PERSON REASON * 1 2016年10月2日 80002253 Create * **************************************************************************** * </pre> * * @author 蒙奇·d·許 * @since 1.0 */public class HashLink {// 儲存雜湊表中的資料private SortLink[] hashArray;// 雜湊表的長度private int arraySize;// 可以被刪除的雜湊項private SortLink nonItem;/** * 初始化雜湊表 * * @param size */public HashLink(int size) {arraySize = size;hashArray = new SortLink[arraySize];// 填充數組for (int i = 0; i < hashArray.length; i++) {hashArray[i] = new SortLink();}}// //////////////////////////////////////////////////////////** * 遍曆雜湊表 */public void displayTable() {System.out.print("Table: ");for (int i = 0; i < arraySize; i++) {if (hashArray[i] != null) {hashArray[i].displayList();} else {System.out.print("**\t");}}System.out.println("");}// //////////////////////////////////////////////////////** * 雜湊函數 * * @param key * @return */public int hashFunc(int key) {return key % arraySize;}// //////////////////////////////////////////////////////public void insert(LinkItem item) {int key = item.getKey();int hashVal = hashFunc(key);// 插入資料hashArray[hashVal].insert(item);}// //////////////////////////////////////////////public void delete(int key) {int hashVal = hashFunc(key);hashArray[hashVal].delete(key);}// ////////////////////////////////////////////public LinkItem find(int key) {int hashVal = hashFunc(key);LinkItem item = hashArray[hashVal].find(key);return item;}// ///////////////////////////////////////////////////////** * 得到一個長度為質數的數組長度 * * @param min * @return */@SuppressWarnings("unused")private int getPrime(int min) {for (int i = 0; true; i++) {if (isPrime(i)) {return i;}}}// ////////////////////////////////////** * 判斷一個數是否為質數 * * @param n * @return */private boolean isPrime(int n) {for (int i = 2; i * i < n; i++) {if (n % i == 0) {return false;}}return true;}}
桶
另一種方法類似於連結地址法,它是在雜湊表的每個單元中使用數組,而不是鏈表。這樣的數組稱為桶。
缺點:桶的容量太小會溢出,太大浪費空間。
開放地址法和鏈地址法的比較:
項數未知:使用鏈地址法。