常用的哈希函数

本文介绍了常用的哈希函数,包括直接寻址法、数字分析法等,并详细讲解了通过“桶”算法处理冲突的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

常用的哈希函数包括:直接定址法、数字分析法、除留余数法、乘留余数法、平方取中法、折叠法等。应该根据实际工作中关 键码的特点选用适当的方法。

虽然采用合适的哈希方法能够降低冲突的概率,但是冲突仍然是不可避免的,处理冲突的最常用方法就是“桶”算法:假设哈希表有m 个地址,就将其改为 m 个“桶”,其桶号与哈希地址一一对应,每个桶都用来存放互为同义词的键,也就是如果两个不同的键用哈希函数计算得到了同一个哈希地址,就将它们放到同一个桶中,检索的时候在桶内进行顺序检索。


       1. 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a•key + b,其中a和b为常数(这种散列函数叫做自身函数)

  2. 数字分析法:分析一组数据,比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。

  3. 平方取中法:取关键字平方后的中间几位作为散列地址。

  4. 折叠法:将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。

  5. 随机数法:选择一随机函数,取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。

  6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。

### 常用哈希函数及其在 `unordered_set` 模板中的使用和实现 #### 1. 哈希函数简介 哈希函数是一种将任意长度的数据映射为固定长度值的算法。对于 C++ 中的 `unordered_set` 和其他基于哈希表的容器来说,良好的哈希函数能够显著提高其性能[^1]。 C++ 标准库提供了默认的通用哈希函数模板 `std::hash<T>`,适用于大多数内置类型(如整数、浮点数、字符串等)。然而,当键类型是非标准类型时,可能需要用户提供自定义的哈希函数。 --- #### 2. 默认哈希函数的应用 对于常见的内置类型,可以直接利用 `std::hash` 提供的功能。例如: ```cpp #include <iostream> #include <unordered_set> #include <string> int main() { std::unordered_set<int> intSet; intSet.insert(42); std::unordered_set<std::string> stringSet; stringSet.insert("hello"); if (intSet.find(42) != intSet.end()) { std::cout << "Integer found." << std::endl; } if (stringSet.find("hello") != stringSet.end()) { std::cout << "String found." << std::endl; } } ``` 上述代码展示了如何使用默认的 `std::hash` 处理基本数据类型和字符串类型的键值[^2]。 --- #### 3. 自定义哈希函数的实现 当键类型是用户定义的结构体或其他复杂类型时,需提供自定义的哈希函数。以下是一个完整的例子: ##### 示例:为自定义结构体定义哈希函数 假设有一个表示二维坐标的结构体 `Point`,我们需要为其创建一个哈希函数以便将其作为 `unordered_set` 的键。 ```cpp #include <iostream> #include <unordered_set> #include <functional> struct Point { int x, y; }; // 自定义哈希函数 namespace std { template <> struct hash<Point> { size_t operator()(const Point& p) const { // 结合 x 和 y 的哈希值生成唯一的哈希码 return hash<int>()(p.x) ^ (hash<int>()(p.y) << 1); } }; } int main() { std::unordered_set<Point> pointSet; pointSet.emplace(Point{1, 2}); pointSet.emplace(Point{3, 4}); if (pointSet.find(Point{1, 2}) != pointSet.end()) { std::cout << "Point (1, 2) exists in the set." << std::endl; } } ``` 在此示例中,我们通过特化 `std::hash` 来支持 `Point` 类型的哈希计算[^4]。 --- #### 4. 特殊情况处理 有些情况下,键类型无法直接转换为整数值,或者需要更复杂的逻辑来生成唯一标识符。例如: - **字符串类型**:虽然 `std::hash<std::string>` 已经存在,但如果希望优化性能或满足特定需求,可以编写自己的哈希函数。 - **指针类型**:由于指针本身已经是地址形式,因此可简单地返回其值作为哈希码。 ##### 示例:为字符串类型提供简化版哈希函数 ```cpp #include <iostream> #include <unordered_set> #include <string> struct CustomHash { size_t operator()(const std::string& str) const { return static_cast<size_t>(str[0]); // 取第一个字符作为哈希值 } }; int main() { std::unordered_set<std::string, CustomHash> customHashSet; customHashSet.insert("apple"); customHashSet.insert("banana"); if (customHashSet.find("apple") != customHashSet.end()) { std::cout << "Custom hash function works for 'apple'." << std::endl; } } ``` 注意,这种方法可能导致较高的碰撞率,仅适合简单的应用场景[^4]。 --- #### 5. 性能考量与调试工具 为了评估哈希表的实际表现,可以借助一些成员函数进行分析。例如: - `bucket_count()` 返回当前桶的数量; - `bucket_size(n)` 获取第 n 个桶的有效元素数量; - `bucket(key)` 查找某个键所属的具体桶编号。 这些工具可以帮助开发者诊断潜在问题并调整哈希策略[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值