Acwing 第二章 数据结构

单链表

image

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int M = 1e5+10;
int val[M],ne[M],idx,head;
int m;

void add_head(int x)
{
    val[idx] = x;
    ne[idx] = head;
    head = idx;
    idx++;
}
void add(int k,int x)
{
    val[idx] = x;
    ne[idx] = ne[k];
    ne[k] = idx;
    idx++;
}
void del(int k)
{
    ne[k] = ne[ne[k]];
}
int main()
{
    memset(ne,-1,sizeof ne);
    idx = 1,head = -1;
    cin>>m;
    for(int i=0;i<m;i++)
    {
        char op;
        int k,x;
        cin>>op;
        switch(op)
        {
            case 'H':
            {
                cin>>x;
                add_head(x);
                break;
            }
            case 'D':
            {
                cin>>k;
                if(k==0) head = ne[head];
                del(k);
                break;
            }
            case 'I':
            {
                cin>>k>>x;
                add(k,x);
                break;
            }
            default:break;
        }
    }
    for(int i=head;i!=-1;i = ne[i])
    {
        cout<<val[i]<<" ";
    }
    return 0;
}

双链表

image
//: # (打卡模板,上面预览按钮可以展示预览效果 ^^)

#include<bits/stdc++.h>
using namespace std;
const int M=1e5+10;
int l[M],r[M],e[M],cur;
void init()
{
    r[0]=1;
    l[1]=0;
    cur=2;//注意索引结点的设置
}
void insert_r(int k,int x)
{
    e[cur]=x;
    l[cur]=k;
    r[cur]=r[k];
    l[r[k]]=cur;//注意:顺序不能反
    r[k]=cur;
    cur++;//注意:更新索引结点
}
void insert_l(int k,int x)
{
    insert_r(l[k],x);
}
void remove(int k)
{
    r[l[k]]=r[k];
    l[r[k]]=l[k];
}
int main()
{
    init();
    int M,x,k;
    cin>>M;
    while(M--)
    {
        string op;
        cin>>op;
        if(op=="L")
        {
           cin>>x;
           insert_r(0,x);
        }
        if(op=="R")
        {
            cin>>x;
            insert_l(1,x);
        }
        if(op=="D")
        {
            cin>>k;
            remove(k+1);
        }
        if(op=="IL")
        {
            cin>>k>>x;
            insert_l(k+1,x);
        }
        if(op=="IR")
        {
            cin>>k>>x;
            insert_r(k+1,x);
        }
    }
    for(int i=r[0];i!=1;i=r[i])
    {
        cout<<e[i]<<" ";
    }
    return 0;
}

表达式求值

image

#include<bits/stdc++.h>
using namespace std;
stack<int>nums;
stack<char>op;
void eval()//作用:分别取出数字栈顶两个数和运算符栈顶的一个数进行运算,并将结果入栈
{
    auto b=nums.top();nums.pop();//注意b a的顺序(先进后出)
    auto a=nums.top();nums.pop();
    auto c=op.top();op.pop();
    int x;
    if(c=='+') x=a+b;
    else if(c=='-') x=a-b;
    else if(c=='*') x=a*b;
    else x=a/b;
    nums.push(x);
}

int main()
{
    unordered_map<char,int>pr{{'+',1},{'-',1},{'*',2},{'/',2}};//定义四则运算符的优先级
    string str;
    cin>>str;
    for(int i=0;i<str.size();i++)
    {
        if(isdigit(str[i]))//字符为数字,就要继续往后走,将连续的数字字符转化为完整的数字(如123 28)
        {
            int x=0,j=i;
            while(j<str.size()&&isdigit(str[j]))
            {
                x=x*10+str[j]-'0';
                j++;
            }//循环结束后str[j]刚好不是数字字符
            nums.push(x);//转化后的数字要入栈
            i=j-1;//注意更新i的位置,考虑到for循环在每次循环结束后i自加,故i为j-1,而不是j
        }
        else if(str[i]=='(') op.push(str[i]);//遇到左括号:入栈
        else if(str[i]==')')//遇到右括号,根据优先级一定要先计算括号里面的表达式
        {
            while(op.top()!='(') eval();
            op.pop();//括号内表达式运算结束后弹出 )
        }
        else//遇到其他字符(+,-,*,/)
        {
            while(op.size()&&pr[op.top()]>=pr[str[i]])//若运算符栈的栈顶优先级大于当前str[i]字符
            {
                eval();//先取栈顶运算
            }
            op.push(str[i]);//再将该字符入栈
        }
    }
    while(op.size()) eval();//处理栈中剩余运算
    cout<<nums.top()<<endl;//最终输出数字栈顶,即为答案
    return 0;
}

