这是C++算法基础-数据结构专栏的第三十四篇文章,专栏详情请见此处。
引入
在基础算法专栏中,我有一篇文章:离散化的实现,在这篇文章中,我讲解了离散化的实现,而离散化是一种特殊的哈希(特殊之处在于离散化是保序的),哈希也是一种将较大范围数据映射到一个较小范围中的方法。
下面我们就来讲哈希表的实现。
定义
哈希表又称散列表,一种以「key-value」形式存储数据的数据结构。所谓以「key-value」形式存储数据,是指任意的键值 key 都唯一对应到内存中的某个位置。只需要输入查找的键值,就可以快速地找到其对应的 value。可以把哈希表理解为一种高级的数组,这种数组的下标可以是很大的整数,浮点数,字符串甚至结构体。
对于哈希表的冲突情况,通常有两种解决方法:第一是拉链法,也称开散列法(open hashing);第二是开放寻址法,也称闭散列法。
过程
主体
对于给定的一个数据,对它的处理很简单:将其模上一个数,再插到相应的位置上。需要注意,这个模数应该是一个较大的质数(原因在数学上可以证明,较复杂不讲解)。
取模相信大家都知道如何操作,但对于负数的取模需要注意。数学上,一个数取模后一定是一个非负数,但在C++中,负数取模后会得到一个负数。例如,在数学中
,而在C++中
,这时,对于被模数
我们需要
(
为模数)。
这个做法有一个弊端,很明显,会有一些数取模后会有相同的结果,它们会被映射到同一个位置上,这被称为哈希冲突(哈希碰撞)。哈希冲突有两个常用解决方法。
拉链法
对于被映射到同一个位置上的数,我们可以在此位置上开一个单链表,如果有数映射到同一个位置上,只用把他们都放到那个位置的链表里就行了。查询的时候需要把对应位置的链表整个扫一遍,对其中的每个数据比较是否与查询值一致。
对单链表不太熟悉的可以看看我的这篇文章:单链表的实现。
开放寻址法
这种方法只需开一个一维数组,直接把数据存储在数组中,如果发生冲突则根据某种方式继续进行探查。比较容易的是线性探查法,若当前位置已经存储数据,则往后一个位置存储,若后一个位置也已经存储数据,则往后两个位置存储......查询的时候也是一样。
需要注意,开放寻址法中一维数组应该适当地将数组开大,在线性探查法中,数组一般开到指定大小的2~3倍。
在实际的代码实现中,上述两种方法我们任选一种实现即可。
代码
下面给出哈希表的实现代码:
// 1.拉链法
int h[N],e[N],ne[N],idx;
void insert(int x){
int k=(x%N+N)%N;
e[idx]=x;
ne[idx]=h[k];
h[k]=idx++;
}
bool find(int x){
int k=(x%N+N)%N;
for(int i=h[k];i!=-1;i=ne[i]){
if(e[i]==x)
return 1;
}
return 0;
}
// 2.开放寻址法
int h[N];
int find(int x){
int t=(x%N+N)%N;
while(h[t]!=null&&h[t]!=x){
t++;
if(t==N)
t=0;
}
return t;
}
代码解释
在拉链法中,第一行是用单链表实现的哈希表;insert()函数的作用是向哈希表中插入一个数;find()函数的作用是在哈希表中查询某个数是否存在。
在开放寻址法中,第一行是哈希表,find()函数的作用是在哈希表中查询某个数是否存在,具体来说如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置。
上一篇-堆排序的实现 C++算法基础专栏文章 下一篇-字符串哈希的实现
每周六更新一篇文章,内容一般是自己总结的经验或是在其他网站上整理的优质内容
点个赞,关注一下呗~