C++基础:数据结构 代码模板

  1. 链表
    • 单链表:链式前向星

#include<iostream>

#include<cstring>

using namespace std;

const int N=1e5+10;

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++;

}

void remove(int k){//删除

    ne[k]=ne[ne[k]];

}

void add(int k,int x){//插入

    e[idx]=x;

    ne[idx]=ne[k];

    ne[k]=idx++;

}

int main(){

    init();

    int m;

    cin>>m;

    while (m--){

        char c;

        cin>>c;

        int k,x;

        if (c=='H'){

            cin>>x;

            add_to_head(x);

        }else if (c=='D'){

            cin>>k;

            if (!k){

                head=ne[head];

            }

            remove(k-1);

        }else{

            cin>>k>>x;

            add(k-1,x);

        }

    }

    for (int i=head;i!=-1;i=ne[i]){

        cout<<e[i]<<' ';

    }

    return 0;

}

    • 双链表:区块链

#include<iostream>

#include<cstdio>

#include<string>

using namespace std;

const int N=1e5+5;

int e[N],l[N],r[N],idx;

void init(){

    l[1]=0;

    r[0]=1;

    idx=2;

}

void add(int k,int x){//插入

    e[idx]=x;

    l[idx]=k;

    r[idx]=r[k];

    l[r[k]]=idx;

    r[k]=idx++;

}

void remove(int k){//删除

    l[r[k]]=l[k];

    r[l[k]]=r[k];

}

int main(){

    init();

    int m;

    scanf("%d",&m);

    while (m--){

        string s;

        cin>>s;

        int k,x;

        if (s=="L"){

            scanf("%d",&x);

            add(0,x);

        }else if (s=="R"){

            scanf("%d",&x);

            add(l[1],x);

        }else if (s=="D"){

            scanf("%d",&k);

            remove(k+1);//注意

        }else if (s=="IL"){

            scanf("%d%d",&k,&x);

            add(l[k+1],x);

        }else{

            scanf("%d%d",&k,&x);

            add(k+1,x);

        }

    }

    for (int i=r[0];i!=1;i=r[i]){

        printf("%d ",e[i]);

    }

    return 0;

}

        • 栈应用

#include<iostream>

#include<stack>

#include<string>

using namespace std;

stack<int> st;

int main(){

    int m;

    scanf("%d",&m);

    while (m--){

        string s;

        cin>>s;

        if (s=="pop"){

            st.pop();

        }else if (s=="query"){

            printf("%d\n",st.top());

        }else if (s=="empty"){

            if (st.empty()){

                puts("YES");

            }else{

                puts("NO");

            }

        }else{

            int x;

            scanf("%d",&x);

            st.push(x);

        }

    }

    return 0;

}

        • 单调栈

用法:一般用于输出每个数左边第一个比它小的数之类的问题

可以先暴力,再优化

示例:

#include<iostream>

#include<stack>

using namespace std;

stack<int> st;

int main(){

    int n;

    cin>>n;

    while (n--){

        int x;

        cin>>x;

        while (!st.empty() && st.top()>=x){

            st.pop();

        }

        if (st.empty()){

            cout<<-1<<' ';

        }else{

            cout<<st.top()<<' ';

        }

        st.push(x);

    }

    return 0;

}

  1. 队列
        • 队列应用

#include<iostream>

#include<queue>

#include<string>

using namespace std;

queue<int> q;

int main(){

    int m;

    cin>>m;

    while (m--){

        string s;

        cin>>s;

        if (s=="push"){

            int k;

            cin>>k;

            q.push(k);

        }else if (s=="pop"){

            q.pop();

        }else if (s=="query"){

            cout<<q.front()<<endl;

        }else{

            if (q.empty()){

                puts("YES");

            }else{

                puts("NO");

            }

        }

    }

    return 0;

}

        • 单调队列

用法:滑动窗口。先暴力,在优化,这里deque存下标,且一定用deque!

#include<iostream>

#include<cstdio>

#include<deque>

using namespace std;

deque<int> q;

int a[1000010];

