前言
使用哈希表可以进行非常快速的查找操作。但是,哈希表究竟是啥?很多人避而不谈,虽然知道经常用到,很多语言的内置数据结构像python中的字典,java中的HashMap,都是基于哈希表实现,但人们很少关注其底层实现的细节,今天便来学习哈希表的实现和书写相关算法题
一、What is hashing?
先来看一段标准的定义:
散列(hashing)是电脑科学中一种对资料的处理方法,通过某种特定的函数/算法(称为散列函数/算法)将要检索的项与用来检索的索引(称为散列,或者散列值)关联起来,生成一种便于搜索的数据结构(称为散列表)。也译为散列。旧译哈希(误以为是人名而采用了音译)。它也常用作一种资讯安全的实作方法,由一串资料中经过散列算法(Hashingalgorithms)计算出来的资料指纹(data fingerprint),经常用来识别档案与资料是否有被窜改,以保证档案与资料确实是由原创者所提供。 ----Wikipedia
再来说说笔者的理解:
哈希表是通过离散和映射的方式,用一群较小的数,表示一群较大的数,从而节省计算机的存储空间,并以此提升查找速度。
举个最简单的例子:若要在0-10(9)的数轴上寻找10(5)个的整数并对其进行存储,你不可能去开一个从0到10(9)-1个坐标的数轴数组,然后对于10(5)个的整数输入,每输入一个整数,就在其对应数轴数组上+1,这样其实有很多点并未用到,浪费许多存储空间。
不理解离散和映射的朋友可以看以下这篇博客,体会一下用离散点存储大容量数据的点的好处。
二、如何实现哈希表中的映射和带来的问题
一般采用Mod取余操作,用一个大区间mod上小区间,从而将一个大区间映射到小区间上;
如0 - 11中随机产生的数,映射到0 - 5,便是11 mod 5
但是同时这样映射会带来一些问题,如冲突问题,如你6 mod 5,映射到的位置为 1, 11 mod 5, 映射到的位置同样为 1;如此便带来了冲突问题。
那如何解决冲突问题,便引来了哈希表的分类。
三、哈希表的分类
依靠哈希表解决映射带来的冲突问题的不同解决方法,我们对哈希表进行分类,分为拉链法实现和寻址法实现。
拉链法
拉链法又称链地址法,是指当元素映射到哈希表的过程中,不同的元素映射到哈希表的同一位置时,这时采用在冲突的位置上引出一个链表,依次将冲突的元素添加到链表上的方法。
具体过程如下:
- 计算 key 的 hashValue
- 根据 hashValue 值定位到 table[hashIndex] 。( table[hashIndex] 是一条链表Node)
- 若 table[hashValue] 为空则直接插入,不然则添加到链表末尾
这里借助一道题目,来演示拉链法的实际应用
题目如下:
//拉链法书写哈希表
#include<iostream>
#include<cstring>
using namespace std;
const int N=100003;
int h[N],e[N],ne[N],idx=0;//h表示哈希表的坐标值指向拉链法的第一个头,e表示值,ne表示下一个坐标值,idx表示当前使用到哪个点
int n;
void insert(int x){
int index=(x%N+N)%N;//这里排除负数的情况
e[idx]=x;
ne[idx]=h[index];
h[index]=idx++;
}
bool query(int num){
int index=(num%N+N)%N;
for (int i=h[index];i!=-1;i=ne[i]){
if (e[i]==num)
return true;
}
return false;
}
int main(){
cin>>n;
memset(h,-1,sizeof h);//设置初始指向为-1
while(n--){
char op[2];
int num;
scanf("%s%d",op,&num);
if (op[0]=='I'){
insert(num);
}else{
if (query(num)){
cout<<"Yes"<<endl;
}else{
cout<<"No"<<endl;
}
}
}
return 0;
}
寻址法
这里所说的寻址法一般是指开放寻址法,即先将要插入哈希表的数映射到哈希表的某个位置上,若这个位置存在数,则往后延一个位置,若后一个位置也存在数,则继续延后,若延后到了尽头,则返回到第第一个数,继续搜寻,直到找到合适的位置或寻找完毕退出;
图示
×表示该位置山有数了,进行后延
√表示找到合适的位置了,进行插入
这里用寻址法解决前文的题目
//寻址法书写哈希表
#include<iostream>
#include<cstring>
using namespace std;
const int N=200003,nullNum=0x3f3f3f3f;
int h[N];
int find(int k){
int index=(k%N+N)%N;
while(h[index]!=nullNum && h[index]!=k){
index++;
if (index==N) index=0;
}
return index;
}
int main(){
int n;
cin>>n;
memset(h,nullNum,sizeof h);
while(n--){
char op[2];
int num;
scanf("%s%d",op,&num);
int loc=find(num);
if (op[0]=='I'){
h[loc]=num;
}else{
if (h[loc]!=nullNum){
cout<<"Yes"<<endl;
}else{
cout<<"No"<<endl;
}
}
}
return 0;
}