数据结构:哈希表

简介: 数据结构:哈希表

一、一般哈希表

1.算法模板

(1) 拉链法
    int h[N], e[N], ne[N], 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;
    }
 
(2) 开放寻址法
    int h[N];
 
    // 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
    int find(int x)
    {
        int t = (x % N + N) % N;
        while (h[t] != null && h[t] != x)
        {
            t ++ ;
            if (t == N) t = 0;
        }
        return t;
    }

2.拉链法:

3.开放寻址法:(蹲坑法)

4.例题:

维护一个集合,支持如下几种操作:

  1. I x,插入一个数 x;
  2. Q x,询问数 x 是否在集合中出现过;

现在要进行 N 次操作,对于每个询问操作输出对应的结果。

输入格式

第一行包含整数 N,表示操作数量。

接下来 N 行,每行包含一个操作指令,操作指令为 I xQ x 中的一种。

输出格式

对于每个询问指令 Q x,输出一个询问结果,如果 x在集合中出现过,则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1≤N≤10^5

-10^9≤x≤10^9

输入样例:

5
I 1
I 2
I 3
Q 2
Q 5

输出样例:

Yes
No

拉链法:

#include<bits/stdc++.h>
 
using namespace std;
 
const int N=100003;// 取大于1e5的第一个质数,取质数冲突的概率最小 可以百度
//* 开一个槽 h
int h[N],e[N],ne[N],idx;//邻接表
 
void insert(int x)
{
    // c++中如果是负数 那他取模也是负的所以加N再%N就一定是一个正数
    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;
    //用上面同样的 Hash函数 讲x映射到 从 0-1e5 之间的数
    for(int i=h[k];i!=-1;i=ne[i])
    {
        if(e[i]==x)return true;
    }
    return false;
}
int main()
{
    memset(h,-1,sizeof h); //将槽先清空 空指针一般用 -1 来表示
    int n;
    cin>>n;
    while(n--)
    {
        int x;
        char op;
        cin>>op>>x;
        if(op=='I')
            insert(x);
        else
        {
            if(find(x))cout<<"Yes"<<endl;
            else cout<<"No"<<endl;
        }
    }
    return 0;
}

*开放寻址法:

#include<bits/stdc++.h>
 
using namespace std;
//开放寻址法一般开 数据范围的 2~3倍, 这样大概率就没有冲突了
const int N=200003;//大于数据范围的第一个质数
const int null=0x3f3f3f3f;//规定空指针为 null 0x3f3f3f3f
 
int h[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;//如果这个位置是空的, 则返回的是他应该存储的位置
}
int main()
{
    memset(h,null,sizeof h);//规定空指针为 0x3f3f3f3f
    int n;
    cin>>n;
    while(n--)
    {
        char op;
        int x;
        cin>>op>>x;
        int k=find(x);
        if(op=='I')h[k]=x;
        else
        {
            if(h[k]!=null)cout<<"Yes"<<endl;
            else cout<<"No"<<endl;
        }
    }
    
}

***二、字符串哈希

1.*算法模板(注意计算公式)

核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
 
typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64
 
// 初始化
void init()
{
    //字符串从1开始编号,h[1]为前一个字符的哈希值
    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 +s[i];      //前缀和求整个字符串的哈希值
    }
}
 
// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

2.思路:

字符串哈希) O(n)+O(m)

全称字符串前缀哈希法,把字符串变成一个p进制数字(哈希值),实现不同的字符串映射到不同的数字。

对形如 X1X2X3⋯Xn−1Xn 的字符串,采用字符的ascii 码乘上 P 的次方来计算哈希值。

注意点:

1. 任意字符不可以映射成0,否则会出现不同的字符串都映射成0的情况,比如A,AA,AAA皆为0

2. 冲突问题:通过巧妙设置P (131 或 13331) , Q (2^64)的值,一般可以理解为不产生冲突。

常见问题是比较不同区间的子串是否相同,就转化为对应的哈希值是否相同。

求一个字符串的哈希值就相当于求前缀和,求一个字符串的子串哈希值就相当于求部分和

