首页 > 其他分享 >.哈希表.

.哈希表.

时间:2024-05-26 13:05:13浏览次数:14  
标签:取模 hash 哈希 int scanf str

哈希

哈希表:将大而复杂的数据映射到紧凑的区间内。分为:①存储结构 

(离散化是特殊的哈希,之前讲的离散化是严格保序的 映射到区间上是连续递增的)


哈希不保序,这里讲的是一般的哈希

弊端:若数据的数值较大可以对其取模后再映射,但是取模后可能造成:
之前不相同的数值取模后映射到同一位置上了,所以引入:开放寻址法拉链法

①存储结构 

{


    1(开放寻址法


    {
    添加:
        开一个一维数组h[],长度一般要开到题目要求长度的2到3倍
        1'假设a取模后的值为k,那么就将a放到h[k]上
        2'若b取模后的值仍为k,那么就将b放到h[k+i]上(即向后遍历,直到找到一个没存放数值的h[k+i]为止)

注意:由于这里数组h[ ]的长度为题设要求的2~3倍,所以一定能找到一个位置满足上述1’,2’两种情况
        ...
    查找:
        如果当前h[k]存在并且h[k]==x那么就找到了,如果!=x那么就继续向下寻找
        如果当前h[k]不存在,那么就不需要向下寻找,即x不存在
    
    删除:
        不真正的删除,而是开一个bool类型的数组,将所要删除的数值标记一下
        后续在查询、添加操作时,对此值是否存在特判一下即可

题目:840. 模拟散列表 - AcWing题库

代码:

暴力+二分,样例11/16:
#include<bits/stdc++.h>

using namespace std;

const int N=1e5+50;
int f[N];

int main()
{
    int n,i=0;
    scanf("%d",&n);
    while(n--)
    {
        char op[5];
        int x;
        scanf("%s %d",op,&x);
        if(!strcmp(op,"I"))
        {
            f[i]=x;
            i++;
        }
        else
        {
                sort(f,f+i);
                int l=0,r=i-1;
                while(l<r)
                {
                    int mid=l + r +1 >> 1;
                    if(f[mid]>x) r=mid-1;
                    else l=mid;
                }
                if(f[l]==x) cout << "Yes" << endl;
                else cout << "No" << endl;
        }
    }
    return 0;
}

 哈希:

#include<iostream>
//memset()函数所在头文件
#include<cstring>
using namespace std;
int n;
const int N=2e5+3,null=0x3f3f3f3f;
int h[N];
            
//如果x在hash表中已经存在的话,就返回x在hash表中所在的位置
//如果不存在的话就返回x应该在hash表中存储的位置
int find(int x)
{
    int k=(x%N + N)%N;
    while(h[k]!=null && h[k]!=x)
        {
            k++;
            //如果k==N的话(最边缘),那么就让它从头开始寻找
            //由于N的长度为题解要求的2倍,所以一定能找到一个k
            //这个k要么是x在hash表中的位置,要么是它可以在hash表中存在的位置
            if(k==N) k=0;
        }
        return k;
                
}
            
int main()
{
    scanf("%d",&n);
                
    memset(h,null,sizeof h);
                
    while(n--)
        {
            char op[2];
            int x;
            scanf("%s %d",op,&x);
            int k=find(x);
            if(!strcmp(op,"I")) h[k]=x;//直接记录下x可以在hash表中插入的位置
            else
            {
            //如果x存在于hash表中,那么一定不等于null
            //如果不存在于hash表中,那么k所返回的值就是x可以在hash表中存在的位置
            //但是由于这一步是查询操作,所以h[k]==null(null是初始值)
                if(h[k]!=null) cout << "Yes" << endl;
                else cout << "No" << endl;
            }
        }
    return 0;
}

}

2(拉链法

{
    添加:
        开一个一维数组a[]存下所有数值,将数值a取模后的值(假设是3),
        就在数组a[3]的后面 “拉一条链” 将a链住
        如果又有数值b取模后仍等于3,那么就在数值a的后面再“拉一条链”将数值b链住
        (每个数组a[]下对应的链就是我们之前所学的单链表)

插入过程与前面链表的插入过程一致,可翻阅查看:.单链表.-CSDN博客


        ...
    查找:
        求出数值取余后对应的是数组a[]上的那一个值,再询问此a[]上的链表是否有所要求的数值
    删除:
        不真正的删除,而是开一个bool类型的数组,将所要删除的数值标记一下
        后续在查询、添加操作时,对此值是否存在特判一下即可

代码:

#include<iostream>
//memset()函数所在头文件
#include<cstring>
using namespace std;
int n;
const int N=1e5+3;
int h[N],e[N],ne[N],idx;
            
void insert(int x)
{
    //取模,映射到数组h[]上,x可能为负数,所以先模再加 之后再次取模
    int k=(x%N+N)%N;
    e[idx]=x,ne[idx]=h[k],h[k]=idx,idx++;
}
            
bool find(int x)
{
    int k=(x%N + N)%N;
    //遍历链表h[k](链表上的第一个下标)表示数组上下标为k的下一个指向
    //每次让i指向它的下一个指向(移动指针i,使其继续向下遍历)
    //i=-1时即遍历结束(空值)
    for(int i=h[k];i!=-1;i=ne[i])
        if(e[i]==x)
            return true;
                   
    return false;
}
            
int main()
{
    scanf("%d",&n);
                
    memset(h,-1,sizeof h);
                
    while(n--)
    {
        char op[2];
        int x;
        scanf("%s %d",op,&x);
        if(!strcmp(op,"I")) insert(x);
        else
        {
            if(find(x)) cout << "Yes" << endl;
            else cout << "No" << endl;
        }
    }
return 0;
}
    

}

取模的值:

上述的N分别取的是N=2e5+3、1e5+3这里不像之前取1e5+10是因为:N是取模的值

取模的值一般满足以下规律使映射时的重复率最低:(取模的数值一般取成质数并且离2的整次幂要远的多)

计算方法:
(取模的数值一般取成质数并且离2的整次幂要远的多)
{
    //本题的操作次数不大于100000,所以从100000开始遍历(根据题设要求来初始化i)
    for(int i=100000;;i++)
    {
        bool flag=true;
        for(int j=2;j*j<=i;j++)
           if(i%j==0)
           {
               flag=false;
               break;
           }
        if(flag)
        {
            cout << i << endl;
            break;
        }
        
    }
    return 0;
    //这里输出的i就是接下来要取模的数值
}

②字符串哈希方式

字符串hash方式
    这里讲的是特殊的hash方式:字符串前缀哈希法
    类似于前缀和:
    {
    str="abcacwing",str[0]=0(特殊处理),str[1]="a",str[2]="ab",
    str[3]="abc",str[4]="abca"...
    定义某一个哈希的前缀和:把这个字符串看成是p进制的数
    每一位字母(str[i])看成是在p进制下的数字
    如果字符串str[4]="abcd",分别在p进制下,a:1,b:2,c:3,d:4

    那么str[4]在p进制下的和即为:1*p^3+2*p^2+3*p^1+4*p^0,
    这样处理就可以把字符串转化成一个数字
    但这个数字可能较大,所以mod(%)上一个数Q,那么就可以把这个大的数字映射成1~Q-1之内的数字了。

接下来通过比较数值大小即可比较两个字串是否相同。

注意:①:不能映射成0,因为0在任何进制下的值都为0,那么字符串:aasxas,axssx,cbdhd都会被映射成0
    ②:这里是假设不存在冲突,不存在重复的情况。当p=131或者13331,Q=2^64时,基本上不存在冲突
    若要模上Q(2^64)那么可以直接用unsigned long long 来存储hash值,溢出的值就是模上2^64的值。

}

这样处理的好处就是可以利用前缀哈希,来确定任意字串的哈希值

{
    这里的左边(1)是高位,右边(R)是低位(进制情况下最左边为最低位,即最左边*P^0)。所以在h[R]中R就是第0位,1就是第R-1位,h[l-1]中l-1就是第0位,1就是第l-2位。

那么如果要求L到R之间字串的hash值,那么就先要将h[l-1]的0位与h[R]的第0位对齐
这里R在l的右边,所以改变h[l-1]的0位与h[R]的第0位对齐
(不对齐0位,由于不同数位对应不同P的幂,那将导致相减毫无意义)
 比如:1*p^3+2*p^2+3*p^1+4*p^0,数位a对应的是P^3,b对应的是P^2..                                          即将h[l-1]*((P^(R-1))/(P^(l-2))

}

也就是达到数位对齐的目的字符串ABCDE 与 ABC 的前三个字符值是一样,只差两位,
乘上P的二次方把 ABC 变为 ABC00,再用 ABCDE - ABC00 得到 DE 的哈希值。
   

最终所推得的公式:


子串的hash值为①:h[R]-h[l-1](P^(R-L+1));

所以最终任意字符串的hash值为②:h[i]=h[i-1]*P + str[i];

公式①:

公式②:

题目:841. 字符串哈希 - AcWing题库

代码:

暴力+双指针,样例:8/13:
#include<iostream>

using namespace std;

int main()
{
    int m,n;
    scanf("%d %d",&m,&n);
    char str[100050];
    scanf("%s",str);
    while(n--)
    {
        int x1,y1,x2,y2;
        scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
        bool flag=true;
        while(x1<=y1 && x2<=y2)
        {
            if(str[x1-1]!=str[x2-1] ||str[y1-1] != str[y2-1])
            {
                flag=false;
                break;
            }
            else x1++,x2++,y1--,y2--;
        }
        if(flag) printf("Yes\n");
        else printf("No\n");
    }
}

 哈希字符串:

#include<bits/stdc++.h>

using namespace std;

typedef unsigned long long ULL;
const int N=1e5+10,P=131;

int n,m;
char str[N];
//p[]是对进制P的预处理,记录了P的不同幂次方
//h[]存放的是该字符串的每一个字母对应的hash值
ULL h[N],p[N];

int get(int l,int r)
{
    return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
    scanf("%d%d%s",&n,&m,str+1);
    
    p[0]=1;
    for(int i=1;i<=n;i++)
    {
//这里需要多次运用到P的幂次方,所以在此提前做预处理
        //预处理,这里溢出值相当于取模后的值
        p[i]=p[i-1]*P;
        //转换成P进制下的数
        h[i]=h[i-1]*P+str[i];
    }
    
    while(m--)
    {
        int l1,r1,l2,r2;
        scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        
        if(get(l1,r1)==get(l2,r2)) printf("Yes\n");
        else printf("No\n");
    }
    
    return 0;
    
}

tips:

注意上述所给出的一些小技巧,能更好的帮助理解。

而且比如:p[i]=p[i-1]*P;很好的避免了pow(P,i)的繁杂操作

倘若还是不理解就先将公式记下来

标签:取模,hash,哈希,int,scanf,str
From: https://blog.csdn.net/2301_80358171/article/details/139211404

相关文章

  • 数据结构与算法学习(07)查找(4)散列、哈希、字典——BUAA
    文章目录查找(4)——散列(Hash)字典介绍散列函数的构造方法直接地址法数字分析法平方取中法叠加法移位叠加法折叠叠加法基数转换法除留余数法随机数法一些好的哈希函数**针对字符串好的哈希函数冲突的处理方法开放地址法线性探测二次探测伪随机特点再散列法链接地址法代......
  • 散列(哈希)及其练习题(基础)
    散列导入:有N个数和M个数,如何判断M个数中每个数是否在N中出现?思想:空间换时间创建hashtable,以N个数本身为索引(数组下标)构建boolhashtable输入x的过程中,hashtable[x]=True(若要计算出现次数,换成++)但终归是有局限性!数字只能是整数,还不能太大,等等。散列函数:平房区中法、取余......
  • windows如何获取文件的哈希值
    在Windows系统中,可以使用以下几种方法来获取文件的哈希值:使用PowerShell在PowerShell中运行以下命令即可计算文件的SHA256哈希值:Get-FileHash-Path<文件路径>-AlgorithmSHA256其中<文件路径>是待计算哈希值的文件的完整路径。使用certutil命令Window......
  • redis之哈希类型
    在Redis中,哈希(Hash)类型是一种将多个键值对存储在单个键中的数据结构。哈希类型被用来表示对象,其中每个键都是对象的属性,并且每个属性都与一个值相关联。哈希类型在Redis中通常用于存储对象的属性集合。哈希类型和python中的字典类型很像哈希类型常用方法【1】hset#用于设置......
  • 哈希
    哈希字符串哈希原理核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低小技巧:取模的数用2^64,这样直接用unsignedlonglong存储,溢出的结果就是取模的结果typedefunsignedlonglongULL;constintN=1e5+10,P=131;ULLh[N],p[N];//h[k]......
  • 哈希基础知识学习-python版
    哈希哈希表根据key直接进行访问的无序数据结构,复杂度为O(1)哈希表的实现---字典初始化d1=dict()查找#使用中括号[]进行查找,括号内为特定的键,键-值dic={"a":1,"d":0,"e":3}print(dic["a"])#输出1print(dic["z"])#报错修改dic["a"]=5print(dic[&quo......
  • 在密码学中,“加盐”(Salting)是指在存储用户密码的哈希值之前,向原始密码添加一个随机生
    在密码学中,“加盐”(Salting)是指在存储用户密码的哈希值之前,向原始密码添加一个随机生成的字符串(称为“盐”Salt)的过程。这个盐值通常是全球唯一的,并且与每个用户账户相关联,存储在数据库中与哈希值一起。加盐的目的主要有两个:抵御彩虹表攻击:彩虹表是一种预先计算好的哈希值对照表......
  • lc1(暴力、哈希)
    给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。 示例1:输入:nums=[2,7......
  • 【Redis】Redis的操作命令(二)——Redis 哈希(HASH)
    Redishash是一个string类型的field(字段)和value(值)的映射表,hash特别适合用于存储对象。当设置一个名为demo的哈希对象时:HSETdemoname"redistutorial"description"redisbasiccommandsforcaching"likes20visitors23000 获取哈希对象语句,如下:HGETALLde......
  • 2024-04-23---简单题---有效的字母异位词(哈希表)
    题目:思路:排序:复杂度较高。两个字符串进行排序,然后开始比较两个字符串是否相等哈希表:主要是一个hashmap记录第一个字符串所有字符出现的次数,然后遍历第二个字符串没找到一个就将次数减一。看最后所有的值是否为0.时间复杂度选第二种,简单题罢了。代码:排序classSolution......