資料結構筆記:雜湊表(Hash Table)

更新於 發佈於 閱讀時間約 17 分鐘

軟體工程師職涯升級計畫啟動!立即預約職涯諮詢、履歷健檢或模擬面試👈,為您的加薪做好準備!


什麼是雜湊表?—— 鍵值對的抽象資料型態

想像一下,你有一本非常厚的通訊錄。如果這本通訊錄是按照每個人的姓名首字母排序的,當你想找「王小明」的電話時,你可以迅速翻到「W」的開頭,然後快速找到他。雜湊表就是這樣的概念,它是一種以「鍵值-資料對(Key-Value Pair)」來描述資料的抽象資料型態(Abstract Data Type, ADT)

換句話說,雜湊表的核心概念就是:你提供一個獨特的鍵(Key),雜湊表就能夠迅速幫你找到與之相對應的值(Value)。這個過程就像給每個資料一個獨特的「標籤」,然後透過這個標籤直接找到資料本身。

raw-image

雜湊表的魔力:極致的搜尋速度

雜湊表之所以被廣泛應用於各種系統中,是因為它擁有一個殺手級的特性:

  • 極高的搜尋、新增、刪除速度: 在理想情況下,雜湊表的操作時間複雜度都趨近於 O(1)
  • 鍵(Key)的唯一性: 雜湊表中,每個鍵都必須是唯一的。這保證了你可以透過一個鍵準確地找到一個唯一的值。如果試圖用相同的鍵新增不同的值,通常會覆蓋舊的值或拋出錯誤,這取決於雜湊表的具體實現。

核心原理:雜湊函式

雜湊表之所以能實現 O(1) 的平均時間複雜度,關鍵在於雜湊函式(Hash Function)。雜湊函式是一個將任意大小的輸入資料(鍵)轉換成一個固定大小的數字(稱為雜湊值雜湊碼)的演算法。這個雜湊值通常會被用作資料在內部陣列中的索引。

運作流程:

  1. 資料新增 (Insert):
    • 將要儲存的**鍵(Key)**傳入雜湊函式。
    • 雜湊函式計算出一個雜湊值(Hash Value)
    • 將這個雜湊值作為內部陣列的索引(Index),把**值(Value)**儲存在該索引位置。
  2. 資料查詢 (Search):
    • 將要查詢的**鍵(Key)**傳入相同的雜湊函式。
    • 雜湊函式計算出相同的雜湊值
    • 利用這個雜湊值作為索引,直接從內部陣列中取出對應的值(Value)
  3. 資料刪除 (Delete):
    • 類似查詢操作,透過雜湊函式找到對應的索引,然後從該位置移除資料。

實作雜湊表:方法與 JavaScript 範例

讓我們用 JavaScript 來簡單模擬雜湊表的實現。在 JavaScript 中,原生的 ObjectMap 類型其實就是雜湊表的實現。

使用 JavaScript Object (物件) 模擬

JavaScript 的物件本身就是一個鍵值對的集合,非常類似於雜湊表。鍵會被轉換為字串,用作物件的屬性名。

JavaScript

class SimpleHashTable {
constructor() {
this.data = {}; // 使用 JS 物件作為內部儲存
}

/**
* 新增資料到雜湊表
* @param {string} key - 唯一的鍵
* @param {*} value - 要儲存的值
*/
insert(key, value) {
if (this.data.hasOwnProperty(key)) {
console.warn(`Key "${key}" already exists. Overwriting value.`);
}
this.data[key] = value; // 直接用鍵作為屬性名
console.log(`Inserted: ${key} -> ${value}`);
}

/**
* 查詢雜湊表中的資料
* @param {string} key - 要查詢的鍵
* @returns {*} - 鍵對應的值,如果找不到則返回 undefined
*/
search(key) {
if (this.data.hasOwnProperty(key)) {
console.log(`Found: ${key} -> ${this.data[key]}`);
return this.data[key];
} else {
console.log(`Key "${key}" not found.`);
return undefined;
}
}

/**
* 從雜湊表中刪除資料
* @param {string} key - 要刪除的鍵
* @returns {boolean} - 如果成功刪除則返回 true,否則返回 false
*/
delete(key) {
if (this.data.hasOwnProperty(key)) {
const deletedValue = this.data[key];
delete this.data[key]; // 使用 delete 操作符移除屬性
console.log(`Deleted: ${key} -> ${deletedValue}`);
return true;
} else {
console.log(`Key "${key}" not found. Nothing to delete.`);
return false;
}
}

/**
* 檢查雜湊表是否包含某個鍵
* @param {string} key - 要檢查的鍵
* @returns {boolean} - 如果包含則返回 true,否則返回 false
*/
has(key) {
return this.data.hasOwnProperty(key);
}

/**
* 獲取所有鍵
* @returns {Array<string>} - 雜湊表中所有的鍵
*/
keys() {
return Object.keys(this.data);
}

/**
* 獲取所有值
* @returns {Array<*>} - 雜湊表中所有的值
*/
values() {
return Object.values(this.data);
}
}

