目录
哈希函数(将不同类型的键值key,映射到哈希表索引范围内的整数,即:index = H( key ) )
哈希表的实现
哈希函数(将不同类型的键值key,映射到哈希表索引范围内的整数,即:index = H( key ) )
1. 直接定址法
- 映射函数:H(key) = key 或 H(key) = a * key + b(其中a和b为常数)。
- 作用的键的类型:直接定址法适用于整数类型的键,或者可以简单转换为整数的键(如通过类型转换或字符串哈希)。这种方法简单直接,但要求事先知道关键字的分布情况,适合查找表较小且连续的情况。
2. 平方取中法
- 映射函数:取关键字的平方值的中间几位作为哈希地址。例如,如果关键字是1234,那么它的平方是1522756,然后取中间的3位(如227)作为哈希地址。
- 作用的键的类型:平方取中法适用于整数类型的键,特别是当关键字的位数适中,且分布较为均匀时。这种方法比较适合于不知道关键字的分布,而位数又不是很大的情况。
3. 折叠法
- 映射函数:将关键字分割成位数相同的几部分(最后一部分位数可以不同),然后取这几部分的叠加和(注意去除进位)作为哈希地址。折叠法有移位叠加和间界叠加两种方法。
- 作用的键的类型:折叠法适用于任何可以分割并计算叠加和的键类型,包括整数、字符串等。通过调整分割的位数和叠加的方式,可以灵活处理不同长度的键。
4. 除留余数法
- 映射函数:H(key) = key mod p。其中,p是一个不大于哈希表大小(size)的最大质数,或者是不包含小于20质因子的合数。
- 作用的键的类型:除留余数法适用于整数类型的键,也适用于可以通过某种方式转换为整数的键(如字符串哈希)。这种方法通过取模运算将关键字映射到哈希表的有限范围内,是哈希表中最常用的方法之一。而且这种方法也可以运用在折叠法、平方取中法之后。
5. 位掩码法(除数留余法的特殊优化)
- 映射函数:H(key) = key & (S - 1)。其中,& 表示按位与操作,S是一个合适的2的k次幂,用位运算优化取模运算(此时有 key % S = key & (S - 1) )。
- 作用的键的类型:由于其是除数留余法的特殊优化,故两者作用类型基本一致。其中整数能够直接进行位运算,并且在哈希表大小为2的幂的情况时表现出色
标准库中的哈希函数
在C++标准库中,std::hash
是一个模板类,用于生成对象的哈希值。这个类在 <functional>
头文件中定义,并且它为多种基本数据类型和某些标准库类型(如 std::string
)提供了特化版本。
使用方法如下:
hash<KeyType> hasher;
size_t index = hasher(key);
// 当类型为自定义类型时
// 一个简单的自定义类型
struct MyType {
int value;
};
// 为 MyType 提供 std::hash 的特化
namespace std {
template<>
struct hash<MyType> {
size_t operator()(const MyType& t) const {
// 这里使用简单的哈希函数,实际应用中可能需要更复杂的算法
return hash<int>()(t.value);
}
};
}
哈希表的代码实现(采用链地址法解决哈希冲突)
// 哈希表节点类
template<typename KeyType, typename ValueType>
class HashNode {
public:
KeyType key; // 键
ValueType value; // 值
HashNode *next; // 指向下一个节点
// 构造函数
HashNode(KeyType key, ValueType value) {
this->key = key;
this->value = value;
this->next = NULL;
}
};
// 哈希表类
template<typename KeyType, typename ValueType>
class HashTable {
private:
int size; // 哈希表大小
HashNode<KeyType, ValueType> **table; // 哈希表
// 哈希函数:根据键值key计算哈希地址
int hash(const KeyType &key) const {
int hashkey = key % size;
if(hashkey < 0) hashkey += size;
return hashkey;
}
public:
// 构造函数
HashTable(int size);
// 析构函数
~HashTable();
// 插入键值对
void insert(const KeyType &key, const ValueType &value);
// 删除键值对
void remove(const KeyType &key);
// 查找指定键的值
bool find(const KeyType &key, ValueType &value) const;
};
template<typename KeyType, typename ValueType>
HashTable<KeyType, ValueType>::HashTable(int size) {
this->size = size;
table = new HashNode<KeyType, ValueType>*[size];
for(int i=0; i < size; i++) {
table[i] = NULL;
}
}
template<typename KeyType, typename ValueType>
HashTable<KeyType, ValueType>::~HashTable() {
for(int i=0; i < size; i++) {
if(table[i]){
HashNode<KeyType, ValueType> *temp = table[i];
while(temp) {
HashNode<KeyType, ValueType> *next = temp->next;
delete temp;
temp = next;
}
}
}
delete[] table;
}
template<typename KeyType, typename ValueType>
void HashTable<KeyType, ValueType>::insert(const KeyType &key, const ValueType &value) {
int index = hash(key); // 哈希地址
HashNode<KeyType, ValueType> *node = new HashNode<KeyType, ValueType>(key, value);
if(table[index] == NULL) {
table[index] = node;
} else {
HashNode<KeyType, ValueType> *temp = table[index];
while(temp->next) {
if(temp->key == key) { // 键值对已存在,更新值
temp->value = value;
return;
}
temp = temp->next;
}
// 键值对不存在,插入到链表末尾,处理哈希冲突
temp->next = node;
}
}
template<typename KeyType, typename ValueType>
void HashTable<KeyType, ValueType>::remove(const KeyType &key) {
int index = hash(key); // 哈希地址
if(table[index] == NULL) {
return; // 键值key不存在,直接返回
} else {
HashNode<KeyType, ValueType> *temp = table[index];
while(temp->next && temp->next->key != key) temp = temp->next;
if(temp->next) {
HashNode<KeyType, ValueType> *next = temp->next->next;
delete temp->next;
temp->next = next;
} else {
return;
}
}
}
template<typename KeyType, typename ValueType>
bool HashTable<KeyType, ValueType>::find(const KeyType &key, ValueType &value) const {
int index = hash(key); // 哈希地址
if(table[index] == NULL) {
return false; // 键值key不存在,直接返回false
} else {
if(table[index]->key == key) {
value = table[index]->value;
return true; // 键值对存在,返回true
}
HashNode<KeyType, ValueType> *temp = table[index]->next;
while(temp->next && temp->key != key) temp = temp->next;
if(temp->next) {
value = temp->next->value;
return true; // 键值对存在,返回true
}
}
return false; // 键值key不存在,返回false
}
标准库中的哈希表
unordered_map
在 <unordered_map>
头文件中。 unordered_map
通过一个哈希函数来组织数据,使得插入、删除和查找操作在平均情况下具有常数时间复杂度,即 O(1)。不过,在最坏情况下(比如所有键的哈希值都相同),这些操作的时间复杂度会退化到 O(n)。
// 定义一个哈希表
unordered_map<KeyType, ValueType> Hash;
// 每个键值对是以 std::pair 类型进行存储的,可以如下遍历
for (const auto& pair : Hash) {
// 这里的 pair 就是 std::pair 类型
cout << pair.first << " : " << pair.second << '\n';
}
内置函数
unordered_map
提供了丰富的成员函数来支持其操作,包括但不限于:
insert
:插入一个键值对或键值对数组。emplace
:在容器内部直接构造一个键值对(可能更高效)。erase
:删除一个元素(通过键、迭代器或迭代器范围)。find
:查找一个键,如果找到则返回指向该键的迭代器,否则返回end()
迭代器。count
:返回与指定键相匹配的元素数量(对于unordered_map
,这总是 0 或 1)。at
:通过键访问值,如果键不存在则抛出std::out_of_range
异常。operator[]
:通过键访问值,如果键不存在则默认构造一个值(对于内置类型如int
,则是 0)。clear
:删除所有元素。size
:返回容器中元素的数量。empty
:检查容器是否为空。begin
/end
:返回指向容器中第一个元素和最后一个元素之后位置的迭代器。
// insert 用法
Hash.insert({1, "One"});
Hash.insert(make_pair(2, "Two"));
// emplace 用法
Hash.emplace(3, "Three");
// erase 用法(通过键)
Hash.erase(1);
// erase 用法(通过迭代器)
auto it = Hash.find(2);
if (it!= Hash.end()) {
Hash.erase(it);
}
// find 用法
auto foundIt = Hash.find(3);
if (foundIt!= Hash.end()) {
cout << "Found: " << foundIt->second << '\n';
} else {
cout << "Not found" << '\n';
}
// count 用法
int count = Hash.count(3);
cout << "Count of key 3: " << count << '\n';
// at 用法
try {
cout << "Value at key 3: " << Hash.at(3) << endl;
} catch (const out_of_range &e) {
cout << "Key not found" << '\n';
}
// operator[] 用法
Hash[4] = "Four";
cout << "Value at key 4: " << Hash[4] << '\n';
// clear 用法
Hash.clear();
// size 用法
cout << "Size after clear: " << Hash.size() << '\n';
// empty 用法
if (Hash.empty()) {
cout << "HashMap is empty" << '\n';
} else {
cout << "HashMap is not empty" << '\n';
}
// begin/end 用法
for (auto it = Hash.begin(); it!= Hash.end(); ++it) {
cout << it->first << " : " << it->second << '\n';
}