int main(){

    int n,k;

    scanf("%d%d",&n,&k);

    for (int i=0;i<n;i++){

        scanf("%d",&a[i]);

    }

    for (int i=0;i<n;i++){

        if (!q.empty() && i-k+1>q.back()){//注意

            q.pop_back();

        }

        while (!q.empty() && a[q.front()]>=a[i]){

            q.pop_front();

        }

        q.push_front(i);//注意

        if (i>=k-1){

            printf("%d ",a[q.back()]);//注意

        }

    }

    puts("");

    q.clear();//注意

    for (int i=0;i<n;i++){

        if (!q.empty() && i-k+1>q.back()){

            q.pop_back();

        }

        while (!q.empty() && a[q.front()]<=a[i]){

            q.pop_front();

        }

        q.push_front(i);

        if (i>=k-1){

            printf("%d ",a[q.back()]);

        }

    }

    return 0;

}

  1. KMP

对字符串处理的优化,一般是求模式串中出现模板串的次数,先想暴力

#include<iostream>

using namespace std;

const int N=1e5+5,M=1e6+5;

char s[M],p[N];

int n,m,ne[N];//记录下一个

int main(){

       cin>>n>>p+1>>m>>s+1;

       for (int i=2,j=0;i<=n;i++){//i从2开始

       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++){

       while (j && s[i]!=p[j+1]){

              j=ne[j];

}

if (s[i]==p[j+1]){

       j++;

}

if (j==n){

       cout<<i-n<<' ';

       j=ne[j];

}

}

       return 0;

}

  1. Trie树

相当于字典树

#include<iostream>

#include<cstdio>

using namespace std;

const int N=1e5+5;

char str[N];

int son[N][26],cnt[N],idx;

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(){

       int t;

    scanf("%d",&t);

    while (t--){

        char op[2];

        scanf("%s%s",op,str);

        if (op[0]=='I'){

            insert(str);

        }else{

            printf("%d\n",query(str));

        }

}

                     return 0;

}

  1. 并查集

作用:

Ⅰ将两个集合合并 Ⅱ询问两个元素是否在一个集合当中

实现:

存每个点的父节点,每个集合用一棵树来表示,树根编号即集合编号

        判断树根:if (x==p[x])

              求x的集合编号:while (x!=p[x]) x=p[x];

              ∪:p[x]=y;

       优化:路径压缩。存每个点的根节点。

       时间复杂度:О(1)

       普通:

       #include<iostream>

using namespace std;

int p[100005],n,m;

void init(){

    for (int i=1;i<=n;i++){

        p[i]=i;

    }

}

int get(int x){

    if (x==p[x]){

        return x;

    }

    return p[x]=get(p[x]);

}

void merge(int x,int y){

    x=get(x);

    y=get(y);

    if (x!=y){

        p[y]=x;

    }

}

int main(){

    cin>>n>>m;

    init();

    while (m--){

        char c;

        int a,b;

        cin>>c>>a>>b;

        if (c=='M'){

            merge(a,b);

        }else{

            if (get(a)!=get(b)){

                puts("No");

            }else{

                puts("Yes");

            }

        }

    }

    return 0;

}

维护每个集合size(只列出修改地方),输出sz[get(x)]

void init(){

    _for(i,1,n){

        p[i]=i;

        sz[i]=1;

    }

}

void merge(int x,int y){

    x=get(x);

    y=get(y);

    if (x!=y){

        p[y]=x;

        sz[x]+=sz[y];

    }

}

维护每个节点到树的距离

int get(int x){

    if (p[x]!=x){

        int u=get(p[x]);

        d[x]+=d[p[x]];

        p[x]=u;

    }

    return p[x];

}

概念:堆是一棵完全二叉树

作用:

        • 插入一个数
        • 求集合当中的min/max
        • 删除min/max
        • 删除任意一个元素(手写堆)
        • 修改任意一个元素(手写堆)

堆分为大根堆和小根堆,不过都用priority_queue,

其中大根堆是priority_queue<类型>pq;

小根堆是priority_queue<类型,vector<类型>,greater<类型> >pq;

  • STL堆:(堆排序)

#include<iostream>

#include<queue>

#include<cstdio>

#define _for(i,a,b) for(int i=a;i<b;i++)

using namespace std;

priority_queue<int,vector<int>,greater<int> > pq;

int main(){

    int n,m;

    scanf("%d%d",&n,&m);

    _for(i,0,n){

        int x;

        scanf("%d",&x);

        pq.push(x);

    }

    while (m--){

        printf("%d ",pq.top());

        pq.pop();

    }

    return 0;

}

  • 手写堆(分为大根堆和小根堆,此列小根堆)

up是向上调整, down是向下调整。

插入:                         h[++size]=x;  up(size);

求min/max:              h[1];

删除min/max:          h[1]=h[size];  size--;  down(1);

删除任意一个元素:    h[k]=h[size];  size--;  down(k);  up(k);

