散列表的開放定址法
開放定址法(open addressing)中,所有元素都存放在槽中,在連結串列法散列表中,每個槽中儲存的是相應連結串列的指標,為了維護一個連結串列,連結串列的每個結點必須有一個額外的域來儲存它的前戲和後繼結點。開放定址法不在槽外儲存元素,不使用指標,也不必須為了維護一個數據結構使用額外的域,所有可以不用儲存指標而節省的空間,使得可以用同樣的空間來提供更多的槽,也潛在地減少了衝突,提高了檢索速度。
為了使用開放定址法插入一個元素,需要連續地檢查散列表,或稱為探查(probe),直到找到一個空槽來放置待插入的關鍵字為止。
有三種常用技術來計算開放定址法中的探查序列:線性探查、二將探查和雙重探查。
1.線性探查:給定個一個普通的雜湊函式 h':U->{0, 1, ..., m-1},稱之為輔助雜湊函式,線性探查方法採用的雜湊函式為:
h(k, i) = (h'(k) + 1) mode n,i = 0, 1, ...,m-1
給定一個一個關鍵字k,首先探查 T[h'(k)],即由輔助雜湊函式所給出的槽位,再探查槽 T[h'(k) + 1], 依次類推,直至槽 T[m - 1],然後,又繞到 T[0], T[1],...,直到最後探查到槽 T[h'(k) - 1],線上性探查方法中,初始探查位置決定了整個序列,故只有 m 種不同的探查序列。
2.二次探查:採用如下列式的雜湊函式:h(k, i) = (h'(k) + c1i + c2i^2) mod m
其中 h' 是一個輔助雜湊波函式,c1 和 c2 為正的輔助常數,i = 0, 1, ... , m - 1。
象線性探查一樣,二次探查的初始探查位置決定了整個序列。
3.雙重雜湊:雙重雜湊是用於開放定址法的最好方法之一,因為它所產生的排列具有隨機選擇排列的這麼多特性。雙重雜湊採用以下形式的雜湊函式:h(k, i) = (h1(k) + ih2(k)) mod m
其中 h1 和 h2 均為輔助雜湊函式。初始探查位置為 T[h1(k)],後續的探查位置是前一位置加上偏移量 h2(k) 模 m。為了能查詢整個散列表,值 h2(k) 必須要與表的大小 m 互素。
以下是開放定址法的一個類的定義的例子:
- #ifndef _OPEN_ADDRESSING_HASH_H_
- #define _OPEN_ADDRESSING_HASH_H_
- /************************************************************************
- 演算法導論
- 開放定址法散列表,本全程採用雙重雜湊的雜湊函式,其中 h1(k) = k % m,
- h2(k) = 1 + k % (m - 1)
- ************************************************************************/
- #include <stdexcept>
- template <class T>
- class OpenAddressingHash{
- public:
- // 定義一個雜湊元素型別
- struct Node {
- friendclass OpenAddressingHash < T > ;
- // 雜湊元素鍵值,key 必須 >= 0,當 key == -1 時,表示槽是空的,
- // 當 key == -2 時表示槽內元素已刪除
- int key;
- T value;
- private:
- Node() :key(-1){}
- Node(int k, const T& v) :key(k), value(v){}
- };
- // 插入一個元素
- Node* insert(size_t key, const T& value);
- // 查詢一個元素
- Node* search(size_t key);
- // 刪除一個雜湊元素
- void remove(size_t key);
- private:
- // 散列表大小
- staticconstsize_t _table_size = 11;
- // 散列表
- Node _table[_table_size];
- // 雜湊函式
- size_t hash(size_t k, size_t);
- // 輔助雜湊函式 h1 h2
- inlinesize_t hash1(size_t k);
- inlinesize_t hash2(size_t k);
- };
- template <class T>
- typename OpenAddressingHash<T>::Node* OpenAddressingHash<T>::insert(size_t key, const T& value){
- size_t i = 0;
- while (i != _table_size) {
- auto hashCode = hash(key, i);
- auto node = &_table[hashCode];
- // 如果槽中關鍵字與要插入的關鍵字相同,則修改元素的值
- if (node->key == key || node->key == -2 || node->key == -1){
- node->key = static_cast<int>(key);
- node->value = value;
- return node;
- }
- ++i;
- }
- throw std::overflow_error("hash table overflow");
- }
- template <class T>
- typename OpenAddressingHash<T>::Node* OpenAddressingHash<T>::search(size_t key){
- size_t i = 0;
- while (i != _table_size)
- {
- auto hashCode = hash(key, i++);
- if (_table[hashCode].key == key)
- return &_table[hashCode];
- }
- return nullptr;
- }
- template <class T>
- void OpenAddressingHash<T>::remove(size_t key){
- auto node = search(key);
- if (node)
- // 將 key 設定為 -2,表示當前槽元素已刪除
- // 不要將 key 設定為 -1,如果這樣可導致之後具有相同雜湊值的元素不可訪問
- node->key = -2;
- }
- template <class T>
- size_t OpenAddressingHash<T>::hash(size_t key, size_t i){
- return (hash1(key) + i * hash2(key)) % _table_size;
- }
- template <class T>
- size_t OpenAddressingHash<T>::hash1(size_t key){
- return key % _table_size;
- }
- template <class T>
- size_t OpenAddressingHash<T>::hash2(size_t key){
- return key % (_table_size - 1) + 1;
- }
- #endif