PKU ACM 3274 数组hash

先说说我对数组哈希的理解,加入现在我们有 按照顺序输入若干个数组,然后找到其中一对相等的数组。
很显然这个问题,如果用暴力的方法复杂度为O(n^2), 如果借用排序的话 复杂度可以变成 n * log(n)

因为我们的目的仅仅是找到一对相同的数组,所以其实我们没有必要排序,因为排序处理了所有元素,自然看起来没有那么必要,这个时候,我们可以借用hash的思想,将一个数组映射到一个数字,然后,如果两个数组映射到相同的数字,那么他们可能是相等的,也可能是冲突,通过比较原来的数组,就能知道两个数组是不是一样了。如果hash函数设计的比较好的话,效率可以达到 O(n),但是要付出额外的存储代价!

假设 unsigned int hashcode(char *v, int size)返回数组的哈希值
int hash[1000000] 是一个哈希的bucket,采用开放地址法
则工作流程如下
INPUT nArray //输入数组的数量
WHILE( i < nArray)
{
       INPUT Array[i];
       code = hashcode(Array[i]);
       while(hash[code] != -1) //用开放地址法
       {
              if(Array[hash[code]] EQUAL Array[i])
                             // Array[i] == Array[hash[code]]
              else
                      code++;//开放地址递增
       }
       if(hash[code] == -1) // 之前没有人存储过--处女地
              hash[code] = i;
       i++;
}


这里一个比较关键的一点是如何设计比较好的哈希函数,哈希函数设计的好坏直接决定效率,例如如果哈希函数聚集严重(生成的哈希code都是1)这个时候的复杂度就变成了 n^2 完全没有任何优势
UNIX里面有一个字符串哈希的构造法,不过不知道为什么这么构造,可能蕴含某种高深的数学原理
int ELFhash(char *key)

    unsigned long h=0;
    while(*key)
    { 
        h=(h<<4)+*key++;
        unsigned long g=h&0Xf0000000L;
        if(g) h^=g>>24;
        h&=~g;
    }
    return h%MOD;
}


下面来说这道OJ上的题目
假设 a[i][j] 存储的是 第 i 头牛的 第j个属性,sum[i][j] 存储的是 a[0][j] + ... a[i][j] 的值
我们要找到就是 sum[i][0] - sum[j][0] == sum[i][1] - sum[j][1] == sum[i][2] - sum[j][2] == sum[i][k-1] - sum[j][k-1] 成立的最大 i - j
转换为 
sum[i][1] - sum[i][0] = sum[j][1] - sum[j][0];
sum[i][2] - sum[i][0] = sum[j][2] - sum[j][0];
..
sum[i][k-1] - sum[i][0] = sum[j][k-1] - sum[j][0];
同时成立 等价于
转化为这么写的好处,使得一侧只有 i 另一侧只有j,这样在一次扫描采用哈希就可以完成任务了
如何利用哈希呢
C[i][1..k-1] = sum[i][1..k-1] - sum[i][0]
...
然后 哈希 C[i], 这样就可以一边读入一边找了,当找到哈希一样的两个C,然后找出之前的j,当前为i,求出最大 i-j


#include <stdio.h>
#include <string.h>
#define maxn 100010
const int prime = 99983;
int n,k;
int a[maxn][31],sum[maxn][31],C[maxn][31];
int hash[1000000];
int ans;
unsigned int hashcode(int *v,int k)
{
	int i;
	unsigned int p = 0;
	for( i = 0; i < k; i++)
	{
		p += v[i] << i; 
		//p = ((p<<2)+(v[i]>>4))^(v[i]<<10);
	}
	p = p % prime;
	return p;
}
int main()
{
	int i,j;
	int code;
	int v[31];
	memset(hash,-1,sizeof(hash));
	memset(sum,0,sizeof(sum));
	memset(C,0,sizeof(C));
	scanf("%d%d",&n,&k);
	hash[hashcode(C[0],k)] = 0;
	unsigned int p;
	ans = 0;
	for( i = 1; i <= n; i++)
	{
		//假设第一个元素的hashcode是0
		scanf("%d",&code);
		for( j = 0; j < k; j++)
		{
			a[i][j] = code % 2;
			code >>= 1;
			sum[i][j] += sum[i-1][j] + a[i][j];
			C[i][j] = sum[i][j] - sum[i][0];
		}
		p = hashcode(C[i],k);
		while(hash[p] != -1)
		{
			for( j = 1; j < k; j++)
			{
				
				if(C[hash[p]][j] != C[i][j] )
				{
					break;
				}
			}
			if(j == k)
			{
				if(i - hash[p] > ans)
				{
					ans = i - hash[p];
				}
				break;
			}
			p++;

		}
		if( hash[p] == -1)
		{
			hash[p] = i;
		}
	}
	printf("%d\n",ans);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值