Data Structure

Template

Single Link List

【Data Structure】
SingleLinkList
【Pattern】

#include <iostream>
using namespace std;
const int N = 100010;
/*每个节点按顺序都有编号,存储在相应数组的下标中
  每个节点i都有对应的值和下一个节点的位置,分别存储在e[i]和ne[i]中
  idx是当前需要分配的节点
*/
int head, e[N], ne[N], idx;
//初始化,-1代表为空节点,0代表从下标0开始分配节点
void init(){
    head = -1;
    idx = 0;
}
//在头节点后插入值为x的节点
void add_to_head(int x){
    e[idx] = x;
    ne[idx] = head;
    head = idx;
    idx ++;
}
//在k节点后插入值为x的节点
void add(int k, int x){
    e[idx] = x;
    ne[idx] = ne[k];
    ne[k] = idx;
    idx ++;
}
//删除k节点后面的节点
void del(int k){
    ne[k] = ne[ne[k]];
}
//打印所有节点
void print(int head){
	for(int i = head; i != -1; i = ne[i])
		cout << e[i] << ' ';
}

Double Link List

【Data Structure】
在这里插入图片描述
【Pattern】

#include<iostream>
#include<string>
using namespace std;
const int N = 100010;
int e[N], l[N], r[N], idx;
/*
这里为了方便,把0设置为第一个节点,把1设置为最后一个节点(这里的0和1不放任何节点)
idx 从2开始出发,这里的物理地址并不代表逻辑地址
r[i]代表i右边的节点,l[i]代表i左边的节点
*/
void init(){
    r[0] = 1;
    l[1] = 0;
    idx = 2;
}
/*
在k的右边插入值为x的节点
*/
void add(int k, int x){
    e[idx] = x;
    r[idx] = r[k];
    l[idx] = k;
    l[r[k]] = idx;
    r[k] = idx;
    idx ++;
}
//删除第k个节点
void del(int k){
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}
//打印所有的节点
void print(int r[]){
	for(int i = r[0]; i != 1; i = r[i])
		cout << e[i] << ' ';
}

Stack

【Pattern】

#include<iostream>
#include<string>
using namespace std;
const int N = 100010;
//栈顶指针为tt,静态地生成数组stk[N]比动态地new数组更加节省时间
int stk[N], tt;
//可以加个初始化stk的操作
void init(){
    tt = 0;
}
//向栈顶插入一个元素x
void push(int x){
    stk[++ tt] = x;
}
//从栈顶弹出一个元素
void pop(){
    tt --;
}
//判断栈是否为空
bool empty(){
    if(tt > 0)
        return true;
    else 
        return false;
}
//查询栈顶元素
int query(){
    return stk[tt];
}

Queue

【Pattern】

#include<iostream>
#include<string>
using namespace std;
const int N = 100010;
//hh是队头,tt是队尾
int q[N], hh, tt;
//hh和tt相等的时候不是空的,是有一个元素的
void init(){
    hh = 0;
    tt = -1;
}
void push(int x){
    q[++ tt] = x;
}
void pop(){
    hh ++;
}
bool empty(){
    if(hh > tt)
        return true;
    else 
        return false;
}
int query(){
    return q[hh];
}

Monotonic_Stack | Monotonic_Queue(Stack和Queue中的元素是单调的)

【Pattern】

#include<iostream>
using namespace std;
//数组长度一般定义为常量
const int N = 1000010;
//队列q放的是窗口有用元素的下标,这样是为了随着窗口移动方便弹出有用的窗口元素
//a放的是数组元素
int q[N], a[N], hh, tt;
void init(){
    hh = 0;
    tt = -1;
}
bool empty(){
    if(hh > tt)
        return true;
    else 
        return false;
}
int main(){
    int n, k;
    cin >> n >> k;
    //存储输入的元素
    for(int i = 1; i<=n; i++) cin >> a[i];
    //初始化队列
    init();
    //随着窗口的移动,窗头元素需剔除,窗尾必须添加一个元素,这都需要在单调队列中更新
    for(int i = 1; i <= n; i++){
        /*即窗口向右滑,需要将窗口的头元素去除,
          此时需要将与窗口头元素相等的队列中的头元素去除掉(更新队列中的有用元素)
        */
        if(!empty() && i - k == q[hh])hh ++;
        //去除无用的元素
        while(!empty() && a[q[tt]] <= a[i])tt --;
        //插入有用有用元素
        q[++ tt] = i;
        //当前窗口是满的,便输出队尾元素
        if(i >= k)cout << a[q[hh]] << ' ';
    }
    return 0;
}