// --- 使用範例 ---
console.log("--- 使用 SimpleHashTable ---");
const myHashTable = new SimpleHashTable();

myHashTable.insert("name", "Alice");
myHashTable.insert("age", 30);
myHashTable.insert("city", "New York");
myHashTable.insert("occupation", "Engineer");

myHashTable.search("name"); // Found: name -> Alice
myHashTable.search("age"); // Found: age -> 30
myHashTable.search("country"); // Key "country" not found.

console.log("Does it have 'city'?", myHashTable.has("city")); // true
console.log("All keys:", myHashTable.keys()); // ["name", "age", "city", "occupation"]

myHashTable.delete("age"); // Deleted: age -> 30
myHashTable.search("age"); // Key "age" not found.

myHashTable.insert("name", "Bob"); // Key "name" already exists. Overwriting value.
myHashTable.search("name"); // Found: name -> Bob

程式碼說明:

  • SimpleHashTable 類別內部使用一個普通的 JavaScript 物件 this.data 來儲存鍵值對。
  • insert() 方法直接將鍵作為屬性名,值作為屬性值儲存。
  • search() 方法透過鍵直接存取物件屬性。
  • delete() 方法使用 delete 運算符移除屬性。
  • hasOwnProperty() 用於判斷物件自身是否包含指定屬性,避免查詢到原型鏈上的屬性。

更原生的方式:JavaScript Map (映射)

從 ES6 開始,JavaScript 引入了 Map 物件,它提供了更完善的鍵值對儲存功能,且允許任何型別的值作為鍵(而不僅僅是字串,這是 Object 的限制)。Map 在內部實現上,也更接近於一個高效的雜湊表。

JavaScript

console.log("\n--- 使用原生的 JavaScript Map ---");
const myMap = new Map();

// 新增資料 (set)
myMap.set("name", "Charlie");
myMap.set(123, "Number Key");
myMap.set(true, "Boolean Key");

console.log("Map size:", myMap.size); // 3

// 查詢資料 (get)
console.log("Name:", myMap.get("name")); // Charlie
console.log("Number Key:", myMap.get(123)); // Number Key
console.log("Non-existent key:", myMap.get("address")); // undefined

// 檢查鍵是否存在 (has)
console.log("Has 'true' key?", myMap.has(true)); // true
console.log("Has 'false' key?", myMap.has(false)); // false

// 刪除資料 (delete)
myMap.delete(123);
console.log("Map size after delete:", myMap.size); // 2
console.log("Number Key after delete:", myMap.get(123)); // undefined

// 再次新增相同鍵會覆蓋
myMap.set("name", "David");
console.log("Updated name:", myMap.get("name")); // David

// 遍歷 Map
console.log("Iterating over Map entries:");
for (const [key, value] of myMap) {
console.log(`${key} -> ${value}`);
}
// Output:
// name -> David
// true -> Boolean Key

// 清空 Map (clear)
myMap.clear();
console.log("Map size after clear:", myMap.size); // 0

程式碼說明:

  • Map 提供了 set()get()has()delete() 等直觀的方法,功能更強大、更符合雜湊表的抽象。
  • Map 允許使用非字串型別作為鍵,例如數字、布林值甚至其他物件。
  • Map 保證了鍵的插入順序,這在 Object 中是不保證的(儘管現代 JavaScript 引擎通常會保留數值和符號鍵的插入順序)。

雜湊衝突與解決方案

