数据结构板子库


这里的python全是python3的
详情请见acwing,acwing赛高
力求快、准、狠、短


数据结构

#单链表

----c++版

数组模拟链表
https://www.acwing.com/problem/content/828/

#include<iostream>
using namespace std;
const int N=1e5+10;
//* head 表示头节点的下标
//* e[i] 表示节点 i 的值
//* ne[i] 表示节点 i 的 next 指针是多少
//* idx 存储当前已经用到了哪个节点,节点下标/编号
//* -1<--@<--@<--@<--@<--@<--head
int head,e[N],ne[N],idx;
//* 初始化
void init(){
    head=-1;
    idx=0;
}
//* 插入到头节点操作
void add_to_head(int x){
    e[idx]=x;
    ne[idx]=head;
    head=idx;
    idx++;
}
//* 插入到下标是k的点后面
void add(int k,int x){
    e[idx]=x;
    ne[idx]=ne[k];
    ne[k]=idx;
    idx++;
}
//* 将位置k的后一个节点删掉
void remove(int k){
    ne[k]=ne[ne[k]];
}
int main(void){
    ios::sync_with_stdio(false);//加速输入
    int m;cin>>m;
    init();
    while(m--){
        int x,k;
        char op;
        cin>>op;
        if(op=='H'){
            cin>>x;
            add_to_head(x);
        }
        else if(op=='D'){
            cin>>k;
            if(k==0)head=ne[head];
            else remove(k-1);
        }
        else{
            cin>>k>>x;
            add(k-1,x);
        }
    }
    for(int i=head;i!=-1;i=ne[i])cout<<e[i]<<" ";
    cout<<endl;
    return 0;
}

#双链表

----c++版

https://www.acwing.com/problem/content/829/

#include<iostream>
using namespace std;

const int N=1e5+10;
int m;
int e[N],l[N],r[N];
int idx;
//初始化
void init(){
    l[1]=0;r[0]=1;//0和1只作为表的头尾,本身并不放值,0最左,1最右
    idx=2;//已经用掉两个点了
}
//在第k个点右边插入一个x
void add(int k,int x){
    e[idx]=x;
    l[idx]=k;
    r[idx]=r[k];
    l[r[k]]=idx;
    r[k]=idx;
    idx++;
}
//删除第k个点
void remove(int k){
    r[l[k]]=r[k];
    l[r[k]]=l[k];
}
int main(){
    ios::sync_with_stdio(false);
    cin>>m;
    init();
    while(m--){
        string op;
        cin>>op;
        int k,x;
        if(op=="R"){
            cin>>x;
            add(l[1],x);
        }else if(op=="L"){
            cin>>x;
            add(0,x);
        }else if(op=="D"){
            cin>>k;
            remove(k+1);
        }else if(op=="IL"){
            cin>>k>>x;
            add(l[k+1],x);
        }else{
            cin>>k>>x;
            add(k+1,x);
        }
    }
    for(int i=r[0];i!=1;i=r[i])cout<<e[i]<<" ";
    return 0;
}

#栈

----c++版

https://www.acwing.com/problem/content/830/

#include<iostream>
#include<cstring>
using namespace std;
const int N=1e6+10;
int stack[N],top;
int main(){
    int m;cin>>m;
    while(m--){
        string op;cin>>op;
        int x;
        if(op=="push"){
            cin>>x;top++;
            stack[top]=x;
        }else if(op=="pop")top--;
        else if(op=="empty")cout<<(top?"NO":"YES")<<endl;
        else cout<<stack[top]<<endl;
    }
    return 0;
}

#队列

----c++版

https://www.acwing.com/problem/content/831/

#include<iostream>
#include<cstring>
using namespace std;
const int N=1e6+10;
int q[N];
int main(){
    int m;cin>>m;
    int i=1,j=1;//双指针算法
    while(m--){
        string op;cin>>op;
        if(op=="push"){
            int x;scanf("%d",&x);
            q[j++]=x;
        }else if(op=="pop"){
            q[i++];
        }else if(op=="query"){
            printf("%d\n",q[i]);
        }else if(op=="empty"){
            j-i==0?printf("YES\n"):printf("NO\n");
        }
    }
    return 0;
}

#单调栈

----c++版

先拿栈顶的数比较,不行了就出栈,换下一个比
https://www.acwing.com/problem/content/832/

#include<iostream>
using namespace std;
const int N=1e5+10;
int stack[N],tt;
//tt是单调栈顶数的下标,stack的每一项存的是当前数左边第一个比其小的数
//过滤掉了其他大数,维护了最紧凑、最连续的单调性,所以新加入的数不需要看其他大数,只需要看单调栈里的数就行
int main(){
    ios::sync_with_stdio(false);
    int n;cin>>n;
    for(int i=1;i<=n;i++){
        int x;cin>>x;
        while(tt&&stack[tt]>=x)tt--;
        if(tt)cout<<stack[tt]<<" ";
        else cout<<"-1 ";
        stack[++tt]=x;//最新的数必须先入栈,因为必须与之后的数比较
    }return 0;
}