KMP

【Instruction】
1、原理:
在这里插入图片描述

2、next数组:
在这里插入图片描述

【Pattern】

#include<iostream>
using namespace std;
const int N = 100010, M = 1000010;
int n, m;
//p是模式串,s是原始串
char p[N], s[M];
//ne是next数组
int ne[N];
int main(){
    //p和s是数组开始的地址,p+1和s+1说明数组从下标1开始输入
    cin >> n >> p + 1 >> m >> s + 1;
    //ne数组:模式串自身作为原始串查询,应为ne[1]默认为0,因此从i=2开始
    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;
    }

    //i代表的是s的数组下标,j代表的是p的数组下标,需要错开比较,是为了使得ne数组的表示更加方便
    for(int i = 1, j = 0; i <= m; i++){
        //j不能从0开始并且当前的字符不匹配,j就开始移动到ne[j]位置,循环比较
        while(j && s[i] != p[j + 1])j = ne[j];
        //要是匹配上,则继续匹配下一个字符
        if(s[i] == p[j + 1])j ++;
        //要是模式串匹配完成
        if(j == n){
            //返回的字符串起始值是以0开始的
            cout << i - n << ' '; 
            //原始串中有许多模式串,需要继续比较
            j = ne[j];
        }
    }
    return 0;
}

Trie

【Data Structure】
在这里插入图片描述

【Pattern】

#include<iostream>
/*其实C++中的标准库中已经包含了scanf和printf函数,
  加不加这个头文件都无所谓
*/
#include<cstdio>
using namespace std;
const int N = 100010;
/*N表示的是trie的最大节点数目,
  26表示每个字符节点的最大儿子个数,因为是26个字母

  cnt[i]表示的是以i节点结尾的字符串的个数,
  因为在trie中以i节点结尾只单独代表一个字符串,但是可能有多个重复的存在trie中

  idx表示当前分配的节点编号
*/
int son[N][26], cnt[N], idx;
char str[N];
void insert(char s[]){
    //从0号节点开始
    int p = 0;
    //字符串是以0结尾的,直接通过s[i]来判断字符串是否已经读到尾部了
    for(int i = 0; s[i]; i ++){
        //将字符a-z映射到0-25
        int x = s[i] - 'a';
        //如果p节点的x儿子的字符不存在,则添加一个新的节点,节点编号为++idx
        if(!son[p][x])son[p][x] = ++ idx;
        p = son[p][x];
    }
    //以该字符串结尾的数量加1
    cnt[p] ++;
}
int query(char s[]){
    int p = 0;
    for(int i = 0; s[i]; i++){
        int x = s[i] - 'a';
        //不存在该字符串,返回0
        if(!son[p][x])return 0;
        p = son[p][x];
    }
    return cnt[p];
}
int main(){
    int n;
    cin >> n;
    while(n --){
        /*操作数设成长度为2的数组是为了存放多余的空格和换行符
         (有些测试样例会多输入,为了避免这种情况,我们只获取真正的操作数即opt[0])
        */
        char opt[2];
        cin >> opt >> str;
        if(opt[0] == 'I')insert(str);
        else if(opt[0] == 'Q')cout << query(str) << endl;
    }
    return 0;
}

Union Find

【Instruction】
Union:将两个集合合并
Find:询问两个元素是否在一个集合当中

