栈 stack

本文介绍了栈这种数据结构的基本概念,包括顺序栈和链式栈的实现方式,并详细探讨了栈在斐波那契数列、括号匹配、回文判断、逆波兰表达式解析和迷宫解题等应用场景中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

栈(stack)是限定仅在表尾进行插入和删除操作的线性表。


一、基本概念

栈是一种后进先出的数据结构。

允许插入和删除的一段称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出的线性表。

1.1 实现方式

根据栈的实现方式,可以分为两类:

1.1.1 顺序栈

用数组的方式存储。

#define MAX_SIZE 100

// 栈的管理结构
typedef struct{
    int data[MAX_SIZE]; //存储栈元素的数组
    int top; //栈顶序号
}Stack;

/*
    初始化栈
*/
void init(Stack * st)
{
    st->top = -1; //假设指向下标-1时,表示空栈,top永远直接指向栈顶元素。
}

/*
    压栈
    返回值 -1:压栈失败 ;0 :压栈成功。
*/
int push(Stack * st, int val)
{
    if(st->top == MAX_SIZE - 1) //栈已满
        return -1;

    st->data[++st->top] = val;

    return 0;
}

/*
    出栈
    返回值 -1:出栈失败 ;0 :出栈成功。
*/
int pop(Stack * st, int * val)
{
    if(st->top == -1)
        return -1;

    *val = st->data[st->top--];

    return 0;
}

以数组方式实现栈往往需要事先确定数组容量的大小。

扩展:

假设有两个存储类型相同的栈,如果为这两个栈各自开辟数组空间,极有可能第一个栈满了,再进栈就溢出了,而另一个栈还有很多存储空间。

针对这类情况,我们可以通过一个数组来存储两个栈。

思路:两个栈的栈底分别置于数组的两段,然后两个栈均向数组中间生长。

#define MAX_SIZE 100

// 栈的管理结构
typedef struct{
    int data[MAX_SIZE];
    int top1;
    int top2;
}Stack;

/*
    初始化栈
*/
void init(Stack * st)
{
    st->top1 = -1;
    st->top2 = MAX_SIZE;
}

/*
    压栈
    返回值 -1:压栈失败 ;0 :压栈成功。
*/
int push(Stack * st, int val, int lrst)
{
    if(st->top1 + 1 == st->top2) //栈满了
        return -1;

    if(lrst == 1) //栈1有元素进栈
        st->data[++st->top1] = val;
    else if(lrst == 2) //栈2有元素进栈
        st->data[--st->top2] = val;

    return 0;
}

/*
    出栈
    返回值 -1:出栈失败 ;0 :出栈成功。
*/
int pop(Stack * st, int * val,int lrst)
{
    if(lrst == 1)
    {
        if(st->top1 == -1)
            return -1;

        *val = st->data[st->top1--];
    }
    else if(lrst == 2)
    {
        if(st->top2 == MAX_SIZE)
            return -1;
        *val = st->data[st->top2++];
    }

    return 0;
}
1.2.2 链式栈

用链表的方式实现。

/*
    栈元素节点
*/
typedef struct StackNode{
    int data;
    StackNode * pre; //指向前一个元素, 指向栈底方向。 个人认为用pre比用next形象。
}StackNode;

typedef struct Stack{
    StackNode * top;
    int cnt; //栈的元素个数
}Stack;

// 初始化栈
void init(Stack * st)
{
    st->top = NULL;
    st->cnt = 0;
}

/*
    压栈
    返回值 -1:压栈失败 ;0 :压栈成功。
*/
int push(Stack * st, int val)
{
    StackNode * node = new StackNode;

    if (!node) //申请内存失败
        return -1;

    node->data = val;
    node->pre = st->top; //新入栈的元素的前一个元素是之前的栈顶元素。

    st->top = node; //修改栈顶为新入栈的元素
    st->cnt++; //栈元素数量+1

    return 0;
}

/*
    入栈
    返回值 -1:出栈失败 ;0 :出栈成功。
*/
int pop(Stack * st, int * val)
{
    if (st->cnt == 0) //空栈
        return -1;

    *val = (st->top)->data;

    StackNode * node = st->top;
    st->top = node->pre;

    delete node;
    st->cnt--;

    return 0;
}


二、std::stack

C++ STL 中提供了栈的实现 —— std::stack。是一个容器适配器,下层容器默认是std::deque。 可参考 C/C++ 语言参考

std::stack 所有元素的进出都必须符合“先进后出”的条件,只有stack顶端的元素,才有机会被外界取用。stack不提供走访功能,也不提供迭代器。

std::stack 提供的操作有:

  • emplace