修改任意一个元素:    h[k]=x;  down(k);  up(k);

注意:左儿子为2*n,右儿子为2*n+1,堆排序从n>>1开始。

原因:n>>1是最后一个不是叶节点的节点。

#include<iostream>

#include<cmath>

#include<cstdio>

using namespace std;

const int N=1e5+5;

int h[N],sz;

void down(int u){

    int t=u;

    if (u*2<=sz && h[u*2]<h[t]){

        t=u*2;

    }

    if (u*2+1<=sz && h[u*2+1]<h[t]){

        t=u*2+1;

    }

    if (u!=t){

        swap(h[u],h[t]);

        down(t);

    }

}

int main(){

    int n,m;

    scanf("%d%d",&n,&m);

    for (int i=1;i<=n;i++){

        scanf("%d",&h[i]);

    }

    sz=n;

    for (int i=n>>1;i;i--){

        down(i);

    }

    while (m--){

        printf("%d ",h[1]);

        h[1]=h[sz--];

        down(1);

    }

    return 0;

}

如果题目问删除第k个数,就直接套,代码略。

但如果题目问删除第k个插入的数,就得用映射数组了:

用ph[k]表示出现在第k次的数原下标,用hp[x]表示下标为x的数是出现第几次。

注意,是记录的下标!

#include<iostream>

#include<cstdio>

#include<cmath>

#include<string>

using namespace std;

const int N=1e5+10;

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

void plus_c(){

    ios::sync_with_stdio(false);

    cin.tie(0);

    cout.tie(0);

}

void heap_swap(int x,int y){

    swap(ph[hp[x]],ph[hp[y]]);

    swap(hp[x],hp[y]);

    swap(h[x],h[y]);

}

void down(int u){

    int t=u;

    if (u<<1<=len && h[u<<1]<h[t]){

        t=u<<1;

    }

    if ((u<<1)+1<=len && h[(u<<1)+1]<h[t]){

        t=(u<<1)+1;

    }

    if (t!=u){

        heap_swap(t,u);

        down(t);

    }

}

void up(int u){

    while (u>>1 && h[u>>1]>h[u]){

        heap_swap(u,u>>1);

        u>>=1;

    }

}

int main(){

    int n,m=0;

    plus_c();

    cin>>n;

    while (n--){

        string s;

        int x,k;

        cin>>s;

        if (s=="I"){//插入

            cin>>x;

            h[++len]=x;

            ph[++m]=len;

            hp[len]=m;

            up(len);

        }else if (s=="D"){//删除

            cin>>k;

            k=ph[k];

            heap_swap(k,len--);

            down(k);

            up(k);

        }else if (s=="C"){//修改

            cin>>k>>x;

            k=ph[k];

            h[k]=x;

            down(k);

            up(k);

        }else if (s=="PM"){//求top

            cout<<h[1]<<endl;

        }else{//删除top

            heap_swap(1,len--);

            down(1);

        }

    }

    return 0;

}

  1. 哈希表

哈希表:一是一种数据结构,分开放寻址发和拉链法,二是字符串哈希方式

主要思想:将x∈(-1e9,1e9)变为h(x)∈(0,1e6),其中h(x)叫哈希函数

主要STL:set、multiset、map

  • 拉链法:

h(x)=x%n; ,每个地址是个槽,相同地址一条链

#include<iostream>

#include<cstring>

using namespace std;

void plus_c(){

    ios::sync_with_stdio(false);

    cin.tie(0);

    cout.tie(0);

}

const int N=1e5+3;

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 count(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(){

    memset(h,-1,sizeof h);

    plus_c();

    int n;

    cin>>n;

    while (n--){

        char c;

        int x;

        cin>>c>>x;

        if (c=='I'){

            insert(x);

        }else if (count(x)){

            puts("Yes");

        }else{

            puts("No");

        }

    }

    return 0;

}

  • 开放寻址法:

h(x)=x%n; 有了往后一个一个的填,数组要长

#include<cstring>

#include<iostream>

using namespace std;

const int N = 2e5 + 3;

const int null = 0x3f3f3f3f;

int h[N];

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;

}

int n;

int main() {

    cin >> n;

    memset(h, 0x3f, sizeof h);

    while (n--) {

        string op;

        int x;

        cin >> op >> x;

        if (op == "I") {

            h[find(x)] = x;

        } else {

            if (h[find(x)] == null) {

                puts("No");

            } else {

                puts("Yes");

            }

        }

    }

    return 0;

}

  • 字符串哈希

