栈——限定性的数据结构

96 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第12天,点击查看活动详情


一、栈的定义与操作

1.顺序栈

1.定义

1.类型定义

typedef struct ssNode {  
    int data[N];  
    int top;//栈顶下标  
    ssNode():top(-1){}  
}seqStack;  

top=-1表示空栈,当top=N-1表示栈满

2.操作

1.栈空

bool empty(seqStack ss) {  
    return ss.top == -1;  
}  

2.入栈

void push(seqStack& ss, int x) {  
    if (ss.top == N - 1) {  
        cout << "栈满" << endl;  
        return;  
    }  
    ss.data[++ss.top] = x;  
}  

3.获取栈顶元素

int top(seqStack ss){  
    if(empty(ss)){  
        cout<<"栈空"<<endl;  
        return 0;  
    }  
    return ss.data[ss.top];  
}  

4.出栈

void pop(seqStack& ss) {  
    if (empty(ss)) {  
        cout << "栈空" << endl;  
        return;  
    }  
    --ss.top;//将top下移一位  
}  

5.遍历

void ss_traverse(seqStack ss) {  
    while (!empty(ss)) {  
        cout << top(ss) << " ";  
        pop(ss);  
    }  
    cout << endl;  
}  

2.链栈

1.定义

1.类型定义

typedef struct csNode {  
    int data;  
    csNode* next;  
    csNode() :next(NULL) {}  
}*chainStack;  

2.操作

1.判断栈空

bool empty(chainStack cs) {  
    return cs->next == NULL;  
}  

2.入栈

void push(chainStack cs, int x) {  
    chainStack q = new csNode;//创建新节点  
    q->data = x;//赋值  
    q->next = cs->next;  
    cs->next = q;//重新定义cs的链域  
}  

3.获取栈顶元素

int top(chainStack cs) {  
    if (empty(cs))return 0;  
    return cs->next->data;  
}  

4.出栈

void pop(chainStack cs) {  
    if (empty(cs))return;  
    chainStack q = cs->next;  
    cs->next = q->next;  
    delete q;  
    q = NULL;  
}  

5.遍历

void cs_traverse(chainStack cs) {  
    while (!empty(cs)) {  
        cout << top(cs) << " ";  
        pop(cs);  
    }  
    cout << endl;  
}  

二、STL中的栈

1.定义

1.头文件:#include<stack>

2.注:对于适配器容器,默认底层容器为deque

    stack<int>stk;//创建stack对象,底层容器为deque,栈中元素类型为int  
    stack<int, vector<int>>stk1;//创建stack对象,底层容器为vector  

2.操作

    int x;  
    q.push(x);      //将x压入栈顶  
    q.top();        //返回栈顶的元素  
    q.pop();        //删除栈顶的元素  
    q.size();       //返回栈中元素的个数  
    q.empty();      //检查栈是否为空,若为空返回true,否则返回false  

三、栈的应用

1.括号匹配

1.问题:根据输入的字符串,判断括号是否合法

2.括号的合法性:

  1. 每一个左括号的右端都有一个同类型的右括号与他匹配

  2. 每一个右括号的左端都有一个同类型的左括号与它匹配

  3. 两对不同类型括号之间不能骑那套

3.算法步骤

  1. 创建一个元素类型为字符型的栈stk

  2. 依次检查str中的每一个字符str[i],如果str[i]为左括号,则将str[i]入栈;如果为右括号,则检查栈顶元素,分为
    1.如果栈顶元素与str[i]同类型的左括号,则出栈,继续考虑str的下一个字符str[i+1]
    2.如果栈为空或栈顶元素为与str[i]不同类型的左括号,则可以确定str中的括号不合法,检查结束

  3. 当str的所有元素都检查完毕时,检查stk,如果stk为空,则str中的括号合法;否则,str中的括号不合法

bool check_brackets(string str){  
    int i,sz=str.size();  
    stack<char>stk;  
    for(int i=0;i<sz;++i){  
        if(str[i]=='('||str[i]=='[')stk.push(str[i]);//左括号入栈  
        else{  
            if(str[i]!=')'&&str[i]!=']')continue;//跳过非括号字符  
            if(stk.empty())return false;//栈为空,不合法  
            char ch=stk.top();//获取栈顶元素  
            if(str[i]==')'&&ch!='('||str[i]==']'&&ch!='[')  
            return false;//栈顶元素与当前括号不匹配,不合法  
            stk.pop();//出栈  
        }  
    }  
    if(!stk.empty())return false;//栈为空,不合法  
    return true;  
}  

2.算术表达式求值

1.功能:求带括号的四则运算的计算结果,假设操作数都是整数,且括号只有小括号,并假设算术表达式中的运算和括号合法

2.算法步骤

  1. 建立一个double类型的栈opers(操作数栈)和char类型的栈ops(运算符栈)。依次考虑exp的每一个字符,假设当前考虑的字符为exp[i],则分为以下几种情况
    1.如果exp[i]为数字,将exp[i]及其之后的连续数字组装为一个整数并加入操作数栈opers
    2.如果exp[i]为左括号,则直接将exp[i]加入运算符栈ops
    3.如果exp[i]为''或'/',则考虑ops的栈顶元素op,如果op为''或'/',则处理op的运算结果,并继续考虑ops的栈顶元素op,直到栈ops为空或op不为'*'或'/'为止。最后将exp[i]加入运算符栈ops。这里,处理op的运算结果是指:从操作数栈opers中取出两个元素v1,v2(先取栈顶元素,然后出栈,第一次去出的元素为v1,第二次取出的元素为v2),计算v2 op v1v_2\ op\ v_1(注意,v2为左操作数,v1为右操作数),将计算结果加入操作数栈opers,并将op从ops中移除
    4.如果exp[i]为'+'或'-',考虑ops的栈顶元素op,如果op不为'(',则处理op的运算结果,并继续考虑ops的栈顶元素op,直到栈ops为空或op为'('。最后将exp[i]加入运算符栈ops
    5.如果exp[i]为')',考虑ops的栈顶元素op,如果op不为'(',则处理op的运算结果,并继续考虑ops的栈顶元素op,直到op为'(',最后将op出栈

  2. 当exp处理完毕,考虑ops的栈顶元素op,处理op的运算结果,并继续考虑op的栈顶元素,直到ops为空

  3. 此时栈opers中只有一个元素,为最终运算结果

