https://labuladong.online/algo/data-structure-basic/hashmap-basic/
今天是學習的第 12 天,主要學習了哈希表核心原理:
哈希表可以理解為一個加強版的陣列,它通過哈希函式將任意類型的 key
映射到陣列索引,從而實現快速的資料存取。
table
)hash
)將 key
轉換為陣列索引 index
key
可以是數字、字串等多種類型// 哈希表偽程式碼邏輯
class MyHashMap {
constructor() {
this.table = new Array(1000).fill(null);
}
// 增/改,複雜度 O(1)
put(key, value) {
const index = this.hash(key);
this.table[index] = value;
}
// 查,複雜度 O(1)
get(key) {
const index = this.hash(key);
return this.table[index];
}
// 刪,複雜度 O(1)
remove(key) {
const index = this.hash(key);
this.table[index] = null;
}
// 哈希函式,把 key 轉化成 table 中的合法索引
// 時間複雜度必須是 O(1),才能保證上述方法的複雜度都是 O(1)
hash(key) {
// ...
}
}
哈希函式負責將任意長度的輸入(key)轉化為固定長度的輸出(索引)。
如果哈希函式的複雜度是 O(n),那麼整個哈希表的操作性能都會降至 O(n),失去哈希表的優勢。
當兩個不同的 key
通過哈希函式得到相同的索引時,就發生了哈希衝突。
當出現了哈希衝突,有兩種常見的解決方案:
key-value
對儲存在同一個鏈表中index + 1
開始尋找空位置負載因子衡量哈希表的填充程度,計算公式為:
負載因子 = size / table.length
size
:哈希表中 key-value
對的數量table.length
:底層陣列的容量擴容時機與影響
哈希表中鍵的遍歷順序是無序的,因為哈希表的遍歷本質上就是遍歷底層的 table
陣列:
// 哈希表底層的 table 陣列
KVNode[] table = new KVNode[1000];
// 獲取哈希表中的所有鍵
// 我們不能依賴這個 keys 列表的順序
List<KeyType> keys = new ArrayList<>();
for (int i = 0; i < table.length; i++) {
KVNode node = table[i];
if (node != null) {
keys.add(node.key);
}
}
key
在陣列中隨機分佈key
在不同容量下可能有不同索引table
陣列結構最佳實踐:避免在任何資料結構的遍歷過程中進行修改操作。
只有不可變型別才能作為哈希表的 key
。
key
的值改變,其哈希值也會改變// 可以把不可變型別當作 key
Map<String, AnyOtherType> map1 = new HashMap<>();
Map<Integer, AnyOtherType> map2 = new HashMap<>();
// 不應該把可變類型當作 key
// 注意,這樣寫並不會產生語法錯誤,但程式碼非常容易出 bug
Map<ArrayList<Integer>, AnyOtherType> map3 = new HashMap<>();