哈希表实战经验

哈希表的作用:简单来说就是将一个庞大的值域(复杂的数据结构)映射到一个较小的空间(例如0 ~ N,N 为1e5或1e6等比较小的数)

通常写哈希函数最简单方法就是:例如:h(x) = x mod 10^5,将很大的x映射到10^5内。但是往往会面临冲突,因为值域太大,经过哈希函数的计算映射的值相同。那么如何处理冲突呢?线性探测、二次探测、开链

这里介绍哈希表的两种存储结构:(1)开放寻址法(这里采用线性探测)(2)拉链法

概念:(1)负载系数:待插入元素的个数 / 一维数组的大小

1、拉链法  负载系数大于1

如图所示:开(例:10^5)个槽(一个一维数组),将相同映射值接在每一个槽的下面,相当于一条拉链,单链表

哈希表为一种期望算法,在平均情况下,每一条链的长度都可以看作常数,因此时间复杂度通常是很小的O(1),添加,查找,删除的话就开一个bool变量进行标记(惰性删除)。

代码例程:实现插入和查询两种操作

#include <iostream>
#include <cstring>

using namespace std;

const int N = 100003;   //  取大于100000的最近的质数,离2的整次幂尽量远,冲突的概率最小

int h[N];   //槽
int e[N], ne[N];    //单链表
int idx;    //当前操作数

void insert(int x)
{
    int k = (x % N + N) % N;    //计算哈希值的一种方式,可以保证哈希值为正
    //插入单链表,从头插入
    e[idx] = x;
    ne[idx] = h[k];
    h[k] = idx ++;
}

bool find(int x)
{
    int k = (x % N + N) % N;

    //遍历单链表
    for(int i = h[k]; i != -1; i = ne[i])
        if(e[i] == x)
            return true;

    return false;
}

int main()
{
    int n;
    scanf("%d", &n);

    memset(h, -1, sizeof h); //每个槽填充-1,相当于使单链表表头指向-1

    while(n --)
    {
        char op[2];
        int x;
        scanf("%s%d", op, &x);

        if(*op == 'I') insert(x);
        else
        {
            if(find(x)) puts("Yes");
            else puts("No");
        }
    }

    return 0;
}

SGI STL中的hash table采用的是拉链法,

2、开放寻址法

没有开拉链,形式上简单些,经验上他开的一维数组要开到两到三倍,这样冲突就会比较低了,处理冲突的方式,类似于上厕所,这个位置有人,则去下一个。

代码例程:实现插入和查找

#include <iostream>
#include <cstring>

using namespace std;

const int N = 200003;   //经验值将数组开两到三倍大小,此处开两倍,最接近的质数
const int null = 0x3f3f3f3f;    //设置大于1e9的数字,只要不在我们的值域范围内就可以

int h[N];

int find(int x)
{
    int t = (x % N + N) % N;    //哈希值
    while(h[t] != null && h[t] != x)    //若该位置非空,但不是x,哈希值相同的值放的位置是相邻的
    {
        t++;    //找下一个
        if(t == N) t = 0;   //找到尽头,就从0开始
    }

    return t;   //若找到,返回的就是x在的位置,若没找到,返回的就是x应该存放的位置
}

int main()
{
    memset(h, 0x3f, sizeof h);  //memset是按字节填充的,h为int,占4字节,则填充为0x3f3f3f3f

    int n;
    scanf("%d", &n);

    while(n--)
    {
        char op[2];
        int x;
        scanf("%s%d", op, &x);
        if(*op == 'I') h[find(x)] = x;
        else
        {
            if(h[find(x)] == null) puts("No");
            else puts("Yes");
        }
    }

    return 0;
}

此处采用的是线性探测,也可以采用二次探测,即使用哈希函数得到元素在数组中的位置为H,但是这个位置被使用了,那么可以依次尝试H + 1^2, H + 2^2, H + 3^2, …, H + i^2。在这里,我们只要保证数组的大小为质数,并且负载系数在0.5以下(超过0.5需要重新配置并重新整理表格),那么就可以确定每插入一个元素所需要的探测次数不多于2。

3、字符串的哈希,预处理前缀的哈希

代码例程:实现判断一个长串中任意两个字串是否相同

#include <iostream>
#include <algorithm>

using namespace std;

typedef unsigned long long ULL; //溢出ULL相当于对2^64取模

const int N = 100010, P = 131;  //经验值,或者取13331

int n, m;
char str[N];
ULL h[N], p[N];

ULL get(int l, int r)   //获得l,r之间的哈希值
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

int main()
{
    scanf("%d%d", &n, &m);
    scanf("%s", str + 1);   //从1开始

    p[0] = 1;
    for(int i =1; i <= n; i++)
    {
        h[i] = h[i - 1] * P + str[i];   //计算前缀哈希值,str[i]转换为ascii码
        p[i] = p[i - 1] * P;    //存储每一位上的幂次
    }

    while(m --)
    {
        int l1, r1, l2, r2;
        scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
        if(get(l1, r1) == get(l2, r2)) puts("Yes");
        else puts("No");
    }

    return 0;
}

4、STL中的set和map

  set/multiset
        insert()  插入一个数
        find()  查找一个数
        count()  返回某一个数的个数
        erase()
            (1) 输入是一个数x,删除所有x   O(k + logn)
            (2) 输入一个迭代器,删除这个迭代器
        lower_bound()/upper_bound()
            lower_bound(x)  返回大于等于x的最小的数的迭代器
            upper_bound(x)  返回大于x的最小的数的迭代器
    map/multimap
        insert()  插入的数是一个pair
        erase()  输入的参数是pair或者迭代器
        find()
        []  注意multimap不支持此操作。 时间复杂度是 O(logn)
        lower_bound()/upper_bound()

unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
    和上面类似,增删改查的时间复杂度是 O(1)
    不支持 lower_bound()/upper_bound(), 迭代器的++,--


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值