单调栈

image
//: # (打卡模板,上面预览按钮可以展示预览效果 ^^)

#include<bits/stdc++.h>
using namespace std;
const int M=1e5+10;
int a[M],N,s[M],top=0;
int main()
{
    scanf("%d",&N);
    for(int i=0;i<N;i++)
    {
        scanf("%d",&a[i]);
        while(top&&s[top]>=a[i]) top--;//注意是>=
        if(top) printf("%d ",s[top]);
        else cout<<"-1 ";
        s[++top]=a[i];
    }
    return 0;
}

单调队列

154. 滑动窗口

image

用一个队列维护窗口里面的所有值
当即将入队的队列比队尾元素小时,就让队尾元素出队,循环直到大于等于队尾元素时,停止,因为没有用
image
求最大值与该思想类似

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e6 + 10;

int n,k;
int a[N];
int q[N];//队列存储的是下标
int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; i ++ )
    {
        cin>>a[i];
    }
    int hh = 0,tt = -1;
    for (int i=0;i<n;i++) //单调队列处理最小值
    {
        if(hh<=tt && i - q[hh] + 1 > k) hh++; //维护当前窗口元素个数与k一致
        while(hh<=tt && a[i] <= a[q[tt]]) tt--; //删去冗余元素,注意是从队尾删除
        q[++tt] = i; //新元素入队
        if(i+1>=k) cout<<a[q[hh]]<<' '; //i要足够大才能输出最值
    }
    puts("");
    hh = 0,tt = -1;
    for (int i = 0; i < n; i ++ ) //单调队列处理最大值
    {
        if(hh<=tt && i - q[hh] + 1 > k) hh++;
        while(hh<=tt && a[i] >= a[q[tt]]) tt--;
        q[++tt] = i;
        if(i+1>=k) cout<<a[q[hh]]<<' ';
    }
    return 0;
}

KMP

image
//: # (打卡模板,上面预览按钮可以展示预览效果 ^^)

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=1e6+10;
int n,m,ne[M];
char s[M],p[N];
int main()
{
    cin>>n>>p+1>>m>>s+1;
    for(int i=2,j=0;i<=n;i++)//ne[]的推导过程,在子串P中推导
    {
        while(j&&p[i]!=p[j+1]) j=ne[j];
        if(p[i]==p[j+1]) j++;
        ne[i]=j;
    }
    for(int i=1,j=0;i<=m;i++)//kmp的匹配过程,在总串S中推导
    {
        while(j&&s[i]!=p[j+1]) j=ne[j];
        if(s[i]==p[j+1]) j++;
        if(j==n)
        {
            cout<<i-n<<" ";//注意本题从0开始计数,不需要+1
            j=ne[j];
        }
    }
    return 0;
}

字典树(Trie)

835. Trie字符串统计

Trie:高效的存储和查找字符串集合的数据结构
image

image

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>

using namespace std;

const int N = 2e4+10;
int son[N][26],idx;//son数组表示字典树,idx表示层数索引
int cnt[N];//以当前结点结尾的单词数量
char x[N];
char op[2];

void insert(char s[])
{
    int cur = 0;//表示当前位于第几层
    for(int i=0;s[i]!='\0';i++)
    {
        int t = s[i] - 'a';
        if(!son[cur][t]) son[cur][t] = ++idx; //没有路径就创造路径,注意是++idx
		//idx是下一字母的层数
        cur = son[cur][t];
    }
    cnt[cur]++;//更新以该结点结尾的单词数量
}
int query(char s[])
{
    int cur = 0;//表示当前位于第几层
    for(int i=0;s[i]!='\0';i++)
    {
        int t = s[i] - 'a';
        if(!son[cur][t]) return 0;//不存在路径,直接返回0
        cur = son[cur][t];
    }
    return cnt[cur];
}
int main()
{
    int n;
    cin >> n;
    for (int i = 0; i < n; i ++ )
    {
        cin>>op>>x;
        if(op[0] == 'I')
        {
            insert(x);
        }
        else cout<<query(x)<<endl;
    }
    return 0;
}

