算法学习--散列

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表
散列函数:将元素通过一个函数转换为整数,使得整数可以尽量唯一代表这个元素。
散列函数

  1. 直接定址法 上述例子就用了直接定址法
  2. 平方取中法
  3. 除留余数法

4.冲突的解决

冲突:通过除留余数法可能会有2个不同的整数却有相同的余数。我们把这种情况叫做冲突
解决方法:

  1. 线性探测法
  2. 平方探查法
  3. 链地址法

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进制
  • 数字均出现在末尾则直接将末尾数字拼接上去
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值