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


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

    #include

    #include

    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<

        }

        return 0;

    }

      • 双链表:区块链

    #include

    #include

    #include

    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

    #include

    #include

    using namespace std;

    stack 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

    #include

    using namespace std;

    stack 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.push(x);

        }

        return 0;

    }

    1. 队列
          • 队列应用

    #include

    #include

    #include

    using namespace std;

    queue 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<

            }else{

                if (q.empty()){

                    puts("YES");

                }else{

                    puts("NO");

                }

            }

        }

        return 0;

    }

          • 单调队列

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

    #include

    #include

    #include

    using namespace std;

    deque q;

    int a[1000010];

    int main(){

        int n,k;

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

        for (int i=0;i

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

        }

        for (int i=0;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

            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

    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<

           j=ne[j];

    }

    }

           return 0;

    }

    1. Trie树

    相当于字典树

    #include

    #include

    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

    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

    #include

    #include

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

    using namespace std;

    priority_queue,greater > 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

    #include

    #include

    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]

            t=u*2;

        }

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

            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

    #include

    #include

    #include

    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]

            t=u<<1;

        }

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

            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<

            }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

    #include

    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

    #include

    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

    #include

    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 b(10);//定义了一个长度为10的vector

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

        size()  返回元素个数

        empty()  返回是否为空

        clear()  清空

        front()/back()

        push_back()/pop_back()

        begin()/end()

        []

        支持比较运算,按字典序

    pair

        first, 第一个元素

        second, 第二个元素

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

    string,字符串

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

        empty()

        clear()

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

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

    queue, 队列

        size()

        empty()

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

        front()  返回队头元素

        back()  返回队尾元素

    pop()  弹出队头元素

    q=queue() 清空q

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

        size()

        empty()

        push()  插入一个元素

        top()  返回堆顶元素

        pop()  弹出堆顶元素

        定义成小根堆的方式:priority_queue, greater > 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位取反

  • 相关阅读:
    算法竞赛入门【码蹄集新手村600题】(MT1280-1300)C语言
    使用ADS进行serdes仿真时,Tx_Diff中EQ的设置对发送端波形的影响。
    CVPR 2019|CFNet:语义分割中的共现特性
    数据库简单介绍 · 数据库分类 · SQL分类
    YoloV1~YoloV4
    【树莓派不吃灰】基础篇⑮ SSH远程访问安全,涉及/etc/hosts.allow白名单 和 /etc/hosts.deny黑名单、ufw防火墙、密钥登录
    Huggingface Transformers各类库介绍(Tokenizer、Pipeline)
    会话管理——Cookie和 Session
    P1529 [USACO2.4] 回家 Bessie Come Home 题解
    Jmeter(八):jmeter接口自动化测试操作流程、计数器、定时器详解
  • 原文地址:https://blog.csdn.net/Keven_11/article/details/126388456