143.最大异或对

image

#include<bits/stdc++.h>
using namespace std;
const int M = 1e5+10;
int n,a[M],son[31*M][2],idx,res;//son[]存字典树
void insert(int x)
{
    int p=0;
    for(int i=30;i>=0;i--)
    {
        int t=x>>i&1;//取第31-i位
        if(!son[p][t]) son[p][t]=++idx;//若为0则说明不存在此节点,建立
        p=son[p][t];//p走到下一层
    }
}
int query(int x)
{
    int p=0,re=0;//re用来以10进制的方式记录最大异或值
    for(int i=30;i>=0;i--)
    {
        int t=x>>i&1;//取第31-i位
        if(son[p][!t])//如果与第31-i位的互异数存在 
        {
            p=son[p][!t];//走到互异的数那一层
            re=re*2+!t;//计算最大异或值
        }
        else
        {
            p=son[p][t];//不存在只能走相同的数的层
            re=re*2+t;//计算最大异或值
        }
    }
    return re;
}
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    scanf("%d",&a[i]);
    for(int i=0;i<n;i++)
    {
        insert(a[i]);//先插入a[i]
        int t=query(a[i]);//求出与a[i]最异的数
        res = max(res,a[i]^t);//比较异或值
    }
    cout<<res;
    return 0;
}

并查集

模板

#include<bits/stdc++.h>
using namespace std;
const int M=1e5+10;
int n,m,p[M];//起初p[]表示每个结点的双亲结点,在find()操作后表示该节点的祖宗结点
int find(int x)//返回x的祖宗结点+路径压缩+递归
{
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) p[i]=i;//初始化每个集合,每个集合的祖宗结点是它数值本身
    while(m--)
    {
        char op[2];
        int a,b;
        scanf("%s%d%d",op,&a,&b);
        if(op[0]=='M')
        {
            p[find(b)]=find(a);//a的祖宗成为b的祖宗的双亲 or b的祖宗成为a的祖宗的双亲
        }
        else
        {
            if(find(a)==find(b)) cout<<"Yes"<<endl;//祖宗一样,证明是一个集合
            else cout<<"No"<<endl;
        }
    }
    return 0;
}

240. 食物链

带权并查集

image
image

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 50005;

int n,k,c,x,y;
int p[N],d[N];

int ans;

