基础算法入门12——模拟哈希表+STL简单使用

模拟哈希表

作用是将很大的值映射到一个很小的值

将一个很大的值映射到数组下标范围之内

k=(x%N+N)%N

将一个较大值x映射成一个较小值k,一般的N是一个很大的质数,为了避免产生不同较大值转换成相同较小值的冲突。

对于解决冲突主要分为两个方法:拉链法和开放寻址法

拉链法就是在发生冲突的位置开设单链表,将在h[k]发生冲突的元素统一存放到由h[k]开出来的单链表中来。

开放寻址法不用设置单链表来解决冲突,就是在h[k]发生冲突的时候,就往后面寻找有没有空的位置进行存储。

  • 拉链法
#include 
#include 
#include 
using namespace std;
const int N=100003;//一般取100003就够用了
int h[N],e[N],ne[N],idx;//h[]表示哈希的数组,e[],ne[]表示从哈希表每个单元素拉出来的单链表,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;
    cin>>n;
    memset(h, -1, sizeof h);//初始值为-1,表示每个单链表为空的
    while (n -- )
    {
        char op[2];
        int x;
        cin>>op>>x;
        if(op[0]=='I')
        {
            insert(x);
        }
        else
        {
            if(find(x)) puts("Yes");
            else puts("No");
        }
    }
    
}

当存的节点出现冲突的时候,就将这个节点存到hash表的链表中

基础算法入门12——模拟哈希表+STL简单使用_第1张图片

  • 开放寻址法
#include
#include
#include
using namespace std;
const int N=200003,null=0x3f3f3f3f;//N表示进行映射的大数,null是一个标志,h[i]==null表示哈希表这个位置是空的
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()
{
    int n;
    cin>>n;
    memset(h,0x3f3f,sizeof h);//一开始哈希表所有位置都是空的
    while (n -- )
    {
        char op[2];
        int x;
        cin>>op>>x;
        if(op[0]=='I')//插入操作
        {
            int k=find(x);
            h[k]=x;
        }
        else 
        {
            if(h[find(x)]!=null)puts("Yes");//查询操作
            else puts("No");
        }
    }
}

开放寻址法,当存在冲突的时候,就向这个点后面遍历找到一个空的位置进行插入数值操作

字符串哈希

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

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

输入格式

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

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

接下来 m m m 行,每行包含四个整数 l 1 , r 1 , l 2 , r 2 l_1, r_1, l_2, r_2 l1,r1,l2,r2,表示一次询问所涉及的两个区间。

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

输出格式

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

每个结果占一行。

数据范围

1 ≤ n , m ≤ 1 0 5 1 \le n, m \le 10^5 1n,m105

输入样例:

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

输出样例:

Yes
No
Yes

我们将一个字符串想象成一个数字

例如:ABCD 我们可以看作 1234

然后我们将这个数字转化成p进制的数就是 1 ∗ p 3 + 2 ∗ p 2 + 3 ∗ p 1 + 4 ∗ p 0 1*p^3+2*p^{2}+3*p^{1}+4*p^0 1p3+2p2+3p1+4p0

一般的 p取131或者13331

由于这个数字会很大,我们需要进行取模,模上一个大数Q ,Q一般取 2 64 2^{64} 264

但是我们将这个数存在unsigned long long 类型的数组中,当数字大于 2 64 2^{64} 264时就会自动帮你取模(也就是溢出导致取模)

整体映射流程:

字符串str=abcd...—>大数num=12345..—>k=num%Q

这样我们就得到了一段字符串对应的哈希值

然后我们需要把字符串的的前缀和记录下来

当我们已经知道[0, l-1] ,[0, r]的字符串表示的值的时候,我们可以根据公式

h [ l , r ] = h [ r ] − h [ l − 1 ] ∗ p r − l + 1 h[l,r]=h[r]-h[l-1]*p^{r-l+1} h[l,r]=h[r]h[l1]prl+1得到[l,r]这段区间的字符串的哈希值

然后通过比较字符串的哈希值来判断这两个字符串是不是相同的

#include
#include
using namespace std;
const int N=100010,P=131;
typedef unsigned long long ULL;//数据存在unsigned long long 类型的数组中,当数字大于2^64时就会自动帮你取模
ULL h[N],p[N];
char str[N];
int query(int l,int r)
{
    return h[r]-h[l-1]*p[r-l+1];//区间[l,r]字符串对应的哈希值
}

int main()
{
    int n,m;
    cin>>n>>m;
    cin>>str+1;
    p[0]=1;
    for(int i=1;i<=n;i++)
    {
        p[i]=p[i-1]*P;//因为query中要用到p^{r-l+1}因此不妨将p的各种幂存下来
        h[i]=h[i-1]*P+str[i];//这里字符串每个字符的值是直接用ascal码了
    }
    while(m--)
    {
        int l1,r1,l2,r2;
        cin>>l1>>r1>>l2>>r2;
        if(query(l1,r1)==query(l2,r2))puts("Yes");
        else puts("No");
    }
    return 0;
}

STL

  • vector 动态变长的数组
  • string 字符串
  • queue 单向队列
  • priority_queue优先队列(堆)
  • stack 栈
  • deque 双端队列()
  • set,map,multi_set,multi_map (红黑树)
  • unordered_map,unordered_set
  • bitset

vector 变长数组

//初始化
vector<int> q;
vector<int> q(n,value);//创建n个值为value的元素
//倍增的思想,数组长度不够就创建一个新的2倍长度的数组,然后复制粘贴过去
//支持的方法
	size()
  	empty()
    clear()
    front()/back()
    push_back()/pop_back()
    begin()/end()
    []
    支持比较运算(两个vector进行比较),按照字典序

pair

pair<int,int>
	first
	second
	支持比较运算,first作为第一关键字,second作为第二个关键字,按照字典序

string

//字符串
//常用方法
	size()
	empty()
	clear()
	substr(字符串起始下标,字符串长度)//查询子串

queue

//常用方法
	size()
	empty()
	push()
	pop()
	front()
	back()
//注意没有clear函数

priority_queue

//优先队列,堆,默认是大根堆
	push()//插入元素
	top()//返回顶端元素
	pop()//弹出顶端元素
	priority_queue<int,vector<int>,greater<int>> heap//定义一个小根堆

stack

//常用方法
    push()
    top()
    pop()
    size()
    empty()

deque

//双端队列
	size()
	empty()
	clear()
    front()/back()
	push_back()/pop_back()
	push_front()/pop_front()
	begin()/end()
    []

set/multiset/map/multimap

	size()
	empty()
	clear()
set/multiset:
	set中没有重复元素/multiset支持重复元素
		insert()
		find()
		count()
		erase()
			1.如果输入的是一个数x,就删除所有x
			2.如果输入的是一个迭代器,删除的就是这个迭代器o
		lower_bound()/upper_bound()
			louwer_bound(x) 返回大于等于x的最小的数的迭代器
			upper_bound(x)	返回大于x的最小的数的迭代器
        
map/multimap
        map中没有重复元素/multimap支持重复元素
        insert() //插入pair类型
		find() 
		count()
		erase()//删除pair类型,或者迭代器
        [] //时间复杂度是O(logn)
        louwer_bound(x) 
        upper_bound(x)	

unordered_map/unordered_multimap/unordered_set/unordered_multiset(哈希表)

和set/multiset/map/multimap类似,只是增删改查的时间复杂度是O(1)
不支持lower_bound()/upper_bound(),也不支持迭代器自增自减

bitset(只能存储二进制数的数组)

没用到过,用到了再说…

你可能感兴趣的:(算法学习,算法,散列表,c++)