• 哈希表之分离链接


      哈希表即散列表,用于存储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和计算哈希值时的&运算。避免求余运算符,直接用位运算截取低位。

  • 相关阅读:
    SDK Hello world(直接使用SDK封装)
    一个类有多个基类的内存布局
    写个测试程序看看磁盘映像文件中哪个扇区可以使用?
    在服务中以当前用户身份启动一个程序
    在类有成员变量的场景下, 按照虚表原理, 模拟虚函数实现
    pipe----管道
    Qt序列化格式分析(qint,QString)(非常简单好用)
    模拟QQ系统设置面板实现功能
    Qt持久性对象进行序列化(同时比较了MFC与Java的方法)
    使用srvany.exe将任何程序作为Windows服务运行
  • 原文地址:https://www.cnblogs.com/zcsor/p/6346864.html
Copyright © 2020-2023  润新知