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
    评论
内含资源如下: 1.基本数据结构 1.1.Array ........... 动态数组 1.2.LinkedList ... 链表 1.3.BST .............. 二分搜索树 1.4.MapBST ..... 二分搜索树(用于实现映射) 1.5.AVLTree ...... AVL树 2.接口 2.1.Queue ........... 队列接口 2.2.Stack .............. 栈接口 2.3.Set .................. 集合接口 2.4.Map ............... 映射接口 2.5.Merger .......... 自定义函数接口 2.6.UnionFind ..... 并查集接口 3.高级数据结构 3.1.ArrayQueue .......................... 队列_基于动态数组实现 3.2.LinkedListQueue .................. 队列__基于链表实现 3.3.LoopQueue ........................... 循环队列_基于动态数组实现 3.4.PriorityQueue ....................... 优先队列_基于最大二叉堆实现 3.5.ArrayPriorityQueue ............. 优先队列_基于动态数组实现 3.6.LinkedListPriorityQueue ..... 优先队列_基于链表实现 3.7.ArrayStack ............................. 栈_基于动态数组实现 3.8.LinkedListStack ..................... 栈_基于链表实现 3.9.BSTSet ..................................... 集合_基于二分搜索树实现 3.10.LinkedListSet ....................... 集合_基于链表实现 3.11.BSTMap ................................ 映射_基于二分搜索树实现 3.12.AVLTreeMap ....................... 映射_ 基于AVL树实现 3.13.LinkedListMap .................... 映射_基于链表实现 3.14.MaxHeap ............................. 最大二叉堆 3.15.SegmentTree ...................... 线段树 3.16.Trie ......................................... 字典树 3.17.QuickFind ............................ 并查集_基于数组实现 3.18.QuickUnion ......................... 并查集_基于树思想实现
以下是C++语言中实现顺序表的基本代码: ```cpp #include<iostream> using namespace std; const int MAXSIZE = 100; // 定义最大容量 template <typename T> class SeqList { private: T data[MAXSIZE]; // 存储元素的数组 int length; // 当前元素个数 public: SeqList(); // 构造函数 ~SeqList(); // 析构函数 bool isEmpty(); // 判断是否为空 bool isFull(); // 判断是否已满 int getLength(); // 获取当前元素个数 bool insert(T x, int pos); // 在指定位置插入元素 bool remove(int pos); // 删除指定位置的元素 bool get(int pos, T& x); // 获取指定位置的元素 void printList(); // 打印顺序表中所有元素 }; template <typename T> SeqList<T>::SeqList() { length = 0; } template <typename T> SeqList<T>::~SeqList() { length = 0; } template <typename T> bool SeqList<T>::isEmpty() { return length == 0; } template <typename T> bool SeqList<T>::isFull() { return length == MAXSIZE; } template <typename T> int SeqList<T>::getLength() { return length; } template <typename T> bool SeqList<T>::insert(T x, int pos) { if (pos < 1 || pos > length + 1 || isFull()) return false; for (int i = length; i >= pos; i--) { data[i] = data[i - 1]; } data[pos - 1] = x; length++; return true; } template <typename T> bool SeqList<T>::remove(int pos) { if (pos < 1 || pos > length) return false; for (int i = pos - 1; i < length - 1; i++) { data[i] = data[i + 1]; } length--; return true; } template <typename T> bool SeqList<T>::get(int pos, T& x) { if (pos < 1 || pos > length) return false; x = data[pos - 1]; return true; } template <typename T> void SeqList<T>::printList() { for (int i = 0; i < length; i++) { cout << data[i] << " "; } cout << endl; } ``` 注意,这里使用了模板类,可以存储任意类型的元素。上述代码实现了顺序表的基本操作,包括插入、删除、获取指定位置元素、判断是否为空或已满、打印所有元素等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值