哈希表(除留余数法)

一、核心概念

  • 哈希表:通过哈希函数将键映射到存储位置,实现快速查找。
  • 哈希冲突:不同键映射到同一位置。​开放定址法通过探测下一个空位解决冲突。
  • 负载因子a = 元素个数 / 表容量,控制扩容时机(通常 a ≥ 0.7 时扩容)。

二、代码结构解析

1. 哈希节点结构

enum STATE {
	EXIST,
	EMPTY,
	DELETE
};

template<class K, class V>
struct HashData {
    pair<K, V> _kv;        // 存储键值对
    STATE _state = EMPTY;  // 状态标记:存在、空、删除
};
  • 状态标记

    • EXIST:该位置有有效数据

    • EMPTY:该位置为空

    • DELETE:该位置数据被删除

  • 作用

    • 解决冲突探测时无法区分“从未插入”和“已删除”的问题

    • 删除时不物理删除数据,仅标记状态,避免破坏探测链

2. 哈希函数设计

  • 默认哈希(整型)​:直接返回键值。
    • 适用场景:整数类型(intchar 等)

    • 局限性:无法直接处理字符串等复杂类型

      template<class K>
      struct DefaultHashFunc {
      	size_t operator()(const K& key) {
      		return (size_t)key;
      	}
      };
  • 字符串特化(BKDR哈希)​:减少冲突。
    • BKDR 算法:通过乘法和累加减少哈希冲突

    • 必要性:字符串需要特殊处理,避免 str[0] 直接取首字符导致高冲突率

      template<>
      struct DefaultHashFunc<string> {
          size_t operator()(const string& str) {
              size_t hash = 0;
              for (auto ch : str) {
                  hash *= 131;  // BKDR 哈希乘子
                  hash += ch;
              }
              return hash;
          }
      };

3. 哈希表类 

template<class K, class V, class HashFunc = DefaultHashFunc<K>>
class HashTable {
private:
    vector<HashData<K, V>> _table;  // 底层数组
    size_t _n = 0;                // 有效元素个数
};
  • 动态数组:底层容器选择 vector,支持动态扩容

  • 模板参数

    • K:键类型,V:值类型

    • HashFunc:哈希函数类型,默认支持 K 类型的哈希

三、关键操作实现步骤

1. 插入操作

 

  • 步骤
    1. 检查负载因子:超过阈值(如0.7)触发扩容。
    2. 扩容处理:创建新表,遍历旧表数据重新哈希插入。
    3. 线性探测插入:找到空位或删除位,插入数据并标记状态。

  • 代码
    bool Insert(const pair<K, V>& kv) {
        // 扩容检查(载荷因子 ≥0.7)
        if (_n * 10 / _table.size() >= 7) {
            size_t newSize = _table.size() * 2;
    
    		// 遍历旧表,重新映射到新表
    		HashTable<K, V, HashFunc> newHT;
    		newHT._table.resize(newSize);
    
    		// 遍历旧表的数据插入到新表即可
    		for (size_t i = 0; i < _table.size(); i++){
    			if (_table[i]._state == EXIST){
    				newHT.Insert(_table[i]._kv);
    			}
    		}
            _table.swap(newHT._table);
        }
    
        // 计算初始哈希位置
        HashFunc hf;
        size_t hashi = hf(kv.first) % _table.size();
    
        // 线性探测:找到空位置或可覆盖位置
        while (_table[hashi]._state == EXIST) {
            ++hashi;
            hashi %= _table.size(); // 环形探测
        }
    
        // 插入数据并标记状态
        _table[hashi]._kv = kv;
        _table[hashi]._state = EXIST;
        ++_n;
        return true;
    }
    • 线性探测:冲突时顺序向后查找空位

    • 环形探测hashi %= _table.size() 保证索引不越界

2. 查找操作

  • 步骤
    1. 计算哈希值定位初始位置。
    2. 线性探测,直到遇到 EMPTY 状态。
    3. 检查每个 EXIST 状态的键是否匹配。
  • 关键细节
    • 跳过删除状态DELETE 状态的位置需继续探测。
  • 代码:
    HashData<const K, V>* Find(const K& key) {
        size_t hashi = hf(key) % _table.size();
        while (_table[hashi]._state != EMPTY) {
            if (_table[hashi]._state == EXIST && _table[hashi]._kv.first == key) {
                return (HashData<const K, V>*)&_table[hashi];
            }
            ++hashi;
            hashi %= _table.size();
        }
        return nullptr;
    }

3. 删除操作

  • 步骤
    1. 调用 Find 找到目标节点。
    2. 标记为 DELETE 状态(不实际移除数据),_n 递减。
  • 优势:逻辑删除避免破坏探测链。
  • 代码
    bool Erase(const K& key) {
        HashData<const K, V>* ret = Find(key);
        if (ret) {
            ret->_state = DELETE; // 标记为删除
            --_n;
            return true;
        }
        return false;
    }

 

### 哈希函数中除留余数法的原理 除留余数法是一种常见的哈希函数设计方法,其基本思想是将关键字 \( key \) 对某个正整数 \( m \) 取模运算,得到的结果作为该关键字对应的存储位置索引。具体表达式如下: \[ h(key) = key \% m \] 其中,\( h(key) \) 是计算所得的哈希值,\( key \) 是待映射的关键字,而 \( m \) 则是哈希表长度。 这种方法的核心在于取模操作能够有效地将任意大小的关键字均匀分布到有限数量的槽位上[^1]。为了获得更好的性能,通常建议选择质数作为 \( m \),因为这样可以减少冲突的概率并提高数据分布的随机性。 ### 使用 Python 实现基于除留余数法的哈希函数 以下是利用 Python 编写的一个简单例子来展示如何应用此技术构建一个基础版的哈希表,并采用拉链法解决可能发生的碰撞问题: ```python class HashTable: def __init__(self, size): self.size = size self.table = [[] for _ in range(size)] def hash_function(self, key): return key % self.size def insert(self, key): index = self.hash_function(key) self.table[index].append(key) def display_table(self): for i in range(self.size): if not self.table[i]: print(f"{i} --> NULL") else: chain = " --> ".join(map(str, self.table[i])) print(f"{i} --> {chain}") # 初始化哈希表以及插入示例键值 hash_table = HashTable(12) keys_to_insert = [32, 15, 7, 11, 4, 28, 56, 61, 79] for key in keys_to_insert: hash_table.insert(key) hash_table.display_table() ``` 上述代码定义了一个 `HashTable` 类,它初始化时创建指定大小的列表数组用于保存各条目链接起来形成的子列表 (即所谓的桶(bucket)) 。每当有新项加入时,会先调用自定义的散列函数决定应该放置在哪一编号的位置之上;如果发生冲突,则直接附加至对应位置现有的序列末端[^2]。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值