#单调队列

----c++版

单调队列给我的感觉是,你必须从队列的头找起,而且头的数可能被舍弃
https://www.acwing.com/problem/content/156/

#include<iostream>
using namespace std;
const int N=1e6+10;
int a[N],q[N];//每滑动一个窗口所得到的最小值的下标存在队列q里
int main(){
    //ios::sync_with_stdio(false);不能加这句话
    int n,k;cin>>n>>k;
    for(int i=0;i<n;i++)scanf("%d",&a[i]);
    int hh=0,tt=-1;//hh当前单调队列中最小值的下标,
    for(int i=0;i<n;i++){
        if(hh<=tt&&i-k+1>q[hh])hh++;//若当点队列最小值所存的下标不在窗口中,去找下一个
        //维护单调性
        while(hh<=tt&&a[q[tt]]>a[i])tt--;//找当前还在队列中的最小值中第一个比a[i]小的值的下标
        q[++tt]=i;//下标加一的位置即可放i
        if(i>=k-1)printf("%d ",a[q[hh]]);//有窗口时再输出
    }
    puts("");
    hh=0;tt=-1;
    for(int i=0;i<n;i++){
        if(hh<=tt&&i-k+1>q[hh])hh++;
        while(hh<=tt&&a[q[tt]]<a[i])tt--;
        q[++tt]=i;
        if(i>=k-1)printf("%d ",a[q[hh]]);
    }
    return 0;
}

#KMP算法

kmp可以快速找到子串

----c++版

https://www.acwing.com/problem/content/description/833/

#include<iostream>
using namespace std;
const int N=100100,M=1000010;
int n,m;
char p[N],s[M];
int ne[N];
int main(){
    cin>>n>>p+1>>m>>s+1;
    //要从第一位开始给字符串赋值,这样输出比较方便
    //生成next数组,next数组中每一位数表示,包括当前下标,从当前下标开始往前计,有几位字符与该单词从开头数的字符相同
    for(int i=2,j=0;i<=n;i++){
        while(j&&p[i]!=p[j+1])j=ne[j];
        if(p[i]==p[j+1])j++;
        ne[i]=j;
    }
    //for(int i=1;i<=n;i++)cout<<ne[i];
    //next数组的第一位永远都是0,如果实在没有匹配了,咱还是从头开始吧
    for(int i=1,j=0;i<=m;i++){
        while(j&&s[i]!=p[j+1])j=ne[j];
        if(s[i]==p[j+1])j++;
        if(j==n){
            printf("%d ",i-n);
            j=ne[j];
        }
    }
    return 0;
}

----python版

当然python有re库和正则表达式

#Trie字典树

----c++版

o(n)
https://www.acwing.com/problem/content/837/

#include<iostream>
using namespace std;

const int N=200010;
int son[N][26],cnt[N],idx;
char str[N];
bool st[N];
int n;

void insert(char str[]){
    int p=0;
    for(int i=0;str[i];i++){
        int u=str[i]-'a';
        if(!son[p][u])son[p][u]=++idx;
        p=son[p][u];
    }
    cnt[p]++;//标记一个单词的结尾
}
int query(char str[]){
    int p=0;
    for(int i=0;str[i];i++){
        int u=str[i]-'a';
        if(!son[p][u])return 0;
        p=son[p][u];
    }
    return cnt[p];
}
int main(){
    cin>>n;
    while(n--){
        string op;
        cin>>op>>str;
        if(op=="I")insert(str);
        else cout<<query(str)<<endl;
    }
    return 0;
}

#并查集

----c++版

https://www.acwing.com/problem/content/838/

#include<iostream>
using namespace std;
const int N=1e5+10;
int f[N];
int find(int x){//寻找根节点
    if(f[x]!=x)f[x]=find(f[x]);//如果不是根节点
    return f[x];
}//真正的核心操作只有find
int main(){
    int n,m;
    cin.tie(0);
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++)f[i]=i;//初始化
    while(m--){
        char op;cin>>op;
        int a,b;cin>>a>>b;
        if(op=='M'){
            if(find(a)!=find(b))f[find(a)]=find(b);//包含了路径压缩
        }else{
            if(find(a)!=find(b))cout<<"No"<<endl;
            else cout<<"Yes"<<endl;
        }
    }
    return 0;
}

----并查集习题增补

https://blog.csdn.net/lafea/article/details/107243946

----高级数据结构:并查集

https://blog.csdn.net/lafea/article/details/113354366

#树状数组

----高级数据机构:树状数组

https://blog.csdn.net/lafea/article/details/113401186

#线段树

----高级数据机构:线段树

https://blog.csdn.net/lafea/article/details/114866738

#堆

----c++版

https://www.acwing.com/problem/content/840/