原理:每一个集合用树来存储,而树用一维数组来存储。树根的编号就是集合的编号,每个节点存储他的父节点,p[x]表示为x的父节点

问题1:如何判断树根 if(p[x] == x)
问题2:如何求x的集合编号:while(p[x] != x)x = p[x]
问题3:如何合并两个集合:p[x]是x的集合编号,p[y]是y的集合编号。p[x] = y
【Pattern】

#include<iostream>
using namespace std;
const int N = 100010;
int n, m;
int p[N];
//找到编号为x的数所属的集合
int find(int x){
    /*当x的父节点不是自己,说明当前节点不是根节点
      路径压缩,使得路径上的节点都指向代表集合类别的根节点
    */
    if(p[x] != x)p[x] = find(p[x]);//这里使用了递归代替了while
    return p[x];
}
int main(){
    cin >> n >> m;
    //初始化,每个数各自在一个集合里,即每个数都各自作为一个根节点
    for(int i = 1; i<=n; i++)
        p[i] = i;
    while(m --){
        int a, b;
        char opt[2];
        cin >> opt >> a >> b;
        if(opt[0] == 'M')p[find(a)] = find(b);
        else if(opt[0] == 'Q'){
            if(find(a) == find(b))
                cout << "Yes";
            else 
                cout << "No";
        }
    }
    return 0;
}

HEAP

【Instruction】以小顶堆为例

  1. 插入一个数:heap[++ size] = x, up(size);
  2. 求集合当中的最小值:heap[1];
  3. 删除最小值:heap[1] = heap[size], size --, down(1);
  4. 删除任意一个元素:heap[k] = heap[size], size – , down(k), up(k);
  5. 修改任意一个元素:heap[k] = x,down(k), up(k);
  6. 将数组改造成堆:down( n/2 ~ 1);

【Pattern】

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100010;
/*s是堆的大小,在std命名空间下不能使用size这个标识符
  ph[i]表示第i个插入的数在堆中的编号,hp[j]表示在堆中编号为j的数是第几个插入的
*/
int h[N], ph[N], hp[N], s;
void heap_swap(int a, int b){
    /*首先我们要交换ph,那么ph[]中的下标的含义应该
    是标号a、b分别是第几次插入的,因此要用到hp
    */
    swap(ph[hp[a]], ph[hp[b]]);
    //其次我们要交换hp,hp[]下标的含义就是编号a, b
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}
void down(int u){
    int t = u;
    //当u的左孩子存在的时候,让t指向u与左孩子的最小值
    if(u * 2 <= s && h[u * 2] < h[t])t = u * 2;
    //当u的右孩子存在的时候,让t指向u与其左右孩子中的最小值
    if(u * 2 + 1 <= s && h[u * 2 + 1] < h[t])t = u * 2 + 1;
    //如果最小值不是自己,则需要交换并且递归down下去
    if(t != u){
        heap_swap(u, t);
        down(t);
    }
}
void up(int u){
    //当u的父节点存在且小于其父节点则交换
    while(u / 2 && h[u / 2] > h[u]){
        //交换父节点和自己
        heap_swap(u / 2, u);
        //继续up
        u /= 2;
    }
}
int main(){
    //m表示的是第m个插入的数
    int n, m = 0;
    cin >> n;
    int k, x;
    char opt[10];
    while(n --){
        //输入操作符opt
        cin >> opt;
        if(!strcmp(opt, "I")){
            cin >> x;
            m ++;
            h[++ s] = x;            
            //更新ph和hp数组
            ph[m] = s;
            hp[s] = m;
            /***************************************
             因为up()中使用过heap_swap,会更改ph和hp数组,
             因此up方法必须要在对ph和hp数组操作之后
            */
            up(s);
        }
        else if(!strcmp(opt, "PM"))
            cout << h[1] << endl;
        else if(!strcmp(opt, "DM")){
            //heap_swap顺便把h[1] = h[s]的事情做了
            heap_swap(1, s);
            s --;
            down(1);
        }
        else if(!strcmp(opt, "D")){
            cin >> k;
            /*这里一定要用k = ph[k]保存第k个插入点的下标
             因为在此处heap_swap操作后ph[k]的值已经发生 
             如果在up,down操作中仍然使用ph[k]作为参数就会发生错误
            */
            k = ph[k];
            heap_swap(k, s);
            s --;
            down(k);
            up(k);
        }
        else if(!strcmp(opt, "C")){
            cin >> k >> x;
            k = ph[k];
            h[k] = x;
            down(k);
            up(k);
        }
    }
    return 0;
}