全称字符串前缀哈希法,把字符串变成一个p进制数字(哈希值),

实现不同的字符串映射到不同的数字。

对形如 X1X2X3Xn1Xn 的字符串,

采用字符的ascii 码乘上 P 的次方来计算哈希值。

映射公式 (X1×Pn1+X2×Pn2++Xn1×P1+Xn×P0)modQ

注意点:

任意字符不可以映射成0,否则会出现不同的字符串都映射成0的情况,

比如A,AA,AAA皆为0

冲突问题:通过巧妙设置P (131 或 13331) , Q (264)的值,

一般可以理解为不产生冲突。

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

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

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

前缀和公式 h[i+1]=h[i]×P+s[i] (i[0,n1] )h为前缀和数组,s为字符串数组

区间和公式 h[l,r]=h[r]h[l1]×Pr-l+1

区间和公式的理解: ABCDE 与 ABC 的前三个字符值是一样,只差两位,

乘上P的二次方把 ABC 变为 ABC00,

再用 ABCDE - ABC00 得到 DE 的哈希值。

#include<iostream>

#include<cstdio>

using namespace std;

typedef unsigned long long ULL;

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

ULL p[N],h[N];

char s[N];

ULL get(int l,int r){

    return h[r]-h[l-1]*p[r-l+1];

}

int main(){

    int n,m;

    scanf("%d%d%s",&n,&m,s+1);

    p[0]=1;

    for (int i=1;i<=n;i++){

        p[i]=p[i-1]*P;

        h[i]=h[i-1]*P+s[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;

}

  1. STL使用技巧

vector, 变长数组,倍增的思想

vector<int> b(10);//定义了一个长度为10的vector

    vector<int> c(10,3);//定义了一个长度为10、里面每一个元素都为3的vector

    size()  返回元素个数

    empty()  返回是否为空

    clear()  清空

    front()/back()

    push_back()/pop_back()

    begin()/end()

    []

    支持比较运算,按字典序

pair<int, int>

    first, 第一个元素

    second, 第二个元素

    支持比较运算,以first为第一关键字,以second为第二关键字(字典序)

string,字符串

    size()/length()  返回字符串长度

    empty()

    clear()

    substr(起始下标,(子串长度))  返回子串

    c_str()  返回字符串所在字符数组的起始地址,用于scanf

queue, 队列

    size()

    empty()

    push()  向队尾插入一个元素

    front()  返回队头元素

    back()  返回队尾元素

pop()  弹出队头元素

q=queue<int>() 清空q

priority_queue, 优先队列,默认是大根堆

    size()

    empty()

    push()  插入一个元素

    top()  返回堆顶元素

    pop()  弹出堆顶元素

    定义成小根堆的方式:priority_queue<int, vector<int>, greater<int> > q;

stack,

    size()

    empty()

    push()  向栈顶插入一个元素

    top()  返回栈顶元素

    pop()  弹出栈顶元素

deque, 双端队列

    size()

    empty()

    clear()

    front()/back()

    push_back()/pop_back()

    push_front()/pop_front()

    begin()/end()

    []

set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列

    size()

    empty()

    clear()

    begin()/end()

    ++, -- 返回前驱和后继,时间复杂度 O(logn)

    set/multiset

        insert()  插入一个数

        find()  查找一个数

        count()  返回某一个数的个数

        erase()

            (1) 输入是一个数x,删除所有x   O(k + logn)

            (2) 输入一个迭代器,删除这个迭代器

        lower_bound()/upper_bound()

            lower_bound(x)  返回大于等于x的最小的数的迭代器

            upper_bound(x)  返回大于x的最小的数的迭代器

    map/multimap

        insert()  插入的数是一个pair

        erase()  输入的参数是pair或者迭代器

        find()

        []  注意multimap不支持此操作。 时间复杂度是 O(logn)

        lower_bound()/upper_bound()

unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表

    和上面类似,增删改查的时间复杂度是 O(1)

    不支持 lower_bound()/upper_bound(), 迭代器的++,--

bitset, 圧位

    bitset<10000> s;

    ~, &, |, ^

    >>, <<

    ==, !=

    []

    count()  返回有多少个1

    any()  判断是否至少有一个1

    none()  判断是否全为0

    set()  把所有位置成1

    set(k, v)  将第k位变成v

    reset()  把所有位变成0

    flip()  等价于~

    flip(k) 把第k位取反

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值