#include<iostream>//小根堆
#include<algorithm>
using namespace std;
const int N=100010;
int h[N],sizee;
int n,m;
void down(int u){
    int t=u;
    if(2*u<=sizee&&h[t]>h[2*u])t=2*u;
    if(2*u+1<=sizee&&h[t]>h[2*u+1])t=2*u+1;//选出左右节点中最小的那个 
    if(u!=t){
        swap(h[u],h[t]);
        down(t);
    }
}
int main(){
    cin>>n>>m;
    sizee=n;
    for(int i=1;i<=n;i++)scanf("%d",&h[i]);
    for(int i=n/2;i;i--)down(i);
    while(m--){
        cout<<h[1]<<" ";
        h[1]=h[sizee--];
        down(1);
    }
    return 0;
}

模拟堆
https://www.acwing.com/problem/content/description/841/

#include<iostream>
#include<algorithm>
using namespace std;

const int N=1e5+10;
int h[N];
int ph[N]; //存放第k个插入点的下标
int hp[N];//存放堆中点的插入次序
int sizee;//size 记录的是堆当前的数据多少
//这个交换过程其实有那么些绕 但关键是理解 如果hp[u]=k 则ph[k]=u 的映射关系
//之所以要进行这样的操作是因为 经过一系列操作 堆中的元素并不会保持原有的插入顺序
//从而我们需要对应到原先第K个堆中元素
//如果理解这个原理 那么就能明白其实三步交换的顺序是可以互换 
//h,hp,ph之间两两存在映射关系 所以交换顺序的不同对结果并不会产生影响
void heap_swap(int u,int v){
    swap(h[u],h[v]);
    swap(hp[u],hp[v]);
    swap(ph[hp[u]],ph[hp[v]]);
}

void down(int u){
    int t=u;
    if(u*2<=sizee&&h[t]>h[u*2])t=u*2;
    if(u*2+1<=sizee&&h[t]>h[u*2+1])t=u*2+1;
    if(u!=t){
        heap_swap(u,t);
        down(t);
    }
}
void up(int u){
    if(u/2>0&&h[u]<h[u/2]){
        heap_swap(u,u/2);
        up(u>>1);
    }
}
int main(){
    int n,m=0;cin>>n;
    while(n--){
        string op;cin>>op;
        int k,x;
        if(op=="I"){
            cin>>x;
            m++;
            h[++sizee]=x;
            ph[m]=sizee;
            hp[sizee]=m;
            up(sizee);
        }
        else if(op=="PM")cout<<h[1]<<endl;
        else if(op=="DM"){
            heap_swap(1,sizee);
            sizee--;
            down(1);
        }else if(op=="D"){
            cin>>k;
            int u=ph[k];
            heap_swap(u,sizee);
            sizee--;
            up(u);
            down(u);
        }else if(op=="C"){
            cin>>k>>x;
            h[ph[k]]=x;
            down(ph[k]);
            up(ph[k]);
        }
    }
    return 0;
}

#哈希表

哈希表一般有 1.开放寻址法,2.拉链法 来实现

##模拟散列表

https://www.acwing.com/problem/content/842/

----c++版
###拉链法
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+3;

int h[N],e[N],ne[N],idx;

void insert(int x){
    //把数映射到小范围内,最坏有N条链
    int k=(x%N+N)%N; //一般模上一个质数,且与2的整次幂尽可能远,加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);
    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;
}
###开放寻址法

只开一维数组,一般是题目数据量的2-3倍

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=200003,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(){
    int n;scanf("%d", &n);
    memset(h, 0x3f,sizeof h);
    while(n--){
        char op[2];int x;
        scanf("%s%d",op,&x);
        int k=find(x);
        if(*op=='I')h[k]=x;
        else{
            if(h[k]!=null)puts("Yes");
            else puts("No");
        }
    }
    return 0;
}
###关于memset

memset是按照字节赋值的,所以如果给2的话,每个数组元素的每一个字节都会被赋值2,然后累加起来,数组元素不会得到2,而是很大的数

所以memset可以对得上的数只有1,0,-1 或者0x3f(无穷大)

##字符串哈希方式

https://www.acwing.com/problem/content/843/
前缀哈希法
将字符串中的每一位看作某( p )进制数的数字
可以百度某进制数的公式

为了限制p进制数的大小,我们常常把该数mod 某个数Q
但这样可能有冲突

所以前提是人品足够好,且不能映射成0

研究表明,当p为131或者13331时,Q为 2 64 2^{64} 264时在99.99%的情况下不会有冲突

----c++版

技巧:用unsigned long long 存数就不用取模了,溢出相当于取模

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef unsigned long long ULL;
const int N=1e6+10,P=131;
int n,m;
char str[N];
ULL h[N],p[N];
ULL 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);//不能映射到0上
    p[0]=1;
    for(int i=1;i<=n;i++){
        p[i]=p[i-1]*P;//先把进制的幂次算好
        h[i]=h[i-1]*P+str[i];//把字符串按前缀哈希好
    }
    while(m--){
        int l1,l2,r1,r2;
        scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        if(get(l1,r1)==get(l2,r2))puts("Yes");
        else puts("No");
    }
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值