Hash(都采用取模的方法确定address)

【拉链法】

#include<iostream>
#include<cstring>
using namespace std;
const int N = 100010;
//h[N]是链表头数组
int h[N], e[N], ne[N], idx;
//插入x
void insert(int x){
    //当x是负数的时候,x%N的值是负数,为了将他变成正数,采取下面的做法
    int k = (x % N + N) % N;//k是x的地址
    e[idx] = x;
    ne[idx] = h[k];
    h[k] = idx ++; 
}
//查找x
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;
    cin >> n;
    //一定要初始化链表为空
    memset(h, -1, sizeof(h));
    while(n --){
        int x;
        char opt[2];
        cin >> opt;
        if(opt[0] == 'I'){
            cin >> x;
            insert(x);
        }
        else if(opt[0] == 'Q'){
            cin >> x;
            if(find(x))cout << "Yes" << endl;
            else cout << "No" << endl;
        }
    }
    return 0;
}

【散列法】

#include<iostream>
#include<cstring>
using namespace std;
//一般再散列的地址空间开2~3倍就可以了,null设置成无限大即可
const int N = 200020, null = 0x3f3f3f3f;
int h[N];
//这里的find是寻址函数,不是判断x是否在集合中
int find(int x){
    int k = (x % N + N) % N;
    //当当前地址上的值不是null且也不是x的时候,向下寻址
    while(h[k] != null && h[k] != x){
        k ++;
        //当寻址到最后一个点的时候,说明不存在
        if(k == N)k = 0;
    }
    return k;
}
int main(){
    int n;
    cin >> n;
    //记得初始化
    memset(h, 0x3f, sizeof(h));
    int x;
    char opt[2];
    while(n --){
        cin >> opt >> x;
        //find还可以找到x插入的点在哪
        int k = find(x);
        if(opt[0] == 'I')h[k] = x;
        else if(opt[0] == 'Q'){
            if(h[k] != null)cout << "Yes" << endl;
            else cout << "No" << endl;
        }
    }
    return 0;
}

【哈希字符串】

#include<iostream>
using namespace std;
typedef unsigned long long ULL;//用来取模的数
const int N = 100010, P = 131;//经证明P取131冲突最小;
int n, m;
char str[N];//用来存储字符串
/*h[i]表示长度为i的字符串前缀的hash值
  字符串前缀的hash值是把字符串看作P进制的数,
  将其转换成十进制就是它的hash值
  每个字符用ASCII码表示
  p[i]表示P的i次方
  将其定义为ULL类型的值是为了“让溢出表示取模”
*/
ULL h[N], p[N];
//区间在l~r区间的字符串的hash值可以通过h[i]获得
ULL get(int l, int r){
    return h[r] - h[l - 1] * p[r - l + 1];
}
int main(){
    cin >> n >> m >> str + 1;
    p[0] = 1;
    for(int i = 1; i <= n; i ++){
        //让p[i]初始化为P的i次方
        p[i] = p[i - 1] * P;
        //初始化所有的字符串前缀的hash值
        h[i] = h[i - 1] * P + str[i];
    }
    while(m --){
        int l1, r1, l2, r2;
        cin >> l1 >> r1 >> l2 >> r2;
        //如果这两段字符串的hash值相同,说明这两个字符串相等
        if(get(l1, r1) == get(l2, r2))cout << "Yes" << endl;
        else cout << "No" << endl;
    }
    return 0;
}
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值