template <class... Args> void emplace (Args&&... args); // 注意形参是 右值引用

入栈新元素,支持传入右值。

  • push
void push (const value_type& val);
void push (value_type&& val);

入栈新元素,支持传入右值 和 左值。

  • pop
void pop();

栈顶元素出栈。

  • top
reference& top();
const_reference& top() const;

返回栈顶元素。

  • empty
bool empty() const;

判断stack是否为空。也就是size()是否为0。

  • size
size_type size() const;

返回栈的元素数量。

  • swap
void swap (stack& x) noexcept(/*see below*/);

交换两个栈的元素。


三、栈的应用

3.1 斐波那契数列

3.1.1 问题描述

假定你有一雄一雌一对刚出生的兔子,它们在长到一个月大小时开始交配,在第二月结束时,雌兔子产下另一对兔子,过了一个月后它们也开始繁殖,如此这般持续下去。每只雌兔在开始繁殖时每月都产下一对兔子,假定没有兔子死亡,在一年后总共会有多少对兔子?

在一月底,最初的一对兔子交配,但是还只有1对兔子;在二月底,雌兔产下一对兔子,共有2对兔子;在三月底,最老的雌兔产下第二对兔子,共有3对兔子;在四月底,最老的雌兔产下第三对兔子,两个月前生的雌兔产下一对兔子,共有5对兔子;……如此这般计算下去,兔子对数分别是:1, 1, 2, 3, 5, 8, 13, 21, 34, 55,89, 144, …看出规律了吗?从第3个数目开始,每个数目都是前面两个数目之和。这就是著名的斐波那契(Fibonacci)数列。

3.1.2 解析

以上问题可转化为如下数学公式:

这里写图片描述

代码如下:

#include <iostream>       

int fb(int i) // 第i个斐波那契数
{
    if (i <= 2)
        return 1;

    return fb(i - 1) + fb(i - 2);
}

int main()
{
    int i;

    for (i = 1; i <= 12; i++)
        std::cout << fb(i) << " ";

    std::cout << std::endl;

    return 0;
}

结果如下:
这里写图片描述


3.2 括号匹配

3.2.1 问题描述

