菜狗复习系列:(1)哈希表

什么情况下我们需要哈希表?

将一个庞大、复杂的空间或者值域映射到比较小的空间

例:

  • 0-10^9 ->0-10^5

将-10^9~10^9通过h(x)输出为0~10^5,这就是哈希函数

哈希函数怎么写?   

  1. 直接取模:x mod N (N尽量取成一个质数,这样冲突的概率最小)

  1. 出现冲突:把若干不同的数映射为了同一个数

按照冲突的处理方式,我们得到两种方法,即开放寻址法和拉链法

基础知识

  1. 存储结构

  1. 开放寻址法

思路:开一个一维数组,长度得是数据范围的两到三倍

添加:从第x mod N 个位置开始找,如果已被占用,则寻找下一个位置

查找:……

int find(int x)
{
    int k=(x%N+N)%N;
    
    while(h[k]!=null&&h[k]!=x)
    {
        k++;
        if(k==N) k=0;
    }
    return k;
}
  1. 拉链法

开一组表头h[N],使用单链表存储取模结果相同的一类数,删除操作一般用布尔值标记一下

  1. 字符串哈希方式

字符串前缀哈希法

设str="ABCDEF"

首先我们要求这些字符串前缀的哈希值

h[1]="A"的hash值

h[2]="AB"的hash值

h[3]="ABC"的hash值

……

如何定义这些字符串的hash值?

采用p进制

这样我们可以将任一字符串映射到0~Q-1,即可求出字符串的每个前缀hash值

注意点:

  1. 避免将某个字符映射为0;

  1. 当p=131或133331,Q=2^64,几乎不会出现冲突

利用哈希的前缀来处理所有的字符串的哈希值

左边为高位,右边为地位

则最高位为p^L-1-1,最低位为p^R-1

所以,该段字符串的哈希值为 h[R] - h[L - 1] * P ^ (R - L + 1)

由此我们可以通过对字符串的哈希函数映射

字符串哈希

#include<iostream>
using namespace std;

const int N=1e5+5,P=131;//不易冲突 

long long h[N],p[N];
//存储哈希值,存储p进制下的每个数位上的单位1 

long long query(int l,int r)
{
    return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
    int n,m;//字符串长度和询问次数 
    cin>>n>>m; 
    string x;
    cin>>x;
    
    p[0]=1;//初始化 
    h[0]=0;
    
    for(int i=0;i<n;i++)
    {
        p[i+1]=p[i]*p;
        h[i+1]=h[i]*P+x[i];//p进制下进行映射,开放寻址法
                           //获取字符串的前缀哈希值 
     }
    
    while(m--)
    {
        int l1,l2,r1,r2;
        cin>>l1>>r1>>r1>>r2;
        if(query(l1,r1)==query(l2,r2)) printf("Yes\n");
        else printf("No\n");
    }
  
    return 0;
 } 

例题:AcWing 2058. 笨拙的手指

选自蓝桥杯每日一题,但是我感觉除了用到unordered_set,感觉和哈希没啥关系

大致题意:已知某个数的二进制下错误某一个数位和三进制下错误某一个数位的结果,求该数

解题思路:求可能的二进制下的答案和可能的三进制的答案,求交集

#include<iostream>
using namespace std;

//将b进制的数转化位十进制
int get(string s,int b)
{
    int res=0;
    for(auto c:s)
    {
        res=res*b+c-"0";
    }
    return res;
}
int main()
{
    string a,b;
    cin>>a>>b;
    
    unordered_set<int> S;
    
    //求可能的二进制数 
    for(auto& c:a)
    {
        c^=1;//将二进制数的每一位取反
        S.insert(get(a,2));
        c^=1;
    }
    //求可能的三进制数
    for(auto& c:b)
    {
        char t=c;
        for(int i=0;i<3;i++)
        {
            if(i+'0'!=t)//与原数相异的另外两种情况 
            {
                c=i+'0';
                int x=get(b,3);
                if(S.count(x))//出现交集,输出答案 
                {
                    cout<<x<<endl;
                    return 0; 
                 } 
            }    
        }
        c=t;
     }
    return 0;
 } 

for(auto &c:s)与for(auto c:s)的区别:

使用for(auto &c:s)时,直接引用原字符串进行遍历操作。

该题求可能的正确的二进制数三进制数时使用&是为了使字符c改变时,字符串a与之对应,所以直接引用原字符

使用for(auto c:s)时,逻辑上会复制一个s字符串再进行遍历操作。

该题将b进制的数转化位十进制不需要改变原字符,只是单纯地遍历字符串的每一位

将b进制的数转化位十进制: 秦九韶算法

解释:10101=((((1*2+0)*2+1)*2+0)*2+1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

社恐不参团

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值