哈希表是計算機科學中常用的數據結構之一,它提供了快速的查找、插入和刪除操作。在本篇博客中,我們將探討如何構造一個高效的哈希表,從最基本的思路逐步完善,直至最終實現。
1. 初始思路:使用布爾數組存儲
我們最初的想法是使用一個布爾數組來存儲哈希表的元素。具體來說,數組中的每個元素代表一個可能的鍵值,如果某個鍵存在于哈希表中,則將對應位置的布爾值設為true
,否則設為false
。這種方法的代碼如下:
public class SimpleHashSet {private boolean[] present;public SimpleHashSet() {present = new boolean[2000000000];}public void add(int key) {present[key] = true;}public boolean contains(int key) {return present[key];}
}
這種方法的問題在于,它會浪費大量的內存空間,尤其是當哈希表中只有少量元素時。此外,它只能處理整數類型的鍵,無法處理其他類型的數據。
2. 使用哈希函數解決鍵的類型問題
為了解決只能處理整數類型鍵的問題,我們可以引入哈希函數將其他類型的鍵轉換為整數。下面是一個將字符串轉換為整數的示例:
public class DataIndexedEnglishWordSet {private boolean[] present;public DataIndexedEnglishWordSet() {present = new boolean[2000000000];}public void add(String key) {present[englishToInt(key)] = true;}public boolean contains(String key) {return present[englishToInt(key)];}private static int letterNum(String s, int i) {int ithChar = s.charAt(i);if (ithChar < 'a' || ithChar > 'z') {throw new IllegalArgumentException();}return ithChar - 'a' + 1;}private static int englishToInt(String s) {int intRep = 0;for (int i = 0; i < s.length(); i++) {intRep = intRep * 26;intRep += letterNum(s, i);}return intRep;}
}
雖然現在我們可以處理字符串類型的鍵,但仍然存在兩個主要問題:內存浪費和處理哈希沖突的困難。
3. 引入開放地址法解決沖突
為了解決哈希沖突的問題,我們引入了開放地址法中的線性探查。當插入的位置已被占用時,通過順序檢查數組的下一個位置來解決沖突。
public class DataIndexedEnglishWordSet {private boolean[] present;public DataIndexedEnglishWordSet() {present = new boolean[2000000000];}public void add(String key) {int index = englishToInt(key);while (present[index] != false) {index = (index + 1) % present.length;}present[index] = true;}public boolean contains(String key) {int index = englishToInt(key);while (present[index] != false) {if (present[index]) {return true;}index = (index + 1) % present.length;}return false;}private static int letterNum(String s, int i) {int ithChar = s.charAt(i);if (ithChar < 'a' || ithChar > 'z') {throw new IllegalArgumentException();}return ithChar - 'a' + 1;}private static int englishToInt(String s) {int intRep = 0;for (int i = 0; i < s.length(); i++) {intRep = intRep * 26;intRep += letterNum(s, i);}return intRep;}
}
通過引入線性探查,我們可以更有效地處理哈希沖突,提高了哈希表的性能。
4. 使用取模操作減少空間浪費
為了減少空間浪費,我們修改了哈希函數,使用取模操作將鍵映射到數組索引上。
public class DataIndexedEnglishWordSet {private boolean[] present;public DataIndexedEnglishWordSet() {present = new boolean[101]; // 使用最小素數作為初始容量}public void add(String key) {int index = englishToInt(key);while (present[index] != false) {index = (index + 1) % present.length;}present[index] = true;}public boolean contains(String key) {int index = englishToInt(key);while (present[index] != false) {if (present[index]) {return true;}index = (index + 1) % present.length;}return false;}private static int letterNum(String s, int i) {int ithChar = s.charAt(i);if (ithChar < 'a' || ithChar > 'z') {throw new IllegalArgumentException();}return ithChar - 'a' + 1;}private int englishToInt(String s) {int intRep = 0;for (int i = 0; i < s.length(); i++) {intRep = intRep * 26;intRep += letterNum(s, i);}return intRep % present.length; // 修改為取模操作}
}
通過取模操作,我們將鍵均勻地映射到數組索引上,減少了空間浪費。
5. 動態更改哈希表大小以優化性能
為了進一步優化性能,我們添加了動態調整哈希表大小的功能。當負載因子超過閾值時,我們將重構哈希表并擴大其容量。
public class ImprovedHashSet {private boolean[] present;private int size;private static final float LOAD_FACTOR_THRESHOLD = 0.75f;public ImprovedHashSet() {present = new boolean[101];size = 0;}public void add(String key) {if (loadFactor() >= LOAD_FACTOR_THRESHOLD) {resize();}int index = englishToInt(key);while (present[index] != false) {index = (index + 1) % present.length;}present[index] = true;size++;}public boolean contains(String key) {int index = englishToInt(key);while (present[index] != false) {if (present[index]) {return true;}index = (index + 1) % present.length;}return false;}private float loadFactor() {return (float) size / present.length;}private void resize() {boolean[] oldPresent = present;int newCapacity = nextPrime(oldPresent.length * 2);present = new boolean[newCapacity];size = 0;for (boolean value : oldPresent) {if (value) {add(someKey); // 重新插入元素,假設someKey為原哈希表的所有鍵}}}private int nextPrime(int n) {while (true) {if (isPrime(n)) {return n;}n++;}}private boolean isPrime(int n) {if (n <= 1) {return false;}if (n <= 3) {return true;}if (n % 2 == 0 || n % 3 == 0) {return false;}int i = 5;while (i * i <= n) {if (n % i == 0 || n % (i + 2) == 0) {return false;}i += 6;}return true;}private static int letterNum(String s, int i) {int ithChar = s.charAt(i);if (ithChar < 'a' || ithChar > 'z') {throw new IllegalArgumentException();}return ithChar - 'a' + 1;}private int englishToInt(String s) {int intRep = 0;for (int i = 0; i < s.length(); i++) {intRep = intRep * 26;intRep += letterNum(s, i);}return intRep % present.length;}
}
通過動態調整哈希表大小,我們可以根據負載因子的變化來合理分配內存,提高了哈希表的效率和性能。
總結
在本篇博客中,我們從最基本的思路出發,逐步完善了一個高效的哈希表的實現。我們通過引入哈希函數解決鍵的類型問題,使用開放地址法解決沖突,通過取模操作減少空間浪費,并最終實現了動態更改哈希表大小以
優化性能。這些優化措施使得我們的哈希表在處理不同類型的鍵和不同規模的數據時都能保持高效運行。
希望通過本文的介紹,你對構造高效的哈希表有了更深入的理解,也能在實際應用中更好地利用哈希表這一重要的數據結構。