—— 哈希表 ( H a s h T a b l e ) (Hash~~Table) (Hash Table)作为一种高效的数据结构,它正在竞赛中发挥着越来越重要的作用。
简单解释:
我们使用一个下标范围比较大的数组来存储元素。可以设计一个函数( h a s h hash hash函数, 也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标)相对应,于是用这个数组单元来存储这个元素;
构建hash函数:
常用的有以下几种方法:
- 除余法
取一个正整数m,用m去除关键码,取其余数作为地址,即: h ( K e y ) = K e y m o d m h(Key)= Key\mod m h(Key)=Keymodm
- 数字分析法
关键码的位数比存储区域的地址的位数多,在这种情况下可以对关键码的各位进行分析,丢掉分布不均匀的位留下分布均匀的位作为地址。
- 平方取中法
将关键码的值平方,然后取中间的几位作为散列地址。
- 折叠法
折叠法是将关键码从某些地方断开,分关键码为几个部分,其中有一部分的长度等于地址码的长度,然后将其余部分加到它的上面,如果最高位有进位,则把进位丢掉。
例: 如关键码Key=58422241,要求转换为3位的地址码。
分析:分如下3段:5 8 4 | 2 2 2 | 4 1,则相加:
5 8 4
2 2 2
4 1
8 4 7
h(Key)=84
冲突处理:
线性重新散列技术易于实现且可以较好的达到目的。
令数组元素个数为 S S S ,当 h ( k ) h(k) h(k) 已经存储了元素的时候,
依次探查 ( h ( k ) + i ) m o d S ( i ∈ N ∗ ) (h(k)+i) \mod S\quad (i\in N^*) (h(k)+i)modS(i∈N∗) ,直到找到空的存储单元为止
如果探查完整个哈希表都没有空位,那么就是 h a s h hash hash数组开小了。
hash数组主要功能:
- 插入元素;
- 查找元素。
例题:
SSL1125 集合
思路:
注意到这个问题的本质就是对于给定的集合A ,确定集合B中的元素是否在集合A中。所以,我们使用 h a s h hash hash表来处理。至于哈希函数,只要按照除余法就行了, H ( x ) = x m o d m H(x) = x \mod m H(x)=xmodm.
补充: m m m尽量是一个大质数,如:15889,149993,100003等。
代码
#include<iostream>
#include<cstdio>
#include<cmath>
#define m 149993
using namespace std;
long long n,n2,x,ans;
long long a[m];
int h(int y) //hash
{
return y%m;
}
int hw(int z) //集合A里每个元素在hash数组里定位
{
int j=0;
int hx=h(z);
while(j<m&&a[(hx+j)%m]!=0&&a[(hx+j)%m]!=x)
j++;
return (hx+j)%m;
}
int main()
{
scanf("%lld",&n);
for(int i=1; i<=n; i++)
{
scanf("%lld",&x);
a[hw(x)]=x;
}
scanf("%lld",&n2);
for(int i=1; i<=n2; i++)
{
scanf("%lld",&x);
if(a[hw(x)]==x) //对比 A 和 B
ans++;
}
if(ans==n&&ans==n2) cout<<"A equals B";
else if(ans==n) cout<<"A is a proper subset of B";
else if(ans==n2) cout<<"B is a proper subset of A";
else if(ans==0) cout<<"A and B are disjoint";
else cout<<"I'm confused!";
return 0;
}
SSL1692 魔板
思路:
该题目很明显是用搜索做,如果用深度优先搜索,则可能出现死循环,而且时间很长,所以只能用宽度优先搜索。但是如果没有一种好的方法来判断是否已经加入列表,时间可能会超时,所以: h a s h hash hash表。按照初始状态标号,则共有8!=40320个情况。根据公式:
可以将每一种情况唯一对应到0~40319中的一个数。再用宽度优先搜索可以很轻松的实现这一算法。
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#define p 100003
using namespace std;
const int ci[3][8]={
{
8,7,6,5,4,3,2,1},{
4,1,2,3,6,7,8,5},{
1,7,2,4,5,3,6,8}};
int c,num[p],fa[p],tail,head;
string mb,a[p],s[p];
char dl[p];
bool hash(string s)
{
int ans=0;
for(int i=0; i<8; i++)
ans=(ans<<3)+(ans<<1)+s[i]-48; //乱搞hash
int j=0,hx=ans%p;
while(j<p&&a[(hx+j)%p]!=""&&a[(hx+j)%p]!=s) //将 hash 和 定位 结合
j++;
if(a[(hx+j)%p]=="")
{
a[(hx+j)%p]=s;
return 0;
}
else
return 1;
}
void bfs()
{
hash("12345678");
s[1]="12345678";
head=0,tail=1;
while(head<tail)
{
head++;
for(int i=0; i<3; i++)
{
tail++;
num[tail]=num[head]+1;
fa[tail]=head; //存储路径,方便输出
s[tail]="";
if(i==0) //存储状态
dl[tail]='A';
else if(i==1)
dl[tail]='B';
else if(i==2)
dl[tail]='C';
for(int j=0; j<8; j++)
s[tail]+=s[head][ci[i][j]-1];
if(hash(s[tail]))
tail--;
else if(s[tail]==mb)
return;
}
}
}
void write(int x)
{
if(x==1) return;
write(fa[x]); //回到每个状态输出
printf("%c",dl[x]);
}
int main()
{
for(int i=1; i<=8; i++)
{
scanf("%d",&c);
mb+=c+48;
}
if(mb=="12345678")
printf("0");
else
{
bfs();
printf("%d\n",num[tail]);
write(tail);
}
return 0;
}
当然,hash的学习远不止这些,剩下的得由自己慢慢探索… …
推荐几道题给大家练习:
- 洛谷P1102
- 洛谷P4305
- 洛谷T24242