開放尋址法(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 {
- friend class 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:
- // 散列表大小
- static const size_t _table_size = 11;
- // 散列表
- Node _table[_table_size];
- // 散列函數
- size_t hash(size_t k, size_t);
- // 輔助散列函數 h1 h2
- inline size_t hash1(size_t k);
- inline size_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