Hash Function
题目描述
For a given set S={a0,a1,a2,...,an−1}S = \{a_0, a_1, a_2, ..., a_{n-1}\}S={a0,a1,a2,...,an−1}, we called a hash function fS(x)f_S(x)fS(x) perfect hash function of S, if it satisfies ∀0≤i<j<n,fS(ai)≠fS(aj)\forall 0 \leq i < j < n, f_S(a_i) \neq f_S(a_j)∀0≤i<j<n,fS(ai)=fS(aj).
Given a set S with non-negative integers, please find the minimum positive integer seed that function hseed(x)=xmod seedh_{seed}(x) = x \mod seedhseed(x)=xmodseed is a perfect hash function of S.
输入描述:
The first line of input contains one integer $n$, describing the size of set S.
The second line contains n(1≤n≤500000)n(1 \leq n \le 500000)n(1≤n≤500000) integers a0,...,an−1a_0,...,a_{n-1}a0,...,an−1, describing the elements in S.
It is guaranteed that ∀0≤i<j<n,ai≠aj,0≤ai≤500000\forall 0 \leq i < j < n, a_i \ne a_j, 0 \le a_i \le 500000∀0≤i<j<n,ai=aj,0≤ai≤500000.
输出描述:
Output one integer in a line, indicating the answer.
示例1
输入
复制
3
2 3 4
输出
复制
3
示例2
输入
复制
3
1 2 4
输出
复制
4
示例3
输入
复制
10
0 7 23 18 29 27 6 28 24 11
输出
复制
15
题意:给出的散列集合里面是一些互不相等的数,我们需要找一个最小的正整数x,使集合里的每个数mod x之后仍是互不相等的数。
思路:先将散列集合由小到大排序,排序完最大的数为a[n]。首先考虑mod,因为给的集合的数互不相同,所以无论给的数再小,最大的也应该>=n,所以最小的mod应该在
n - a[n]+1之间,其次考虑如果集合里的数比mod小,求余后仍为mod,所以一开始就标记所有在集合里的数为true,等会逆着遍历时如果比mod大的数求余后仍和原来集合已经存在的数不相同,就满足条件。
代码如下:
#include <bits/stdc++.h>
using namespace std;
#define N 500010
int n,a[N];
bool vis[N];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
vis[a[i]]=true;
}
sort(a+1,a+n+1);
for(int i=n;i<=a[n]+1;i++){//最大比集合中最大的数大一,因为如果模出来都等于自身,也是可以的,且满足最小
int flag=1;
for(int j=n;a[j]>=i;j--){//比i小的模i模模出来是自身,所以只考虑>=i的
if(vis[a[j]%i]){
flag=0;
break;
}
}
if(flag){
cout<<i<<endl;//满足条件的最小值,输出,并结束循环
break;
}
}
return 0;
}
附上一点关于散列的知识:
原理参考《算法导论》
一、除法散列函数
根据描述实现算法,先取对应种子
int hash_mod_seed(int n, int iCheck) {//存放n个关键字,一次不成功的查找要检查iCheck个关键字,默认一个字符是8位
int iStart= n / iCheck, prime = (iStart == 1) ? 2 : iStart;
assert(iCheck >= 0 && iCheck <= 8);
//odd起始要跳过已经判断了的奇数
for (int j = 0, odd = (iStart % 2 == 0) ? iStart / 2 : (iStart - 1) / 2 + 1;
j < 8 - iCheck; odd++) {
//生成一个素数
bool fPrime = true;
for (int k = 2; k <= sqrt(prime); k++)
if (prime % k == 0) {
fPrime = false;
break;
}
if (fPrime) //记录素数
j++;
prime = odd * 2 + 1;//待判断的奇数
}
return prime - 2;
}
关键算法实现
int hash_mod(int key, int seed) {
return key % seed;
}
二、乘法散列函数
根据算法描述实现(应用时,slot大小是2^p,此函数无论如何映射,不会超出slot大小)
int hash_mul(int key, int w, int p) {//w指关键字int是32位,p是映射后截取的最高有效位
__int64 s = (sqrt(5) - 1) / 2 * pow(2, w);
__int64 r0 = s*key % (__int64)pow(2, w);
return r0 / pow(2, w - p);//高p位有效位
}
三、全域散列
原理是:假设数组有n个元素,通过除法散列算法中的hash_mod_seed函数取得一个素数种子R。利用系统的随机函数并采用取模的方法生成一个长度位R的数组A,在进行全域哈希时,通过关键字key生成一个类似数组A的数组B。最后,散列的主要算法核心是求A[0]*B[0]+....+A[R-1]*B[R-1]的模(被除数是R*R)
全域散列种子生成
int *hash_seed(int *pKey, int R) {//pKey为NULL时表示初始种子,并作为hash_full函数中的v参数传入
int *v = new int[R], key;
memset(v, 0, R * sizeof(R));
if (pKey == NULL) {
srand(time(NULL));
for (int i = 0; i < R; i++)
v[i] = rand() % R;
}
else {
key = *pKey;
for (int i = 0; i < R && key; i++) {
v[i] = key % R;
key = key / R;
}
}
return v;
}
对应的种子释放
void hash_seed_free(int *v) {
delete[] v;
}
关键函数,全域散列算法实现(应用时,slot大小是R*R)
int hash_full(int key, int R, int *v) {//R是hash_mod_seed(n, 3),M是hash_mod_seed^2
int slot = 0, M = R*R;
int *numV = hash_seed(&key, R);
for (int i = 0; i < R; i++)
slot += numV[i] * v[i];
hash_seed_free(numV);
return slot % M;
}
为了方便大家测试代码,我还是补一个全域散列的打印函数
void hash_full_print(int *A, int n) {
int R = hash_mod_seed(n, 3);
int *v = hash_seed(NULL, R), *hash = new int[R*R];
memset(hash, 0, sizeof(int)*R*R);
printf("hash seed=%d, slot size=%d\n", R, R*R);
for (int i = 0; i < n; i++) {
int m = hash_full(A[i], R, v);
while (hash[m]) //若数据存在,则循环后移一位,注意:当散列全满了,这里是一个死循环,实际应用中,请自行添加判断
m = (m + 1) % (R*R);
hash[m] = A[i];
printf("[%d] is %d\n", m, hash[m]);
}
hash_seed_free(v);
delete[] hash;
}
结果补一张(很明显至少有一个碰撞,但是通过后移一位,解决了这个问题,总体来说效果很不错。最后完全散列很容易在此基础上修改,就不发代码了。各位创新下!!!思路就是在每个对应hash值处,生成一个R*R链表,看需求实现吧!!!):
第二次运行就不存在碰撞了,上一个测试图吧。
所有代码均经过测试,结果正确