儘管雜湊表在理想情況下能達到 O(1) 的效率,但在實際應用中,不同的鍵經過雜湊函式計算後,可能會得到相同的雜湊值,這就是所謂的雜湊衝突(Hash Collision)

想像一下,你的通訊錄裡有兩個人,一個叫「王力宏」,一個叫「王麗紅」,它們的首字母都是「W」,當你翻到「W」的頁面時,你會發現兩個人都在。這就是一個簡化的雜湊衝突。

有兩種主要的方法來解決雜湊衝突:

  1. 鏈式串接(Chaining):
    • 在每個雜湊值對應的儲存位置,不直接儲存值,而是儲存一個鏈結串列(Linked List)或陣列。當發生衝突時,新的鍵值對會被添加到該鏈結串列的末尾。
    • 優點: 實現相對簡單,對雜湊函式的質量要求不高。
    • 缺點: 當衝突過多時,鏈結串列會變長,導致搜尋時間退化到 O(n)(最壞情況)。
    • 範例: 想像一個百貨公司的停車場,每個樓層(雜湊值)有許多停車位(鏈結串列)。即使同一樓層的停車位都滿了,你還是可以在同一樓層找到另一個空位,但可能要找很久。
  2. 開放定址法(Open Addressing):
    • 當發生衝突時,雜湊表會嘗試在其他位置尋找下一個可用的空位來儲存資料。常見的策略有:
      • 線性探測(Linear Probing): 依序檢查下一個位置,直到找到空位。
      • 二次探測(Quadratic Probing): 以平方增長的方式跳躍尋找空位。
      • 雙重雜湊(Double Hashing): 使用第二個雜湊函式來計算探測的步長。
    • 優點: 不需要額外的儲存空間來儲存鏈結串列,快取效率較高。
    • 缺點: 實現較複雜,容易出現**叢集(Clustering)**現象(已佔用的區塊聚集),影響性能;需要處理表格滿載時的擴容問題。
    • 範例: 想像一個百貨公司的立體停車塔,當目標停車位被佔用時,系統會自動幫你找附近的其他空位。

良好雜湊函式的重要性

為了最大程度地減少雜湊衝突並維持雜湊表的高效率,一個好的雜湊函式至關重要。好的雜湊函式應該具備以下特點:

  • 快速計算: 函式本身的計算應該非常快速。
  • 均勻分布: 能夠將不同的輸入鍵均勻地映射到雜湊表的各個位置,減少衝突的發生。
  • 一致性: 對於相同的輸入鍵,雜湊函式必須始終產生相同的雜湊值。

總結:雜湊表——高效數據管理的利器

雜湊表是一個強大且用途廣泛的資料結構,因其出色的平均時間複雜度而成為許多應用程式的核心組件。從資料庫索引、快取系統、程式語言的字典/物件實現,到網路路由和密碼學,雜湊表無處不在。

理解雜湊表的運作原理、特性以及如何處理雜湊衝突,對於任何想要深入學習資料結構和演算法的開發者來說都至關重要。透過 JavaScript 的物件和 Map 範例,我們可以看到這種高效資料結構在實際程式設計中的應用。

