哈希表即散列表,用于存储key-value对,key作用和数组下标相似,只是其key是经过一定加工(hashfunction)之后得出的。其目的主要是加速查找,也可以认为以散列函数的时间消耗换取了对数组空间的紧缩。一般说来,设计一个合理的散列函数是关键——好的散列函数可以使得存储空间被充分利用,亦即减少冲突的发生。但无论如何,冲突都是存在的,多数情况下,我们需要检测并处理冲突。处理方法是多种多样的,可以沿着key+n的方向继续查找空的位置,也可以另开一个哈希表采用不同的散列函数,也可以在当前位置保存一个链表。相对于安全性和编码而言,在当前位置使用链表更突出,但其效率相对低一些。这里,以一个NOI题目为例,介绍分离链接法解决哈希表冲突:
1807:正方形 查看 提交 统计 提问 总时间限制: 8000ms 单个测试点时间限制: 4000ms 内存限制: 65536kB 描述 给出平面上一些点的坐标,统计由这些点可以组成多少个正方形。注意:正方形的边不一定平行于坐标轴。 输入 输入包括多组测试数据。每组的第一行是一个整数n (1 <= n <= 1000),表示平面上点的数目,接下来n行,每行包括两个整数,分别给出一个点在平面上的x坐标和y坐标。输入保证:平面上点的位置是两两不同的,而且坐标的绝对值都不大于20000。最后一组输入数据中n = 0,这组数据表示输入的结束,不用进行处理。 输出 对每组输入数据,输出一行,表示这些点能够组成的正方形的数目。 样例输入 4 1 0 0 1 1 1 0 0 9 0 0 1 0 2 0 0 2 1 2 2 2 0 1 1 1 2 1 4 -2 5 3 7 0 0 5 2 0 样例输出 1 6 1
这个题目要求我们查找正方形,方法也是多种多样的。但是效率较高的算法是这样的:取任意两点作为正方形对角线AC,则若另一条对角线的两个端点B、D也在给定数据中,则找到一个正方形。这样,问题就变为:遍历点集中每两点A,C,求B,D坐标,在点集中查找B,D;若B、D均在点集中,则找到一个正方形。遍历两点的方法和冒泡排序一样,剩下的问题就是查找另外两点,如果可以用一个桶来标记点的存在就好了,但是题目数据很大,即使使用BITARRAY也难于达到要求,所以我们需要把点集散列存储。需要注意的是,每个正方形的两条对角线如果我们不做限制,则都可用作确定正方形,即得到的正方形个数为两倍。因为代码已经达到300ms完成测试,所以此处没有进行限定对角线的角度:
#include<iostream> #include<cstring> using namespace std; const int htsize=(1<<20); struct point{ int x; int y; point *next; //用于分离链接 }ps[1005]; point* ht[htsize]; int hsfunc(point p){ return ((p.x+1)*17 + p.y )&(htsize-1); } void writehash(point *p){ int hash=hsfunc(*p); if(ht[hash]==0){ ht[hash]=p; }else{ point *cp=ht[hash]; while(cp->next!=0){ cp=cp->next; } cp->next=p; } } bool readhash(point* p){ int hash=hsfunc(*p); if(0<=p->x && p->x<=80000 && 0<=p->y && p->y<=80000 && ht[hash]>0){ point *cp=ht[hash]; while(cp!=0){ if(cp->x==p->x&&cp->y==p->y){ return true; } cp=cp->next; } } return false; } int rectcnt(int pointcnt){ int i,j,rectcnt=0; point p1,p2; for(i=0;i<pointcnt;i++){ for(j=i+1;j<pointcnt;j++){ //做对角线看待,计算另外两点。此时一个正方形只有两条对角线被计算。 p1.x=(ps[i].x+ps[j].x+ps[j].y-ps[i].y)/2; p1.y=(ps[i].y+ps[j].y+ps[i].x-ps[j].x)/2; p2.x=(ps[i].x+ps[j].x-ps[j].y+ps[i].y)/2; p2.y=(ps[i].y+ps[j].y-ps[i].x+ps[j].x)/2; if(readhash(&p1)&&readhash(&p2)){ rectcnt++; } } } return rectcnt/2; } int main(){ int i,cnt; cin>>cnt; while(cnt!=0){ memset(ht,0,sizeof(ht)); for(i=0;i<cnt;i++){ cin>>ps[i].x>>ps[i].y; ps[i].x=(ps[i].x+20000)*2; //增加哈希表覆盖率,避免对角线计算时出现小数 ps[i].y=(ps[i].y+20000)*2; ps[i].next=0; writehash(&ps[i]); } cout<<rectcnt(cnt)<<endl; cin>>cnt; } }
代码中有几处小技巧:
1、main函数中对p.x,p.y进行处理,因为求对角线BD的时候需要/2,为了避免浮点数计算先*2。
2、readhash函数中限定了点在(0,0),(80000,80000)矩形范围内减少不必要的查找。
3、设定哈希表大小hashsize和计算哈希值时的&运算。避免求余运算符,直接用位运算截取低位。