假设有一个字符串str,其中只包含有”(“,”)”,”{“,”}”,”[“,”]”,”<”,”>”这样的字符,或没有。括号要成对匹配,不能有交叉,如(({}))是匹配的,(([])和{([]})是不匹配的。

现给定一个字符串,判断是否匹配。

3.2.2 解析
  1. 建立一个空栈.

  2. 对字符串中每一个字符做如下操作:

    1. 如果字符为左括号:

      • 将字符压栈;

      • 重复操作(操作下一个字符);

    2. 如果字符为右括号,则查看当前栈顶的字符是否和此字符匹配:

      • 如果匹配,栈顶元素弹出,操作下一个字符,跳到2

      • 否则,括号不匹配,返回不匹配

    3. 其他字符,则操作下一个字符

  3. 处理完字符串之后,查看栈是否为空:

    • 如果栈空,返回匹配

    • 否则,返回不匹配

代码如下:

#include <iostream>       
#include <string>
#include <stack>

using namespace std;

#define is_left(c)  (((c)=='(')||((c)=='{')||((c)=='[')||((c)=='<'))

/*
    ASCII值:
    '(': 40  ; ')': 41 ;
    '<': 60  ; '>': 62 ; 
    '[': 91  ; ']': 93 ; 
    '{': 125 ; '}': 125 ; 
*/
#define is_pair(l,r)   ((l)=='('?(r)==')':(r)==(l)+2)

bool ispair(string & str)
{
    if (str.length() == 0)
    {
        return true;
    }
    int i = 0;

    stack<char> st;
    st.push(str.at(i));
    i++;

    while (i < str.length())
    {
        if (is_left(str.at(i))) //左括号
        {
            st.push(str.at(i));
            i++;
            continue;
        }
        else //右括号
        {
            if (st.size() > 0 && is_pair(st.top(), str.at(i)) )
            {
                st.pop();
                i++;
                continue;
            }
            return false;
        }
    }

    if (st.size() == 0)
        return true;
    else
        return false;
}

int main()
{
    string str;

    while(1)
    {
        cin >> str;

        cout << ispair(str) << endl;
    }

    return 0;
}

结果:

这里写图片描述


3.3 回文

3.3.1 问题描述

回文字符串是指从左到右和从右到左相同的字符串。

给定一个字符串,判断是否回文?

3.3.2 解析

做法可以分为两种,一是利用栈的做法,二是不利用栈。

// 不利用栈,首尾相比较
bool ishuiwen1(char * str, int len){

    char * beg = str;
    char * end = str + len - 1;

    while (beg < end)
    {
        if (*beg != *end)
            return false;

        beg++;
        end--;
    }

    return true;
}

// 利用栈
bool ishuiwen2(char * str, int len)
{
    stack<char> st;

    int i = 0;
    while (i < len / 2)
    {
        st.push(str[i++]);
    }

    if (len / 2 == 1)
        i++;

    while (str[i] != '\0')
    {
        if (str[i] == st.top())
            st.pop();
        i++;
    }

    return st.size() == 0;
}


3.4 逆波兰表达式( 四则运算 )

3.4.1 问题描述

逆波兰表达式 是一种不需要括号的后缀表达式 。 对于“ 3 + (4 -1) * 3 + 6 / 2”这样常见的四则运算式子,我们称之为 中缀表达式 。当转换成逆波兰表达式时,能方便的处理四则运算。

对于上述的四则运算式子,转换成逆波兰表达式之后将变为:”3 4 1 - 3 * + 6 2 / + ” 。

如何利用逆波兰表达式处理四则运算:

从左到右遍历表达式的每个数字和符号,遇到数字就入栈,遇到符号,就将栈顶两个数字出栈,进行运算,运算结果出栈,一直到最终获得结果。

更多关于逆波兰表达式的介绍参考 维基百科 - 逆波兰表达式

3.4.2 解析
#include <iostream>  
#include <stack>      //use STL  
#include <stdio.h>  
#include <string.h>  
#include <stdlib.h>  
using namespace std;

const int MAXSIZE = 256;

int InfixToPostfix(char *infix, char *postfix);
double Calculate(char *arr);

int main() 
{
    cout << "四则运算,请输入运算式:" << endl; // prints 四则运算  
    char in[MAXSIZE] = { 0 };
    char postfix[MAXSIZE] = { '\0' };
    fgets(in, MAXSIZE, stdin);

    if (InfixToPostfix(in, postfix) != 1)
    {
        cout << "InfixToPostfix wrong!!!";
        return -1;
    }
    puts(in); puts(postfix);
    cout << Calculate(postfix);

    return 0;

/*
将中缀表达式转换为后缀表达式
参数:infix 指向中缀表达式,以回车键即\n结尾。
postfix 指向后缀表达式临时缓冲区,用来存放转换后的结果。
附转换规则:从左到右遍历中缀表达式的每个数字和符号,若是数字则直接保存在postfix数组中;若是符号,则判断其与栈顶符号的优先级,是右括号或者优先级不大于栈顶符号,则栈顶元素依次出栈并输出,直到遇到左括号或者栈空时,才将刚才的那个符号入栈。
*/
int InfixToPostfix(char *infix, char *postfix)
{
    stack<char> s;
    char c, e;
    int j = 0, i = 0;
    c = *(infix + i); //取出中缀表达式中的第一个字符  
    i++;
    while ('\n' != c) //遇到换行符,表示转换结束  
    {
        while (c >= '0'&&c <= '9') //先判断一下取出的字符是否是数字,如果是数字的话,则直接存入postfix数组  
        {
            postfix[j++] = c;
            c = *(infix + i);
            i++;
            if (c<'0' || c>'9') //如果不是数字,则在后面添加空格,以便区分各个符号  
            {
                postfix[j++] = ' ';
            }
        }
        if (')' == c) //不是数字,则判断是否为右括号。[括号的优先级最高,所以,如果是右括号的话,就得先进行括号里的各种运算]  
        {
            e = s.top(); s.pop();
            while ('(' != e) //直到遇到左括号为止  
            {
                postfix[j++] = e;
                postfix[j++] = ' ';
                e = s.top(); s.pop();
            }
        }
        else if ('+' == c || '-' == c) //如果是加减号,因为他俩的优先级最低了,所以此时先将栈里的所有符号出栈后(除非遇到左括号),再把此符号入栈  
        {
            if (!(s.size())) //如果是空栈,则直接将加减号入栈  
            {
                s.push(c);
            }
            else//如果不是空栈,首先将所有优先级大于加减的出栈,然后再把加减号入栈  
            {
                do{
                    e = s.top(); s.pop();
                    if ('(' == e)
                    {
                        s.push(e);
                    }
                    else
                    {
                        postfix[j++] = e;
                        postfix[j++] = ' ';
                    }
                } while (s.size() && '(' != e);  //将栈里的所有符号出栈(除非遇到左括号)  
                s.push(c); //最后将新来的加减号再入栈  
            }
        }
        else if ('*' == c || '/' == c || '(' == c) //如果是乘除号或左括号,因为他们的优先级高,所以直接入栈。  
        {
            s.push(c);
        }
        else if ('\n' == c) //判断一下,所有符号是否都已转换完成  
        {
            break;
        }
        else //能走到这个else的,都是我不认识的符号了  
        {
            // printf("\nError:input error,the character %d cann't recognize!\n",c);  
            return -1;
        }
        c = *(infix + i); //取出下一个字符进行转换  
        i++;
    }
    while (s.size()) //转换完成后,栈里可能还有没出栈的运算符号  
    {
        e = s.top(); s.pop();
        postfix[j++] = e;
        postfix[j++] = ' ';
    }
    return true;
}

/*
计算后缀表达式的结果
参数:arr使用空格分隔的后缀表达式字符串。例:arr="31 5 + "
result 保存计算完毕后的结果
注:如何利用栈来计算后缀表达式的结果:依次取出后缀表达式中的符号进行比较,如果是数字,则直接入栈;如果是符号,则出栈两次,弹出两个要计算的因数,进行计算,之后再将计算结果入栈。知道后缀表达式中所有符号都已比较完毕。
*/
double Calculate(char *arr)
{
    // printf("%s\n",arr);  
    double d, e, f; //d,e 存放两个因数。f存放d,e计算后的结果.  
    stack<double> s;
    char *op; //存放后缀表达式中的每个因数或运算符  
    char *buf = arr; //声明bufhe saveptr两个变量,是strtok_r函数的需要。  
    char *saveptr = NULL;
    while ((op = strtok(buf, " ")) != NULL) //利用strtok_r函数分隔字符串  
    {
        buf = NULL;
        switch (op[0])
        {
        case '+':
            d = s.top(); s.pop();
            e = s.top(); s.pop();
            f = d + e;
            s.push(f);
            break;
        case '-':
            d = s.top(); s.pop();
            e = s.top(); s.pop();
            f = e - d;
            s.push(f);
            break;
        case '*':
            d = s.top(); s.pop();
            e = s.top(); s.pop();
            f = d*e;
            s.push(f);
            break;
        case '/':
            d = s.top(); s.pop();
            e = s.top(); s.pop();
            f = e / d;
            s.push(f);
            break;
        default:
            d = atof(op); //不是运算符,就肯定是因数了。所以,用atof函数,将字符串转换为double类型  
            s.push(d);
            break;
        }
    }
    double result = s.top(); s.pop();
    return result;
}


3.5 迷宫解题

3.5.1 问题描述

MAP里面0代表墙(通不过),1代表空格(可通过)。求起始点到目的点的一条路径。

3.5.2 解析
#include<stdio.h>  
#include<malloc.h>  
#include<stdlib.h>  

#define TRUE  1   
#define FALSE 0  
#define OK    1  
#define ERROR 0  
#define INFEASIBLE -1  
#define OVERFLOW   -2  

typedef struct
{
    int x;          //x坐标  
    int y;          //y坐标  
}Postype;           //坐标类型 

typedef struct
{
    //int ord;      //通道块在路径上的序号  
    //Postype seat; //通道块在迷宫中的坐标  
    //int di;       //从此通道块走向下一通道块的“方向”  
    int x;
    int y;          //元素坐标  

    //  bool track;     //是否已经走过  
}ElemType;          //栈的元素类型  

int MAP[9][9] =
{
    //0 1 2 3 4 5 6 7 8  

    0, 0, 0, 0, 0, 0, 0, 0, 0,  // 0
    0, 1, 0, 0, 1, 1, 1, 1, 0,  // 1
    0, 1, 0, 0, 1, 1, 1, 1, 0,  // 2
    0, 1, 1, 1, 1, 0, 1, 1, 0,  // 3
    0, 1, 0, 1, 0, 1, 1, 1, 0,  // 4 
    0, 1, 0, 1, 0, 1, 1, 1, 0,  // 5
    0, 1, 0, 1, 0, 1, 1, 1, 0,  // 6
    0, 0, 0, 1, 1, 1, 1, 1, 0,  // 7
    0, 0, 0, 0, 0, 0, 0, 0, 0,  // 8
};
/*-------------------------------------栈的元素类型定义完毕-------------------------*/
typedef int Status;     //函数返回值  

#define STACK_INIT_SIZE 100     // 栈的初始大小  
#define STACK_INCREMENT 10      // 每次增加的空间大小  


//下面给出栈的相关定义  
typedef struct
{
    ElemType *base;     //在构造栈之前和销毁之后,base的值为NULL  
    ElemType *top;      //栈顶指针  
    int stacksize;      //当前已分配的存储空间,以元素为单位  
}ZJC_Stack;

//--------------------------------------栈基本操作的算法部分--------------------------   
//栈的初始化  
Status InitStack(ZJC_Stack &S)
{

    S.base = (ElemType *)malloc(STACK_INIT_SIZE * sizeof(ElemType));    //分配内存空间  
    if (!S.base)
        exit(OVERFLOW);

    else                //否则分配成功  
    {
        S.top = S.base;
        S.stacksize = STACK_INIT_SIZE;
        return  OK;
    }

}

//获得栈顶元素  
ElemType GetTop(ZJC_Stack S)
{
    if (S.top == S.base)
        exit(ERROR);
    return *(S.top - 1);
}
//压栈  
Status Push(ZJC_Stack &S, ElemType e)
{
    if (S.top - S.base >= S.stacksize)
    {
        S.base = (ElemType*)realloc(S.base, (S.stacksize + STACK_INCREMENT) * sizeof(ElemType));
        if (!S.base)
            exit(OVERFLOW);
        S.stacksize += STACK_INCREMENT;
        S.top = S.base + S.stacksize;
    }
    *S.top++ = e;
    return OK;
}

void Print_Path(ZJC_Stack S)    //打印出栈中的元素  
{
    printf("\n寻路完成..路径坐标值如下:......\n");
    ElemType *p = S.base;       //首先p指向栈底指针  
    ElemType temp;
    while (p != S.top)          //只要没有到顶端,指针就移动,然后输出元素值  
    {
        temp = *p;
        printf("\n路径 x = %d , y = %d", temp.x, temp.y);
        p++;
    }

    printf("\n");
}

//出栈函数  
Status Pop(ZJC_Stack &S, ElemType &e)
{
    if (S.top == S.base)   //空栈,返回错误  
        return ERROR;
    else                        //不是空栈  
    {
        e = *--S.top;
        return OK;
    }
}
void PathAddToStack(int i, int j, ElemType temp, ZJC_Stack &robot)     //因为要修改值,所以取地址,开始没加取地址符号,栈顶元素一直是(1,1)  
{
    temp.x = i, temp.y = j;
    Push(robot, temp);
    MAP[i][j] = 2;                      //标记已经走过该格子了,当初想是否需要用其他标记,实际上不需要的,既然标记2,那么证明当然可以走过(不是墙)!  
}
void MAZH_SOLVE(int endx, int endy)      //解决迷宫问题函数,参数为终点的坐标  
{
    int i = 1, j = 1;                    //起点坐标    
    ZJC_Stack robot;                    //定义栈;  
    if (InitStack(robot))             //初始化栈  
        printf("\n栈的初始化完成....\n");
    ElemType CurrentPos;               //当前位置         
    ElemType start;                     //初始位置的相关信息  
    ElemType temp;                      //暂时用的  
    start.x = i;
    start.y = j;
    temp = start;
    //start.track = true;               //Robot站在初始位置,初始位置已经走过  
    MAP[i][j] = 2;                      //走过的标记为2             
    Push(robot, start);                  //初始位置入栈  
    printf("\n开始寻路....\n");
    do                                  //主要寻路算法:  
    {
        CurrentPos = GetTop(robot);
        i = CurrentPos.x;
        j = CurrentPos.y;
        printf(" \n寻路过程如下栈顶元素的 x = %d ,y = %d....\n", i, j);

        if (MAP[i][j + 1] == 1)          //表明向下一格走得通  
        {
            printf("\n向下能走动");    //向下前进一步,压栈,标记  
            j++;
            PathAddToStack(i, j, temp, robot);
        }
        else if (MAP[i + 1][j] == 1)
        {
            printf("\n向右能走动");
            i++;
            PathAddToStack(i, j, temp, robot);
        }
        else  if (MAP[i - 1][j] == 1)
        {
            printf("\n向左能走动");
            i--;
            PathAddToStack(i, j, temp, robot);
        }
        else if (MAP[i][j - 1] == 1)
        {
            printf("\n向上能走动");
            j--;
            PathAddToStack(i, j, temp, robot);
        }
        else  //都走不动  
        {
            printf("\n都走不动,退栈");
            Pop(robot, temp);
        }

    } while (GetTop(robot).x != endx || GetTop(robot).y != endy);        //只要栈顶元素的x,y不等于终点坐标的x,y,则一直循环找路径  
    printf("\n完成!\n");
    Print_Path(robot);                  //打印出坐标值                               
}

int main()    //入口函数  
{
    MAZH_SOLVE(7, 7);

    return 0;
}

结果:
这里写图片描述

这里写图片描述

这里写图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值