留言
avatar-img
留言分享你的想法!
avatar-img
跨越國界的程式人生
2會員
33內容數
自學程式,現為網頁開發工程師,同時擔任線上課程講師,專注於幫助自學程式的開發者找到理想工作。熱愛技術與分享,致力於將複雜的概念轉化為實用知識,讓更多人踏入軟體開發的世界。
2025/07/22
如果你正在規劃出國工作,那麼今天的這篇文章,絕對能給你一些扎實的參考資訊。我們要來聊聊一個近年來在台灣人圈子裡越來越受歡迎的歐洲工作目的地——愛爾蘭!🇮🇪
Thumbnail
2025/07/22
如果你正在規劃出國工作,那麼今天的這篇文章,絕對能給你一些扎實的參考資訊。我們要來聊聊一個近年來在台灣人圈子裡越來越受歡迎的歐洲工作目的地——愛爾蘭!🇮🇪
Thumbnail
2025/07/22
在資料結構中,除了後進先出的 Stack,另一個常見且實用的就是 Queue(佇列)。它是許多系統背後默默運作的重要機制,例如排程器、印表機佇列、網路封包、事件處理機制等。 這篇筆記將帶你從零理解 Que
Thumbnail
2025/07/22
在資料結構中,除了後進先出的 Stack,另一個常見且實用的就是 Queue(佇列)。它是許多系統背後默默運作的重要機制,例如排程器、印表機佇列、網路封包、事件處理機制等。 這篇筆記將帶你從零理解 Que
Thumbnail
2025/07/22
這篇文章深入淺出地介紹堆疊(Stack)這種資料結構,包含其定義、特性、操作流程、JavaScript實作範例、應用場景(例如:回溯、瀏覽器上一頁功能、深度優先搜尋等),以及LeetCode相關題目。文末提供延伸學習資源,並鼓勵讀者提出學習需求。
Thumbnail
2025/07/22
這篇文章深入淺出地介紹堆疊(Stack)這種資料結構,包含其定義、特性、操作流程、JavaScript實作範例、應用場景(例如:回溯、瀏覽器上一頁功能、深度優先搜尋等),以及LeetCode相關題目。文末提供延伸學習資源,並鼓勵讀者提出學習需求。
Thumbnail
看更多
你可能也想看
Thumbnail
2025 vocus 推出最受矚目的活動之一——《開箱你的美好生活》,我們跟著創作者一起「開箱」各種故事、景點、餐廳、超值好物⋯⋯甚至那些讓人會心一笑的生活小廢物;這次活動不僅送出了許多獎勵,也反映了「內容有價」——創作不只是分享、紀錄,也能用各種不同形式變現、帶來實際收入。
Thumbnail
2025 vocus 推出最受矚目的活動之一——《開箱你的美好生活》,我們跟著創作者一起「開箱」各種故事、景點、餐廳、超值好物⋯⋯甚至那些讓人會心一笑的生活小廢物;這次活動不僅送出了許多獎勵,也反映了「內容有價」——創作不只是分享、紀錄,也能用各種不同形式變現、帶來實際收入。
Thumbnail
高效生活,幫助你找回更多自己的時間 歡迎來到 AL 的 Googlesheet 學習筆記系列文章。在這個系列中,我們將一步步介紹各種函數,並將它們應用於日常生活中,加速工作、提高效率。 今天要介紹的是使用 Index 、 Counta 函數尋找最後一列的資料!
Thumbnail
高效生活,幫助你找回更多自己的時間 歡迎來到 AL 的 Googlesheet 學習筆記系列文章。在這個系列中,我們將一步步介紹各種函數,並將它們應用於日常生活中,加速工作、提高效率。 今天要介紹的是使用 Index 、 Counta 函數尋找最後一列的資料!
Thumbnail
給定一個字串陣列,請把它們所共有的字元伴隨著出現次數輸出。這篇文章介紹如何使用字典統計出現次數,和字典取交集的方法來解決此問題。並提供了複雜度分析和關鍵知識點。
Thumbnail
給定一個字串陣列,請把它們所共有的字元伴隨著出現次數輸出。這篇文章介紹如何使用字典統計出現次數,和字典取交集的方法來解決此問題。並提供了複雜度分析和關鍵知識點。
Thumbnail
這邊統整了過往喜特先生發布過的「資料驗證」系列文! 資料驗證是個「驗證資料是否符合某條件的機制」,我們通常會用它來避免別人輸入無效的值,減少錯誤的發生。你可以按照順序慢慢學習,把資料驗證這功能一次搞懂!
Thumbnail
這邊統整了過往喜特先生發布過的「資料驗證」系列文! 資料驗證是個「驗證資料是否符合某條件的機制」,我們通常會用它來避免別人輸入無效的值,減少錯誤的發生。你可以按照順序慢慢學習,把資料驗證這功能一次搞懂!
Thumbnail
計數原理的題目們,以及上次排組的解析。
Thumbnail
計數原理的題目們,以及上次排組的解析。
Thumbnail
我捨棄了編號系統,解放三倍大腦思考能量
Thumbnail
我捨棄了編號系統,解放三倍大腦思考能量
Thumbnail
目錄 序 導論: 一個西方觀點的評述 1.0 從函數到函數算法 ......1.1 句子成份
Thumbnail
目錄 序 導論: 一個西方觀點的評述 1.0 從函數到函數算法 ......1.1 句子成份
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News