3.例题:

给定一个长度为 n 的字符串,再给定 m 个询问,每个询问包含四个整数 l1,r1,l2,r2请你判断 [l1,r1][l2,r2]这两个区间所包含的字符串子串是否完全相同。

字符串中只包含大小写英文字母和数字。

输入格式

第一行包含整数 nm,表示字符串长度和询问次数。

第二行包含一个长度为 n 的字符串,字符串中只包含大小写英文字母和数字。

接下来 m行,每行包含四个整数 l1,r1,l2,r2表示一次询问所涉及的两个区间。

注意,字符串的位置从 1 开始编号。

输出格式

对于每个询问输出一个结果,如果两个字符串子串完全相同则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1≤n,m≤10^5

输入样例:

8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2

输出样例:

Yes
No
Yes
#include<bits/stdc++.h>
 
using namespace std;
 
const int N=100010,P=131;
 
typedef unsigned long long ULL;
 
ULL h[N],p[N];
// h[i]前i个字符的hash值
// 字符串变成一个p进制数字,体现了字符+顺序,需要确保不同的字符串对应不同的数字
// P = 131 或  13331 Q=2^64,在99%的情况下不会出现冲突
// 使用场景: 两个字符串的子串是否相同
string s;
int n,m;
 
void init()
{
    //字符串从1开始编号,h[1]为前一个字符的哈希值
    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 +s[i];      //前缀和求整个字符串的哈希值
    }
}
ULL get(int l,int r)
{
    return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
    cin>>n>>m;
    cin>>s;
    init();
    while(m--)
    {
        int l1,r1,l2,r2;
        cin>>l1>>r1>>l2>>r2;
        if(get(l1,r1)==get(l2,r2))
            cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
}


目录
相关文章
|
6月前
|
算法
数据结构-哈希表(二)
数据结构-哈希表(二)
74 0
|
6月前
|
存储 索引 Python
python中的哈希表数据结构
python中的哈希表数据结构
50 0
|
6月前
|
存储 C++ Python
【数据结构】哈希表—C/C++实现
【数据结构】哈希表—C/C++实现
87 0
|
6月前
|
存储 缓存 算法
数据结构之哈希表
数据结构之哈希表
76 0
|
1月前
|
算法 Java 数据库
数据结构与算法学习十五:哈希表
这篇文章详细介绍了哈希表的概念、应用实例、实现思路,并提供了使用Java实现的哈希表代码。
53 0
数据结构与算法学习十五:哈希表
|
2月前
|
存储 Java Serverless
【数据结构】哈希表&二叉搜索树详解
本文详细介绍了二叉搜索树和哈希表这两种数据结构。二叉搜索树是一种特殊二叉树,具有左子树节点值小于根节点、右子树节点值大于根节点的特点,并且不允许键值重复。文章给出了插入、删除和搜索等方法的具体实现。哈希表则通过哈希函数将键名映射为数组下标,实现快速查找,其插入、删除和查找操作时间复杂度理想情况下为O(1)。文中还讨论了哈希函数的设计原则、哈希冲突的解决方法及哈希表的实现细节。
48 8
【数据结构】哈希表&二叉搜索树详解
|
1月前
|
存储 缓存 Java
【数据结构】哈希表
【数据结构】哈希表
27 1
|
3月前
|
存储 Java
数据结构中的哈希表(java实现)利用哈希表实现学生信息的存储
这篇文章通过Java代码示例展示了如何实现哈希表,包括定义结点类、链表类、数组存储多条链表,并使用简单的散列函数处理冲突,以及如何利用哈希表存储和查询学生信息。
数据结构中的哈希表(java实现)利用哈希表实现学生信息的存储
|
5月前
|
存储 NoSQL 算法
redis数据结构—哈希表
redis数据结构—哈希表
54 0
|
5月前
|
存储 算法 大数据
深入解析力扣170题:两数之和 III - 数据结构设计(哈希表与双指针法详解及模拟面试问答)
深入解析力扣170题:两数之和 III - 数据结构设计(哈希表与双指针法详解及模拟面试问答)

热门文章

最新文章