散列是常用的算法思想之一
应用如,给出N个正整数,再给出M个正整数
问:M个数中的每个数是否在N个数中出现过
如果N,M<=10^5,可以用遍历来做,但时间复杂度很大
这时就可以用空间换时间
即设定一个bool型数组hash Table[100010],
这样在一开始读入N个正整数时就进行预处理
代码:
#include<cstdio>
#define maxn 100010
bool hashTable[maxn]={false};
int main()
{
int n,m,x;
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
scanf("%d",&x);
hashTable[x]=true;
}
for(int i=0;i<m;i++)
{
scanf("%d",&x);
if(hashTable[x]==true)
{
printf("YES\n");
}
else
{
printf("NO\n");
}
}
return 0;
}
如果要求M个欲查询的书中每个数在N个数中出现的次数
那么可以把hashTable换成int型
代码:
#include<cstdio>
#define maxn 100010
int hashTable[maxn]={0};
int main()
{
int n,m,x;
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
scanf("%d",&x);
hashTable[x]++;
}
for(int i=0;i<m;i++)
{
scanf("%d",&x);
printf("%d\n",hashTable[x]);
}
return 0;
}
以上两个问题都有一个特点
就是直接把输入的数作为数组下标来对这个数的性质 进行统计
这是一个很好的用空间换时间的策略
还有以上题目的每个数都不会超过10^5
因此直接作为数组下标是可行的
但是如果输入的可能是10^9大小的整数
或者甚至是一个字符串
就不能直接将他们作为下标了
此时就要用到hash
即为“将元素通过一个函数转化为整数,使得该整数可以尽量唯一地代表这个元素”
元素在转换前为key,转换后就是一个整数H(key)
对于key是整数的情况,常用的散列函数
①直接地址法
A恒等变换
即H(key)=key(开头两个应用,数组下标)
B线性变换
H(key)=a*key+b
②平方取中法
指取key的平方的中间若干位作为hash值(很少用)
③除留余数法
把key除以一个数mod得到的余数作为hash的方法
H(key)=key%mod
通过这个散列函数,可以将很大的数转换为不超过mod的整数
这样就可以将它作为可行的数组下标
(注意:表长TSize必须不小于mod,不然会产生越界)
显然,当mod是一个素数时
H(key)能尽可能覆盖[0,mod)范围内的每一个数
因此一般为了方便起见
下文中取TSize是一个素数
而mod直接取成与TSize相等
但是,通过除留余数法可能会有两个不同的数key1,key2
它们的hash值H(key 1)与H(key 2)是相同的
这样当key1已经把表中位置为H(key 1)的单位占据时
key2便不能再使用这个位置了
我们把这种情况叫做冲突
以下三种解决冲突的方法:
(其中第一种和第二种都计算了新的hash值,又称为开放定址法)
①线性探查法
当得到key的hash值H(key)
但是表中下标为H(key)的位置已经被某个其他元素使用了
那么就检查下一个位置H(key)+1是否被占
如果没有就使用这个位置
否则就继续检查下一个位置(也就是hash值不断+1)
如果检查过程中发现超过了表长
那么就回到表的首位继续循环
直到找到一个可以使用的位置
或者发现表中的所有位置都已被使用
显然,这个方法容易导致扎堆
即表中连续若干个位置都被使用
这在一定程度上会降低效率
②平方探测法
为了尽可能避免扎堆现象
当表中下标为H(key)的位置被占时
将按下面的顺序检查表中的位置
H(key)+1^2、H(key)-1^2、H(key)+2^2、H(key)-2^2、H(key)+3^2……
如果检查过程中H(key)+k^2超过了表长TSize
那么就把H(key)+k^2对表长TSize取模
如果检查过程中出现H(key)-k^2<0的情况(假设表的首位是0)
那么将((H(key)-k^2)%TSize+TSize)%TSize作为结果
等价于将H(key)-k^2不断加上TSize直到出现第一个非负数
如果想要避免负数的麻烦
可能只进行正向的平方探查
可以证明
如果k在[0,TSize)范围内都无法找到位置
那么当T>=TSize时,也一定无法找到位置
③链地址法
此方法不和以上两种方法一样
不计算新的hash值
而是把所有H(key)相同的key连接成一条单链表
这样可以设定一个数组Link
范围是Link[0]~Link[mod]
其中Link[h]存放H(key)=h的一条单链表
于是当多个关键字key的hash值都是h时
就可以直接把这些冲突的key直接用单链表连接起来
此时就可以遍历这条单链表来寻找所有H(key)=h的key
一般来说,直接使用map使用hash的功能
因此除非必须模拟这些方法或是对算法的效率要求比较高
一般不需要自己实现上面解决冲突的方法
知识点来自于《算法笔记》