int find(int x)
{
    int t = p[x];
    if(p[x]!=x) 
    {   
        p[x] = find(p[x]);
        d[x] += d[t]; //维护距离,find前表示到父节点的距离,find后表示到祖宗结点的距离
    }
    return p[x];
}
int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
    }
    
    while(k--)
    {
        cin>>c>>x>>y;
        if(x>n||y>n)
        {
            ++ans;
            continue;
        }
        int px = find(x),py = find(y);
        if(c==1) //同类,距离取余3为0
        {
            if(px == py && (d[x] - d[y]) % 3 != 0) ans++; //同一集合但距离mod3不为0,假话
            else if(px!=py) //不同集合,则是真话,合并它们并用距离表示同类关系
            {
                p[px] = py;
                d[px] = d[y] - d[x];
            }
        }
        else //x吃y,距离取余3为1
        {
            if(px == py && (d[x] - d[y] - 1) % 3 !=0) ans++; //同一集合但距离mod3不为1,假话
            else if(px != py) //不同集合,则是真话,合并它们并用距离表示捕食关系
            {
                p[px] = py;
                d[px] = d[y] - d[x] + 1;
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

838. 堆排序

#include<bits/stdc++.h>
using namespace std;
const int M=1e5+10;
int h[M],cnt,n,m;
void down(int u)
{
    int t=u;
    if(2*u<=cnt&&h[2*u]<h[t]) t=2*u;//左儿子是否存在?左儿子是否小于父节点?
    if(2*u+1<=cnt&&h[2*u+1]<h[t]) t=2*u+1;//右儿子是否存在?右儿子是否小于父节点或左儿子
    if(t!=u)//最小值不是父节点
    {
        swap(h[t],h[u]);//交换
        down(t);//必须最大限度的down到底
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&h[i]);
        cnt++;
    }
    for(int i=n/2;i;i--)//无序插入不符合小根堆的性质,必须从n/2到堆顶进行down操作
    down(i);
    while(m--)//输出堆顶,删除堆顶
    {
        cout<<h[1]<<" ";
        h[1]=h[cnt--];
        down(1);
    }
    return 0;
}

839. 模拟堆

image

#include <iostream>
#include <algorithm>
#include <string.h>

using namespace std;

const int N = 100010;

int h[N], ph[N], hp[N], cnt;

void heap_swap(int a, int b)
{
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}

void down(int u)
{
    int t = u;
    if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
    if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    if (u != t)
    {
        heap_swap(u, t);
        down(t);
    }
}

void up(int u)
{
    while (u / 2 && h[u] < h[u / 2])
    {
        heap_swap(u, u / 2);
        u >>= 1;
    }
}

int main()
{
    int n, m = 0;
    scanf("%d", &n);
    while (n -- )
    {
        char op[5];
        int k, x;
        scanf("%s", op);
        if (!strcmp(op, "I"))
        {
            scanf("%d", &x);
            cnt ++ ;
            m ++ ;
            ph[m] = cnt, hp[cnt] = m;
            h[cnt] = x;
            up(cnt);
        }
        else if (!strcmp(op, "PM")) printf("%d\n", h[1]);
        else if (!strcmp(op, "DM"))
        {
            heap_swap(1, cnt);
            cnt -- ;
            down(1);
        }
        else if (!strcmp(op, "D"))
        {
            scanf("%d", &k);
            k = ph[k];
            heap_swap(k, cnt);
            cnt -- ;
            up(k);
            down(k);
        }
        else
        {
            scanf("%d%d", &k, &x);
            k = ph[k];
            h[k] = x;
            up(k);
            down(k);
        }
    }

    return 0;
}

哈希

840. 模拟散列表

image
//: # (打卡模板,上面预览按钮可以展示预览效果 ^^)

拉链法

#include<bits/stdc++.h>
using namespace std;
const int M=1e5+3;//打表找出的符合条件的最小质数
int h[M],idx,e[M],ne[M];//数组模拟链表
void insert(int x)
{
    int t=(x%M+M)%M;//让所有数的余数都为正
    e[idx]=x;
    ne[idx]=h[t];//链表操作
    h[t]=idx++;
}
bool find(int x)
{
    int t=(x%M+M)%M;
    for(int i=h[t];i!=-1;i=ne[i])//遍历链表
    {
        if(e[i]==x) return true;
    }
    return false;
}
int main()
{
    int n;
    cin>>n;
    memset(h,-1,sizeof h);//初始化:格式化数组默认为空
    while(n--)
    {
        string op;
        int x;
        cin>>op>>x;
        if(op=="I")
        {
            insert(x);
        }
        if(op=="Q")
        {
            if(find(x)) cout<<"Yes"<<endl;
            else cout<<"No"<<endl;
        }
    }
    return 0;
}

开放寻址法

#include<bits/stdc++.h>
using namespace std;
const int M = 2e5+3;//打表找最小质数,开放寻址法的范围一般是数据范围的2-3倍
int h[M];
int find(int x)
{
    int t=(x%M+M)%M;//让所有数的余数都为正数
    while(h[t]!=x&&h[t]!=-1)//若位置被其他数占用
    {
        t++;
        if(t==M) t=0;//走到范围边界后从头开始找位置
    }
    return t;//若x在哈希表中返回下标,不在哈希表中返回应该插入的位置
}
int main()
{
    int n;
    cin>>n;
    memset(h,-1,sizeof h);//初始化:格式化数组默认为空
    while(n--)
    {
        string op;
        int x;
        cin>>op>>x;
        int t=find(x);
        if(op=="I")
        {
            h[t]=x;
        }
        if(op=="Q")
        {
            if(h[t]!=-1) cout<<"Yes"<<endl;
            else cout<<"No"<<endl;
        }
    }
    return 0;
}

841. 字符串哈希

image
image
image

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10,P = 131;

typedef unsigned long long ULL;

ULL h[N],p[N];

int n,m;
char str[N];
ULL get(int l,int r)
{
    return h[r] - h[l-1] * p[r-l+1]; //取区间字符串的哈希值 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[i] = p[i-1] * P; //预处理P的n次幂
        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)) puts("Yes");
        else puts("No");
    }
    return 0;
}
posted @   安河桥北i  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示