数据结构与算法-散列
散列的引入
考虑下面的问题
给出N个正整数,再给出M个正整数,其中N,M<=105,问M中的每个数是否在N中出现过?
最直观的解法:首先需要把N个数据存储起来,然后遍历M个数据,拿到每个M后,再遍历N,判断是否存在一个数与当前待判断数相等,时间复杂度为O(NM)。
有一种空间换时间的解法:有一个大小为105的数组,数组中存储bool变量。读取N的时候,以N中的数作为数组下标,把数组下标对应位置的bool变量设置为true。再遍历M,把M中的数作为数组下标直接随机访问,如果为true,代表出现过。时间复杂度O(N+M)。
c实现
#include <stdio.h>
bool hashtable[100000] = {false};
void hashSearch(){
int n = 0, m = 0;
int tmp = 0;
scanf("%d%d", &n, &m);
for (int i = 0; i < n; ++i) {
scanf("%d", &tmp);
hashtable[tmp] = true;
}
for (int i = 0; i < m; ++i) {
scanf("%d", &tmp);
if (hashtable[tmp]) {
printf("%d,YES\n", tmp);
} else {
printf("%d,NO\n", tmp);
}
}
}
int main() {
hashSearch();
return 0;
}
散列的定义
上面的做法,通过把输入的数字作为数组下标来对数字的某些性质进行统计,后续再查询时,也是通过数组的随机访问直接定位,查找效率很高。
但是现实中,首先数字可能很大,或者输入的是字符串,不能直接作为数组下标了,如何处理?
散列 关注的就是这个转换的过程,进一步说,散列使元素通过一个函数转换为整数,并使得该整数尽可能唯一的代表这个元素。
散列函数
如果待散列的元素为整数,有以下常见的散列函数:
- 直接定址法:采用 元素本身 (上面例题中的做法)或者元素的线性函数来定址,适合表元素数量较小的情况。
- 数字分析法:分析待散列元素数字,通过抽取部分分布均匀的数字来作为散列地址,适合元素本身位数较大,并且数字分布有规律的情况。(比如手机号,身份证号等等)
- 平方取中法:数字平方后,取中间的N位,适合 位数不大,不知道数据分布的情况
- 叠加求和法:把一个多位数字按照位数 切割 分成多个数字,这些数字求和,取模。适合位数较大,不知道数据分布的情况
- 除留余数法:取模得到散列地址,需要注意,除数尽量选择 小于等于表长的 素数。
如果带散列的元素为字符串,介绍一个散列函数,进制hash法:
假设字符中只包含A到Z这26个大写字母,A到Z可以视为0到25,那么这个字符串可以看作是一个26进制的数,将26进制转换为10进制,就得到了一个整数。
从N进制转换为十进制的算法这里就不再细说,假设读者已经有了这个基础。
上面的字符串hash,可以转换为下面的C实现代码:
void hashFunc(char str[]) {
int id = 0;
for (int i = 0; i < strlen(str); ++i) {
id = id * 26 + (str[i] - 'A');
}
printf("%s", id);
}
重点解释一下下面这行
id = id * 26 + (str[i] - 'A');
以一个字符串“ZZZ”为例,计算过程如下:
可以看到,这个公式最终可以转换为N进制转换为10进制的公式。