//处理ops的栈顶op的运算结果:从栈opers中取两个数据v1,v,计算v op v1的结果并返回  
double cal(stack<double>& opers, stack<char>& ops) {  
    double v1, v, ret;  
    v1 = opers.top(), opers.pop();//右操作数  
    v = opers.top(), opers.pop();//左操作数  
    char op = ops.top();  
    ops.pop();//运算符  
    switch (op) {  
    case '+':  
        ret = v + v1;  
        break;  
    case '-':  
        ret = v - v1;  
        break;  
    case '*':  
        ret = v * v1;  
        break;  
    case '/':  
        if (v1 == 0)exit(0);//分母为0  
        ret = v / v1;  
        break;  
    }  
    opers.push(ret);//计算结果入栈  
}  
//算术表达式求值  
double calculate(string exp) {  
    stack<double>opers;  
    stack<char>ops;  
    int i, sz = exp.size(), v;  
    for (i = 0; i < sz; i++) {  
        if (isdigit(exp[i])) {//exp为数字,将其后连续的数字组装为一个整数  
            v = exp[i] - '0';  
            while (++i < sz && isdigit(exp[i]))  
                v = v * 10 + exp[i] - '0';  
            opers.push(v);//将组装的整数入栈  
            --i;  
        }  
        else if (exp[i] == '(')ops.push(exp[i]);//将左括号入栈  
        else if (exp[i] == '*' || exp[i] == '/') {  
            while (!ops.empty() && (ops.top() == '*' || ops.top() == '/'))  
                cal(opers, ops);  
            ops.push(exp[i]);  
        }  
        else if (exp[i] == '+' || exp[i] == '-' || exp[i] == ')') {//exp为加号、减号或右括号,消除栈顶的加减乘除  
            while (!ops.empty() && ops.top() != '(')  
                cal(opers, ops);  
            if (exp[i] == ')')ops.pop();//exp[i]为右括号,ops栈顶一定为'(',出栈  
            else ops.push(exp[i]);  
        }  
    }  
    while (!ops.empty())cal(opers, ops);//处理栈ops中的剩余运算符  
    return opers.top();//返回计算结果  
}  

四、单调栈

1.定义

1.定义:栈中的元素始终严格单调

2.递增栈:从栈顶到栈底的元素单调递增的单调栈

3.递减栈:从栈顶到栈底的元素单调递减的单调栈

2.操作

1.入栈和出栈的限制条件

  1. 如果栈空或a[i]大于栈顶元素,则a[i]入栈

  2. 如果a[i]小于栈顶元素,则出栈,知道a[i]大于栈顶元素或栈空,然后将a[i]入栈

2.处理问题

  1. 利用递减栈可以求a[i]左端比a[i]小且离a[i]最近的元素,利用递增栈可以求a[i]左端第一个比a[i]大的元素,利用递增栈可以求a[i]左端第一个比a[i]大的元素

  2. 利用递减栈可以求a[i]左端与a[i]相邻且连续比a[i]大的元素个数,利用递增栈可以求a[i]左邻比其小的第一个元素个数

3.问题

1.求给定序列中每一个元素左端比其小的第一个元素

1.功能:对于序列a,求每一个元素a[i]左端比a[i]小的第一个元素,如果不存在,则为-1

2.算法步骤:

  1. 定义递减栈stk,依次考虑a中的每一个元素a[i]

  2. 如果stk为空,则输出-1,如果a[i]>stk.top(),则栈顶元素为第一个比a[i]小的元素,直接输出stk.top(),a[i]入栈

  3. 如果a[i]≤stk.top(),出栈,知道a[i]>stk.top()

  4. 重复步骤2,3,知道所有元素都考虑完毕

//输出数组a的每一个元素的左邻比其小的值  
void left_smaller(int a[], int n) {  
    stack<int>stk;  
    for (int i = 0; i < n; ++i) {  
        while (!stk.empty() && a[i] <= stk.top())//a[i]不大于栈顶元素,出栈  
            stk.pop();  
        if (stk.empty())cout << -1 << ' ';//栈为空,a[i]左端不存在比其小的数  
        else cout << stk.top() << " ";//栈不空,栈顶即为a[i]左端第一个比其小的数  
        stk.push(a[i]);  
    }  
}  
//输出数组a的每一个元素的左邻比其大的元素的个数  
void left_bigger(int a[], int n) {  
    stack<int>stk;  
    for (int i = 0; i < n; ++i) {  
        while (!stk.empty() && a[i] <= a[stk.top()])stk.pop();  
        if (stk.empty())cout << i << " ";//栈为空,a[i]之前的元素都比a[i]大  
        else cout << i - stk.top() - 1 << ' ';//栈不空,a的下标在区间(i,stk.top())中的元素都比a[i]大  
        stk.push(i);//下标入栈         
    }  
}