开放寻址法(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