散列的定义与整数散列
散列是常用的算法思想之一,在很多程序中都能见到。
经典问题:给出N个正整数,再给出M个正整数,问这M个数中的每个数分别在N个数中是否出现过。
最直观的思路:对每个欲查询的正整数x,遍历所有N个数,看是否有一个数与x相等。这种做法的时间复杂度为O(MN),当M和N很大时,显然这种方法是不可取!
#include<bits/stdc++.h>
using namespace std;
int main()
{
int N,M;
cin>>N>>M;
int a[N],b[M];
for(int i=0;i<N;i++)
cin>>a[i];
for(int j=0;j<M;j++)
cin>>b[j];
for(int i=0;i<N;i++)
for(int j=0;j<M;j++)//时间复杂度为O(NM)
if(b[j]==a[i])
cout<<"true"<<endl;
else
cout<<"false"<<endl;
return 0;
}
这时候我们不妨用空间来换时间,即设定一个bool型数组hashTable[100010],其中hashTable[x]==true表示正整数x在N个正整数中出现过,而hashTable[x]==false则表示整数x在N个整数没有出现过。这样就可以在一开始读入N个正整数时就能进行预处理,即当读入的数为x,令hashTable[x]=true,对M个书进行查询时,就那个根据hashTable数组判断出每个数是否出现过,这中算法的时件复杂度为O(N+M);
#include<bits/stdc++.h>
using namespace std;
int main()
{
bool hashTable[100010]={false};//初始化未出现
int N,M,x;
cin>>N>>M;
for(int i=0;i<N;i++) {
cin>>x;
hashTable[x]=true;
}
for(int j=0;j<M;j++) {
cin>>x;
if(hashTable[x]==true)//时间复杂度为O(N+M)
cout<<"true"<<endl;
else
cout<<"false"<<endl;
}
return 0;
}
经典问题:M个欲查询的数中每个数在N个数中出现的次数。
此时bool hashTable[100010]改为int hashTable[100010]
#include<bits/stdc++.h>
using namespace std;
#define Max 100010
int main()
{
int hashTable[Max]={0};//初始化次数为0
int N,M,x;
cin>>N>>M;
for(int i=0;i<N;i++) {
cin>>x;
hashTable[x]++;
}
for(int j=0;j<M;j++) {
cin>>x;
cout<<hashTable[x]<<endl;
}
return 0;
}
这种方法固然好用,但数组下标数过大时,超过10^9时,此时就会出现错误,那这是该如何解决呢?
*
解决方法就是散列(hash),散列(hash)就是将元素通过一个函数转化为整数,使得该整数可以尽量唯一的代表这个元素。
这个转换函数就叫做散列函数H,元素转换为key,则转换后就是H(key)。
当key是整数时,常用方法:直接定址法,平方取中法,除留余数法。
上面的程序用到的就是直接定址法:H(key)=key
平方取中法:取key的平方的中间若干位作为hash值
除留取余法:H(key)=key%mod
把一个很大的数转化为了一个不超过mod的数,把这个数作为数组的下标(表长TSize不能小于mod),mod一般取素数,这样H(key)就能尽可能覆盖[0,mod)范围内的每一个数了,因此为了方便起见,TSize是一个素数,而mod直接取成与TSize相等。
这个时候就会出现一种情况,就是key1,key2,它们的数组下标是一样的,key1已经占用那个位置了,key2就不能再用了。这种情况成为冲突
解决方法
- 线性探查法(Linear Probing)
- 平方探查法(Quadratic probing)
- 链地址法(拉链法)
具体内容请见: 解决哈希冲突的三种方法.