链表,栈,队列,单调栈,单调队列,KMP算法

链表:

概念:每一个结点只知道旁边的点,通过传递的方式遍历链表。

单向链表(用数组模拟,用结构体模拟的话new比较慢,数组虽然会导致一点空间浪费,但很快)

const int s = 1e6 + 10;
int e[s], ne[s];     //e数组存值,ne数组存指针(即这个下标对应的结点指向哪)
int n, ind, hp;
void init()     //创建链表,hp是头指针,ind是标记目前数组用到哪里
{
    hp = -1;
    ind = 0;
}
void add_to_head(int x)     //在链表头插入结点
{
    e[ind] = x;     
    ne[ind] = hp;     //注意,用新结点指第一个结点,再用头指针指新结点
    hp = ind;
    ind++;     //ind永远指向下一个空的位置
}
void add(int k, int x)     //在物理存储(即数组)位置为k的右边插入元素,即k+1个插入的元素右边
{
    if (k == -1)     //特判在链表头插入
    {
        e[ind] = x;
        ne[ind] = hp;
        hp = ind;
        ind++;
    }
    else
    {
        e[ind] = x;
        ne[ind] = ne[k];
        ne[k] = ind;
        ind++;
    }
}
void remove(int k)     //移除元素
{
    if (k == -1)     //特判移除链表头元素
    {
        hp = ne[hp];
    }
    else
    {
        ne[k] = ne[ne[k]];     //原理就是直接移去指向它的指针(这样就找不到它了,相当于删除,实际上还在)
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n;
    init();
    while (n--)
    {
        int k, x;
        char c;
        cin >> c;
        switch (c)
        {
        case 'H':cin >> x; add_to_head(x); break;
        case 'D':cin >> k; remove(k - 1); break;
        case 'I':cin >> k >> x; add(k - 1, x);
        }
    }
    for (int i = hp; i != -1; i = ne[i])     //从头指针开始传递遍历
    {
        cout << e[i] <<  ';a
    }
}

注意点:分清物理存储结构(即用数组存储)和逻辑存储结构(即链表),链表是结果,数组存储是实现方式

升级为双向链表(都一样的,只是一个结点变得可以指向左右两结点)

模版:

const int s = 1e6 + 10;
int e[s], l[s], r[s];     //e数组存值,l数组存左指针,r数组存右指针(下标对应结点)
int m, ind;
void init()     //创建链表,以0为左指针,1为右指针(他们都没有值)
{
    ind = 2;
    r[0] = 1;
    l[1] = 0;
}
void add(int k, int x)     //在物理存储结构为k(即第k个插入的元素右边)右边插入元素
{
    e[ind] = x;
    l[ind] = k;
    r[ind] = r[k];
    l[r[k]] = ind;     //这里一定要注意顺序
    r[k] = ind;
    ind++;
}
void remove(int k)
{
    r[l[k]] = r[k];
    l[r[k]] = l[k];     //除去物理存储结构位置为k的元素(即第k个插入的元素),去除指向它的指针
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> m;
    init();
    while (m--)
    {
        int k, x;        //这里的k是第几个元素
        string c;
        cin >> c;
        if (c == "L")
        {
            cin >> x; add(0, x);
        }
        else if (c == "R")
        {
            cin >> x; add(l[1], x);
        }
        else if (c == "D")
        {
            cin >> k; remove(k + 1);     
        }                                //加一是因为占用0和1
        else if (c == "IR")
        {
            cin >> k >> x;
            add(k + 1, x);
        }
        else
        {
            cin >> k >> x;
            add(l[k + 1], x);     //加在k的左边相当于加在k左边的右边(这里有个转换)
        }
    }
    for (int i = r[0]; i != 1; i = r[i])
    {
        cout << e[i] << ' ';
    }
}

栈:

概念:可以想象成一个罐子,先进后出。

用数组模拟:

const int s = 1e6 + 10;
int sta[s];
int top = -1;     //初始化头指针为-1,使其永远指向栈顶元素
int m;
void push(int x)
{
    sta[++top] = x;
}
void pop()
{
    top--;     //要删除栈顶元素只要下调栈顶指针,之后入栈就会覆盖旧的数据
}
bool empty()
{
    if (top < 0)return true;
    else return false;
}
int query()     //查询栈顶元素
{
    return sta[top];
}
int main()
{
    cin >> m;
    while (m--)
    {
        int x;
        string c;
        cin >> c;
        if (c == "push") { cin >> x; push(x); }
        else if (c == "pop") { pop(); }
        else if (c == "empty") { if (empty())cout << "YES" << endl; else cout << "NO" << endl; }
        else { cout << query() << endl; }
    }
}

应用:用栈来求表达式的值

给定一个表达式,其中运算符仅包含 +,-,*,/(加 减 乘 整除),可能包含括号,请你求出表达式的最终值。

(2+2)*(1+1)

stack<int>num;
stack<char>op;     //两个栈,一个存数字,一个存操作符
string a;
unordered_map<char, int>pi{ {'+',1},{'-',1},{'*',2},{'/',2} };     //操作符绑定优先级
void eval()     //取一个操作符和两操作数计算
{
    char c = op.top(); op.pop();
    int x, y, ans;
    y = num.top(); num.pop();
    x = num.top(); num.pop();     //注意先取的在右边,后去的在前面
    if (c == '+') ans = x + y;
    else if (c == '-') ans = x - y;
    else if (c == '*') ans = x * y;
    else ans = x / y;
    num.push(ans);     //把结果放回栈
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> a;
    for (int i = 0; i < a.size(); i++)
    {
        char c = a[i];
        if (isdigit(c))     //判断c是否为数字
        {
            int x = 0;
            x = c - '0';
            while (i < a.size() && isdigit(a[++i])) x = x * 10 + a[i] - '0';     //模拟位数
            i--;
            num.push(x);     //是数字直接进数字栈
        }
        else if (c == '(') op.push(c);     //遇到(直接进操作符栈
        else if (c == ')')
        {
            while (op.top() != '(') eval();     //如果遇到)一直计算,知道操作栈中(为栈首
            op.pop();     //去(
        }
        else
        {
            while (op.size() && op.top() != '(' && pi[op.top()] >= pi[c]) eval();     //遇到其他操作符,规则就是
            op.push(c);      //一直进行计算,知道操作栈顶的元素优先级小于要放进的操作符
        }
    }
    while (!op.empty()) eval();     //全输入后一直计算达到结果
    cout << num.top();
}

原理就是将重载符和重载数组合在一起按优先级写成一颗二叉树

(2+2)*(1+1)

就画成 

再如 55*9+55/45+4可画成

可以通过后序遍历,先求所有子树,最后求值

但我们可以通过栈来模拟(核心就是模拟优先级

队列:

想象成中通纸筒,先进先出。

数组模拟:

const int s = 1e6 + 10;
int q[s];
int l = 0, r = -1;     //l为队头指针,r为队尾指针(其初始为-1的原因与栈相同)
int m;
void push(int x)
{
    q[++r] = x;
}
void pop()      //弹出元素就移动队头指针来实现
{
    l++;
}
bool empty()
{
    return l > r;
}
int query()
{
    return q[l];
}
int main()
{
    string c; int x;
    cin >> m;
    while (m--)
    {
        cin >> c;
        if (c == "push") { cin >> x; push(x); }
        else if (c == "empty") { if (empty()) cout << "YES" << endl; else cout << "NO" << endl; }
        else if (c == "pop") pop();
        else cout << query() << endl;
    }
}

单调栈(比较抽象)

概念:就是使栈内的元素成单调增减的关系

应用的场景:求一个数列中在某个元素左边离他最近的、小于它的数

给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。

模版:

const int s = 1e6 + 10;
stack<int>sta;
int a[s];
int n;     //n是数组长度
int main()
{
    cin.tie(0);
    ios::sync_with_stdio(false);
    cin >> n;
    while (n--)
    {
        int x;
        cin >> x;
        while (!sta.empty() && sta.top() >= x) sta.pop();  //把大于讨论元素的栈值从上到下去掉
        if (sta.empty()) cout << "-1" << ' ';    
        else cout << sta.top() << ' ';
        sta.push(x);
    }
}

原理:举个例子 1 5 3 6 7 9 10 2,当讨论2后面的数时,5 3 6 7 9 10 都没有机会是答案,因为是从讨论数往前找,找也是找2,所以说5 3 6 7 9 10 都可以在讨论2时都删了。

变形:

1.如果找左边离最近的大于的数,就直接把sta.top() >= x改成<=就OK。

2.如果找右边的就把数组翻转

单调队列:

和单调栈一样,都是维持队列中的单调性

应用场景:给定一个移动区间,求区间中的最小、大值

给定一个大小为 n≤106�≤106 的数组。

有一个大小为 k� 的滑动窗口,它从数组的最左边移动到最右边。

你只能在窗口中看到 k� 个数字。

每次滑动窗口向右移动一个位置。

以下是一个例子:

该数组为 [1 3 -1 -3 5 3 6 7],k� 为 33。

窗口位置最小值最大值
[1 3 -1] -3 5 3 6 7-13
1 [3 -1 -3] 5 3 6 7-33
1 3 [-1 -3 5] 3 6 7-35
1 3 -1 [-3 5 3] 6 7-35
1 3 -1 -3 [5 3 6] 736
1 3 -1 -3 5 [3 6 7]37

你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值

模版:

const int s = 1e6 + 10;
int j[s], k[s];     //j数组存题目给的数组,k数组是队列数组(存的是原数组的下标)
int n, m;
int main()
{
    cin.tie(0);
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 0; i < n; i++) cin >> j[i];
    int h = 0, t = -1;     //h头指针,t尾指针
    for (int i = 0; i < n; i++)
    {
        if (i - k[h] + 1 > m) h++;     //判断队列头元素是否出了区间
        while (h <= t && j[k[t]] >= j[i]) t--;     //和讨论元素进行比较,特别注意,这里元素是通过队尾出队列的,保证队列头元素一直为队列中作为原数组最大元素的下标,也维持单调性
        k[++t] = i;       //插入讨论元素
        if (i >= m - 1) cout << j[k[h]] << ' ';     //一直输出队头元素就行(因为它就是最大的)判断是因为区间限制,区间一开始就要占m个位
    }
    cout << endl;
    h = 0, t = -1;     
    for (int i = 0; i < n; i++)
    {
        if (i - k[h] + 1 > m) h++;
        while (h <= t && j[k[t]] <= j[i]) t--;     //求最大时就这里符号有变
        k[++t] = i;
        if (i >= m - 1) cout << j[k[h]] << ' ';
    }
}

原理:

队头元素永远是最大、小的

KMP算法

用途:给定一个字符序列,在一个字符串中寻找这个序列的出现位置

给定一个字符串 S(被匹配的串,长),以及一个模式串 P(匹配串,短),所有字符串中只包含大小写英文字母以及阿拉伯数字。

模式串 P 在字符串 S 中多次作为子串出现。

求出模式串 P 在字符串 S 中所有出现的位置的起始下标。

输入样例:
3
aba
5
ababa
输出样例:
0 2

算法说明:

但我们细想一下,因为我们在不匹配的情况出现之前,已经匹配了一段,为什么不利用呢?

由此可见,我们如果知道匹配串每个字符到首字符的字符串中,前缀和后缀最大的相等字母数 j 。当一个字母的后一个字母开始不匹配时,直接将那个字母的位值用对应j位置的匹配字符串开始就行;

举个例子

 

基本思路就这样。

我们来看看代码模版:

 const int s = 1e6 + 10, ss = 1e6 + 10;
char da[s], xiao[ss];     //da数组是字符串,xiao数组是模式串
int ne[ss];     //ne数组存前后缀最大相等长度
int n, m;
int main()
{
    cin >> n >> xiao + 1 >> m >> da + 1;
    for (int i = 2, j = 0; i <= n; i++)     //这一步是找各个字符的j值
    {
        while (j && xiao[j + 1] != xiao[i]) j = ne[j];     //和下面匹配的思路是完全一样的,用到的ne都是已经算好的     
        if (xiao[j + 1] == xiao[i]) j++;     //如果符合,j++。
        ne[i] = j;
    }
    for (int i = 1, j = 0; i <= m; i++)
    {
        while (j && xiao[j + 1] != da[i]) j = ne[j];     //匹配不成功就移到最大相等前缀尾
        if (xiao[j + 1] == da[i]) j++;
        if (j == n)
        {
            cout << i - n << ' ';
            j = ne[j];
        }
    }
}

有什么想法或单纯想喷疑问的都欢迎评论~~~

持续更新中~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值