哈希表
哈希表又称为散列表,他是建立key与value之间的映射,实现高速的元素查询,简而言之,我们输入一个key,可以快速的查到一个value,时间复杂度为O(1)
例如,每个学生都有学号和姓名,我们可以根据学号快速的知道姓名。
元素查询效率的对比
数组 | 链表 | 哈希表 | |
---|---|---|---|
查找元素 | O(1) | O(n) | O(1) |
添加元素 | O(1) | O(1) | O(1) |
删除元素 | O(n) | O(n) | O(1) |
哈希表的常用操作
哈希表的常用操作包括:初始化哈希表,查询操作,添加键值对,删除键值对等
//
// Created by Administrator on 2024/1/11.
//
#include "iostream"
#include "unordered_map"
#include "string"
using namespace std;
int main(){
//初始化哈希表
unordered_map<int,string > map;
//添加操作
map[123]="张三";
map[456]="李四";
map[789]="王五";
//查询操作
string name = map[123];
cout<<name<<endl;
//删除操作
map.erase(123);
return 0;
}
- 哈希表的遍历有三种方式:遍历键值对,遍历键,遍历值
//
// Created by Administrator on 2024/1/11.
//
#include "iostream"
#include "unordered_map"
#include "string"
using namespace std;
int main(){
//初始化哈希表
unordered_map<int,string > map;
//添加操作
map[123]="张三";
map[456]="李四";
map[789]="王五";
//遍历键值对
cout<<"遍历键值对"<<endl;
for(auto kv:map){
cout<<kv.first<<"->"<<kv.second<<endl;
}
cout<<"----------------------"<<endl;
cout<<"使用迭代器遍历"<<endl;
for(auto iter = map.begin();iter != map.end();iter++){
cout<<iter->first<<"->"<<iter->second<<endl;
}
return 0;
}
//输出结果
遍历键值对
789->王五
456->李四
123->张三
----------------------
使用迭代器遍历
789->王五
456->李四
123->张三
哈希表的简单实现
哈希函数:将一个较大的输入空间映射到一个较小的输出空间。
输入一个key,我们可以通过哈希函数得到该key对应的键值对在数组中的存储位置。
哈希函数的计算步骤:
1.通过某种哈希算法hash()计算得到哈希值
2.将hash()值与数组容量取模,从而得到该key值对应的数组索引index
index = hash(key) % capacity
基于数组的实现
设数组长度 capacity = 100;
哈希算法hash(key) = key;
哈希函数
key % 100
//
// Created by Administrator on 2024/1/11.
//
#include "iostream"
#include "string"
#include "vector"
using namespace std;
struct Pair{
int key;
string val;
Pair(int key,string val){
this->key = key;
this->val = val;
}
};
class arrayHash{
private:
vector<Pair *> buckets;
public:
arrayHash(){
buckets = vector<Pair*>(100);
}
int hashFuction(int key){
return key % 100;
}
//查询操作
string get(int key){
int index = hashFuction(key);
Pair* temp = buckets[index];
if(temp == nullptr){
return "";
}
return temp->val;
}
//添加操作
void put(int key,string val){
int index = hashFuction(key);
Pair *temp = new Pair(key,val);
buckets[index] = temp;
}
//删除操作
void remove(int key){
int index = hashFuction(key);
delete buckets[index];
buckets[index] = nullptr;
}
/* 获取所有键值对 */
vector<Pair *> pairSet() {
vector<Pair *> pairSet;
for (Pair *pair : buckets) {
if (pair != nullptr) {
pairSet.push_back(pair);
}
}
return pairSet;
}
/* 获取所有键 */
vector<int> keySet() {
vector<int> keySet;
for (Pair *pair : buckets) {
if (pair != nullptr) {
keySet.push_back(pair->key);
}
}
return keySet;
}
/* 获取所有值 */
vector<string> valueSet() {
vector<string> valueSet;
for (Pair *pair : buckets) {
if (pair != nullptr) {
valueSet.push_back(pair->val);
}
}
return valueSet;
}
/* 打印哈希表 */
void print() {
for (Pair *kv : pairSet()) {
cout << kv->key << " -> " << kv->val << endl;
}
}
};
int main(){
cout<<"测试基于数组实现的哈希表"<<endl;
arrayHash hash;
cout<<"添加三组数据"<<endl;
hash.put(123,"张三");
hash.put(456,"李四");
hash.put(789,"王五");
cout<<"打印输出哈希表"<<endl;
hash.print();
cout<<"查询456的信息:"<<hash.get(456)<<endl;
cout<<"删除123的信息:"<<endl;
hash.remove(123);
cout<<"打印输出哈希表"<<endl;
hash.print();
cout<<"获取所有的键值对"<<endl;
vector<Pair*> temp = hash.pairSet();
for(Pair * i : temp){
cout<<i->key<<"->"<<i->val<<endl;
}
cout<<"获取所有的键:"<<endl;
vector<int> key = hash.keySet();
for(auto i : key){
cout<<i<<endl;
}
cout<<"获取所有的值"<<endl;
vector<string> val = hash.valueSet();
for(auto i : val){
cout<<i<<endl;
}
return 0;
}
//输出结果
测试基于数组实现的哈希表
添加三组数据
打印输出哈希表
123 -> 张三
456 -> 李四
789 -> 王五
查询456的信息:李四
删除123的信息:
打印输出哈希表
456 -> 李四
789 -> 王五
获取所有的键值对
456->李四
789->王五
获取所有的键:
456
789
获取所有的值
李四
王五
哈希冲突与扩容
从本质上看,哈希函数的作用是将所有输入的key映射到数组所有构成的索引空间,然而,输入空间往往大于输出空间,因此,理论上存在”多个输入对应一个输出“
-
对于上述的哈希函数,当输入的key的后两位相同时,哈希函数输出的结果也是相同的,例如学号为12934和13034
得到
12934 % 100 = 34
13034 % 100 = 34
-
两个学号指向了同一个索引,这显然是不对的,当多个输入对应一个输出时,我我们称之为哈希冲突。
-
可以想到,当哈希表的容量越大,多个key被映射到同一个索引的概率就会降低,冲突就会减少,因此,我们可以通过扩容哈希表来减少哈希冲突。
-
负载因子:哈希表元素的数量除以哈希表的容量,用于衡量哈希冲突的严重程度,也常常作为哈希扩容的出发条件。
关注微信公众号:IT零壹学府,每日分享自己的学习经验。