1.散列的引入
首先先来看一个简单的问题:
给出N个整数,在给出M个整数,问这些M个整数中有那些数在N个整数中出现过?
例如N={2,4,5,6,9} M={1 4 6 8 10} 输出:4 6
最直观的思想便是定义数组N,遍历M的每一个元素时和数组N中的元素挨个比较,相等则输出。
上述算法时间复杂度为O(NM),当N和M很大时,就会无法承受
不妨,采取空间换时间,定义一个bool型的数组hashtable[Z],Z表示为比较大的数。读入N中的元素x时,将hashtable[x]=true。如在上述问题中hashtable[2]=true;hashtable[4]=true。于是,对M个欲查询的数就可以直接通过hashtable数组判断是否出现过。
2.上述问题的代码部分
#include <cstdio>
const int maxn=100010;
bool hashtable[maxn]={false};
int main(){
int n,m,x;
int a[maxn]={0},count=0;
scanf("%d",&n);//输入N的个数
for(int i=0;i<n;i++){
scanf("%d",&x);
hashtable[x]=true;
}
scanf("%d",&m);//输入M的个数
for(int i=0;i<m;i++){
scanf("%d",&x);
if(hashtable[x]==true){
a[count]=x;//存入输出数组中
count++;
}
}
//输出
for(int i=0;i<count;i++){
printf("%d ",a[i]);
}
}
同样的,如果还想要查询M个欲查询元素在N中出现的个数,则可以吧hashtable替换为int型,当要查询的数为x时,则hashtable[x]++;
代码如下:
#include <cstdio>
const int maxn=100010;
int hashtable[maxn]={0};
int main(){
int n,m,x;
int a[maxn]={0},count=0;
scanf("%d",&n);//输入N的个数
for(int i=0;i<n;i++){
scanf("%d",&x);
hashtable[x]++;
}
scanf("%d",&m);//输入M的个数
for(int i=0;i<m;i++){
scanf("%d",&x);
if(hashtable[x]>0){
a[count]=x;//存入输出数组中
count++;
hashtable[x]++;
}
}
//输出
for(int i=0;i<count;i++){
printf("%d ",a[i]);
printf("次数为:");
printf("%d\n",hashtable[a[i]]-1);
}
return 0;
}
这段代码有点瑕疵:
例如会形成如下的结果
问题在于要输出的元素存入数组中,重复输入了。可以再用个hashtable或者遍历 来判断是否当前元素是否已存在数组当中。或者放弃统一输出改为单次查询后单独输出。
3.散列函数
详细参考数据结构查找—hash表
散列函数:将元素通过一个函数转换为整数,使得整数可以尽量唯一代表这个元素。
散列函数
- 直接定址法 上述例子就用了直接定址法
- 平方取中法
- 除留余数法
4.冲突的解决
冲突:通过除留余数法可能会有2个不同的整数却有相同的余数。我们把这种情况叫做冲突
解决方法:
- 线性探测法
- 平方探查法
- 链地址法
5. 字符串hash初步
问题:如果key不是整数,那么该如何设计散列函数?
比如对于字符串,怎么才能将一个字符串映射为一个整数呢?
思想:为了讨论方便,不妨假设字符串都是由大写字母A~Z构成。在这个基础上,不妨把A-Z视为0-25,这样就将26个大写字母对应到了26进制数当中。接着,将26进制数转为10位数,则得到了10进制数字且是唯一的。
下面用函数封装这个功能:
int hashFunc(char S[],int len){
int id=0;
for(int i=0;i<len;i++){
id=id*26+S[i]-'A';//二十六进制转为十进制
}
return id;
}
或者参考之前的进制转换(点击跳转)
这样写更方便理解:
int hashFunc(char S[],int len){
int id=0;
int p=1;//p为当前位权
for(int i=len-1;i>=0;i--){
id=id+(S[i]-'A')*p;//二十六进制转为十进制
p=p*26;
}
return id;
}
更进一步:如果字符串中有大写也有小写该怎么实现转换呢?
方法:可以把A-Z当做0-25,a-z当做26-51,这样便成了52进制的数
转换函数为:
int hashFunc1(char S[],int len){
int id=0;
for(int i=0;i<len;i++){
if(s[i]>='A'&&s[i]<='Z'){
id=id*52+(s[i]-'A');
}
else if if(s[i]>='a'&&s[i]<='a'){
id=id*52+(s[i]-'a');
}
}
return id;
}
再进一步:如果字符串中有数字该怎么实现转换呢?
- 数字出现在任意位置则按上述方法增大进制为62进制
- 数字均出现在末尾则直接将末尾数字拼接上去