hash table visualization with keys and values

Python 資料結構:Hash Map(雜湊映射)(06)

Hash Map 是以「鍵—值(key–value)」方式儲存資料、並能在平均 O(1) 時間完成查找/插入/更新的資料結構。它以陣列為底、靠 Hash Function(雜湊函數) 將鍵轉為索引,進而定位到資料所在位置。本文帶你從概念到實作,完整掌握 Hash Map 的運作與 Python 實作要點。


為何需要映射(Mapping)

許多資料更適合以「關聯」而非「順序」來表達:

  • 例如「州 → 州花」、「使用者帳號 → 使用者資訊」。
  • 映射中,每個「鍵」對應唯一的「值」(一對一關係)。
  • 查找的重點在「給定鍵,快速得到值」,而非依序遍歷。

hash table visualization with keys and values

Hash Function 的角色

Hash Map 的核心在於:把鍵轉成陣列索引。流程如下:

  1. 將鍵(如字串)餵給 hash function,得到一個整數 hash code
  2. 將 hash code 經由 壓縮函數(compression) 取模至 [0, array_size),得到合法索引。

設計考量

  • 輸入:通常為字串或任意型別(會先轉為位元組/整數序列)。
  • 輸出:陣列索引(整數)。
  • 效率:計算必須簡單快速(否則會拖累整體效能)。
  • 不可逆:雜湊輸出資訊量少於輸入,本質不可逆。

小提醒:壓縮函數常用「hash_code % array_size」。良好的 hash function 能讓鍵分布均勻,減少碰撞。


programmer writing hash function code

碰撞(Collision)與解法

碰撞:不同鍵可能被映射到相同索引。這是無可避免的,因此需要策略處理。

1) Separate Chaining(分離鏈結)

  • 陣列每格放的不是單一值,而是容器(常見:鏈結串列)。
  • 同索引的多個鍵值對,串在同一條鏈上。
  • 優點:插入簡單,陣列不用搬動。
  • 缺點:碰撞多時,鏈會變長,查找成本接近 O(n)。

2) Open Addressing(開放定址)

  • 仍只用陣列;若索引衝突,就**探測(Probing)**下一個位置直到找到空位。
  • Linear Probing:每次往後 +1(或固定步長)。
  • Quadratic Probing:依序 +1、+4、+9…(平方數),較能減緩群聚(clustering)。

多數教材都會介紹以上兩種。工業界實作還會考慮負載因子(load factor)動態擴容(rehash & resize)等。


Python 與 Hash Map

實務上,Python 內建的 dict 就是高度最佳化的 Hash Table。
為了理解原理,下面示範Hash Map(以 list 模擬陣列,採 open addressing / linear probing)。

建構子:配置陣列

class HashMap:
    def __init__(self, array_size):
        self.array_size = array_size
        self.array = [None for _ in range(array_size)]

Hash 與 Compression

class HashMap:
    def __init__(self, array_size):
        self.array_size = array_size
        self.array = [None for _ in range(array_size)]

    def hash(self, key, count_collisions=0):
        key_bytes = key.encode()
        hash_code = sum(key_bytes)
        # 加上碰撞次數,支援 probing
        return hash_code + count_collisions

    def compressor(self, hash_code):
        return hash_code % self.array_size

指派(Setter):處理碰撞(Linear Probing)

  • 陣列每格存 [key, value],以利檢查是否同鍵或不同鍵。
    def assign(self, key, value):
        array_index = self.compressor(self.hash(key))
        current = self.array[array_index]

        # 空位:直接放入
        if current is None:
            self.array[array_index] = [key, value]
            return

        # 同鍵:覆蓋
        if current[0] == key:
            self.array[array_index] = [key, value]
            return

        # 不同鍵:開放定址(線性探測)
        collisions = 1
        while True:
            new_index = self.compressor(self.hash(key, collisions))
            current = self.array[new_index]

            if current is None:
                self.array[new_index] = [key, value]
                return

            if current[0] == key:
                self.array[new_index] = [key, value]
                return

            collisions += 1

取值(Getter):同樣需探測

    def retrieve(self, key):
        array_index = self.compressor(self.hash(key))
        current = self.array[array_index]

        if current is None:
            return None
        if current[0] == key:
            return current[1]

        # 不同鍵:沿相同探測序列繼續找
        collisions = 1
        while True:
            new_index = self.compressor(self.hash(key, collisions))
            current = self.array[new_index]

            if current is None:
                return None
            if current[0] == key:
                return current[1]

            collisions += 1

提醒:若你要支援刪除,在 open addressing 中一般會使用 lazy deletion(標記刪除),避免破壞探測序列。

結語

在理想情況下,它能以近乎 O(1) 的時間完成插入、查找與刪除,成為快取、索引、會話管理等場景的首選結構。真正影響效能與穩定度的,往往不是語法,而是工程細節:雜湊函數的分布品質、碰撞處理策略(separate chaining / open addressing)、負載因子控制與動態擴容(rehash)等。理解這些設計抉擇,能讓你在實務系統中做出「快且穩」的資料結構決策。

thinking programmer at computer

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

High Quality

Lorem ipsum dolor sit amet, consectetur adipiscing elitsed do eiusmod tempor.

Fast Delivery

Lorem ipsum dolor sit amet, consectetur adipiscing elitsed do eiusmod tempor.

Best Warranty

Lorem ipsum dolor sit amet, consectetur adipiscing elitsed do eiusmod tempor.