散列定义和整数散列——《算法笔记》学习笔记

本文内容完全参考胡凡的《算法笔记》,目的自己学习一边再记一下,方便本人日后复习;也本着分享的初衷给如我一样的小白参考参考,在这里推荐胡凡大佬的《算法笔记》,令我受益匪浅。

问题:给出N个正整数,再给出M个正整数,问这M个数中每个数分别是否再N个数中出现过,其中N,M\leq10^{5}.

最直接的思路是:对每个欲查询的正整数x,遍历所有N个数,看是否有一个数与x相等。这个做法的时间复杂度为0(NM),当N和M很大时,这个方法肯定不行了。

有一个新思路——用空间换时间,设定一个bool型数组hashTable[100010],其中hashTable[x]==true表示正整数x在N个正整数中出现过,而hashTable[x]==false表示正整数x在N个正整数中没有出现过。这样就可以在读入N个正整数可以预处理,即当读入数为x时,令hashTable[x]==true(说明:hashTable数组需要初始化为false,表示初始状态下所有数都未出现过)。于是对M个数欲查询的数,就能直接通过hashTable数组判断每个数是否出现过。这种方法的时间复杂度O(N+M)。

#include<cstdio>
const int 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;//数字x出现
	}
	for (int i = 0; i < m; i++) {
		scanf("%d", &x);
		if (hashTable[x] == true) {    //如果数字x出现过,则输出YES
			printf("YES\n");
		}
		else {
			printf("NO\n");
		}
	}
	return 0;
}

如果题目要求M个欲查询的数中每个数在N个数中出现的次数,那么可以把hashTable数组替换为int型,然后再输入N个数时进行预处理,即当输入数为x时,就令hashTable[x]++,这样就可以用O(N+M)的时间复杂度输出每个欲查询的数出现的次数。

#include<cstdio>
const int 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;
}

这两个问题的做法非常使用,还望初学者务必掌握!!!

 

以上两个方法虽然好,也只能解决一部分这方面问题,如果输入的数非常大呢(十的九次方以上),如果输入的是一个字符串呢,就不能直接将输入的内容作为数组下标了。这就引出了散列(hash).如果需要可以看看数据结构中散列表那章节的内容,加深理解。

散列可以浓缩成一句话“将元素通过一个函数转换为整数,使得该整数可以尽量唯一地代表这个元素”。其中把这个转换函数称为散列函数H,也就是说,如果元素在转换前为key,那么转换后就是一个整数H(key)。

那么对key是整数的情况来说,有哪些常用的散列函数呢?一般来说,常用的有直接定址法、平方取中法、除留余数法等,其中直接定址法是恒等变换(H(key)=key,前面开始讨论的就是直接把key作为数组下标,是最常见最实用的散列应用)或是线性变换(即H(key)=a*key+b);而平方取中法是指key的平方的中间若干位作为hash值(极少用)。比较实用的还有除留余数法

除留余数法是指把key除以一个数mod得到的余数作为hash值的方法,即

                                              H(key)==key%mod

  通过这个散列函数,可以把很大的数转换为不超过mod的整数,这样就可以把它作为可行的数组下标(注意TSise必须不小于mod,不然会产生越界)。显然当mod是一个素数时,H(key)能尽可能覆盖[0,mod]范围内的每一个数。因此一般为了方便,下文中去TSize是一个素数,而mod直接取成与TSize相等。

稍加推敲,会发现除留余数法可能会有两个不同的数,出现“冲突”情况。关于这部分问题不了解的朋友可以看这篇文章:https://blog.youkuaiyun.com/qq_36771269/article/details/79728243

 

关于字符串hash

问题:如果将一个二维整点P的坐标映射为一个整数,使得整点P可以由该整数唯一地代表。假设一个整点P的坐标是(x,y),其中0<=x,y<=Range,那么可以令hash函数为H(P)=x*Range+y,这样对数据范围内的任意两个整点P1与P2,H(P1)都不会等于H(P2),就可以用H(P)来唯一地代表该整点P,接着便可以通过整数hash的方法来进一步映射到较小的范围。

先假设字符串均由大写字母A~Z构成,不妨把A~Z视为0~25,这样就把26个大写字母对应到二十六进制中。按照二十六进制转换为十进制的思路,由进制转换的结论可知,在进制转换过程中,得到的十进制肯定是唯一的,由此便实现将字符串映射为整数的需求。

int hashFunc(char S[],int len){
	int id=0;
	for(int i=0;i<len;i++){
		id=id*26+(S[i]-'A');
	}
	return id;
}

如果字符串S的长度比较长,那么转换成的整数也会很大,因此需要注意使用len不能太长。如果字符串出现了小写字母,那么可以把A~Z作为0~25,而把a~z作为26~51,这样就变成五十二进制转换为十进制的问题,做法是相同的:

int hashFunc(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(S[i]>='a'&&S[i]<='a'){
			id=id*52+(S[i]-'a')+26;
		}
	}
	return id;
}

如果出现数字,一般有两种处理方法:

1、按照小写字母的处理方法,增大进制数至62;

2、如果保证在字符串的末尾确定个数的数字,那么就可以把前面英文字母的部分按上面思路转换成整数,再将末尾的数字直接拼接上去。例如对由三个字符加一位数字组成的字符串“BCD4”来说,就可以先将前面的“BCD”转换为整数731,然后直接拼接上末位的4变为7314.

int hashFunc(char S[],int len){
	int id=0;
	for(int i=0;i<len-1;i++){
		id=id*26+(S[i]-'A');
	}
	id = id*10+(S[len-1]-'0');
	return id;
}

以一个问题结尾:给出N个字符串(恰好三位大写字母组成),再给出M个查询字符串,问每个查询字符串在N个字符串中出现的次数

#include<cstdio>
const int maxn = 100;
char S[maxn][5],temp[5];
int hashTable[26*26*26+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 main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++){
		scanf("%s",S[i]);
		int id=hashFunc(S[i],3);
		hashTable[id]++;
	}
	for(int =0;i<m;i++){
		scanf("%s",temp);
		int id = hashFunc(temp,3);
		printf("%d\n",hashTable[id]);
	}
	return 0;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值