深入浅出讲解栈

1、栈的定义

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

如果还不明白,我们可以将其想象成手枪的弹夹,弹夹即为一个顺序存储结构的栈,子弹即为我们操作的数据,第一颗放进弹夹的里子弹在射击时却是最后一颗被发射出去的子弹,该子弹射出弹夹的顺序,就和我们栈中存储的数据是一样的,先存进去的数据,要最后才能出来。

2、栈顶和栈底

允许插入和删除数据的一端称为栈顶(top),另一端则称为栈底(bottom)

3、栈的操作

栈的操作一般来说由如下几种:

/**************************************************************
 * ADT 栈 (stack)
 * operation
 *     InitStack(*S):初始化操作,建立一个空栈
 *     DestroyStack(*S):若栈存在,则销毁它
 *     ClearStack(*S):将栈清空
 *     StackEmpty(S):判断栈是否为空,若为空,返回ture,否返回false
 *     GetTop(S,*e):若栈存在且为非空,则用 e 返回栈顶元素
 *     Push(*S,e):若栈S存在,插入新元素e到栈S,并成为栈顶元素   
 *     Pop(*S,*e):若栈S存在,将栈顶元素删除,并用e返回删除的元素
 *     StackLength(S):返回栈S的元素个数
 * endADT
 ***************************************************************/

4、栈的类型

4.1、栈的顺序存储结构及实现

栈是线性表的特例,因此和线性表一样,顺序存储结构的栈也称为线性栈,也由一个数组来实现,数组下标为0的一端用来做栈底,定义一个变量top来指示栈顶元素在数组中的位置,规定栈为空时,top为-1。
在这里插入图片描述

4.2 、栈的结构定义

#define MAXSIZE 20          //栈的长度
typedef int SElemType;      //栈中存储的数据类型,根据需要定
typedef struct 
{
    /* data */
    SElemType data[MAXSIZE];
    int top;
}SqStack;

4.3、栈的初始化

初始化主要是初始化数组元素,以及栈顶变量top,代码如下:

/***************************************************************** 
* 作者:xx
* 时间:2022.10.28
* 函数名:InitStack
* 参数:S : SqStack类型的结构体的地址
* 功能:top初始化为-1
* 修改记录:无 
********************************************************************/
#define MAXSIZE 5          //栈的长度,根据需求随时更改
void InitStack(SqStack *S)
{
    S->top = -1;            //将栈顶标号初始化为-1,空栈 
    printf("初始化完成\n");
    printf("S->top = %d\n",S->top);
}

4.3、栈的销毁

顺序存储结构的栈的销毁感觉就是清除数组元素的意思吧,这里再看大话数据结构时没理解这个销毁的意思,所以这里贴的代码并没有实质性的功能,只是为了文章的完整性贴在这里。

/***************************************************************** 
* 作者:xx
* 时间:2022.10.28
* 函数名:DestroyStack
* 参数:S : SqStack类型的结构体的地址
* 功能:无
* 修改记录:无 
********************************************************************/
void DestroyStack(SqStack *S)
{
    
}

4.4、清空栈里的元素

将数组里的元素全部清零。

/***************************************************************** 
* 作者:xx
* 时间:2022.10.28
* 函数名:ClearStack
* 参数:S : SqStack类型的结构体的地址
* 功能:将栈的元素值清0,并将栈顶值恢复为-1
* 修改记录:无 
********************************************************************/
void ClearStack(SqStack *S)
{
    if(S->top == -1)
    {
        printf("栈已经为空,不用清除\n");
        return;
    }
    for(int i = S->top; i >= 0; i--)        //注意栈清空时,栈顶的值必须为-1
    {
        S->data[i] = 0;     //将栈元素清0
    }
    S->top = -1;           //栈顶值恢复为-1
    printf("栈已清空\n");
}

4.5、判断栈是否为空

前面以及提到,当栈为空栈的时候,top为-1,因此只需判断top的值是否为-1即可。

/***************************************************************** 
* 作者:xx
* 时间:2022.10.28
* 函数名:StackEmpty
* 参数:S : SqStack类型的结构体
* 功能:判断栈是否为空,为空返回ture,否则返回false
* 修改记录:无 
********************************************************************/
bool StackEmpty(SqStack S)
{
    if(S.top == -1)
    {
        printf("栈为空\n");
        return true;
    }
    else
    {
        printf("栈不为空\n");
        return false;
    }
}

4.6、获取栈顶元素

/***************************************************************** 
* 作者:xx
* 时间:2022.10.28
* 函数名:GetTop
* 参数:S : SqStack类型的结构体,*e栈顶元素的地址
* 功能:若栈存在且为非空,则用 e 返回栈顶元素
* 修改记录:无 
********************************************************************/
void GetTop(SqStack S,SElemType *e)
{
    if(S.top == -1)    //栈为空
    {
        printf("栈为空\n");
        return;
    }
    *e = S.data[S.top];
}

4.7、元素入栈

元素入栈只需记住一点,元素只能从栈顶一端入栈。如下图:

在这里插入图片描述

/***************************************************************** 
* 作者:xx
* 时间:2022.10.28
* 函数名:Push
* 参数:*S :SqStack类型的结构体的地址    e:需要插入的元素
* 功能:将元素e压入栈S中,并成为栈顶元素,压入成功返回OK,否则返回ERROR
* 修改记录:无 
********************************************************************/
bool Push(SqStack *S,SElemType e)
{
    if(S->top == MAXSIZE - 1)       //栈已满
    {
        printf("栈已满\n");
        return ERROR;
    }
    S->data[++S->top] = e; //将元素e插入到栈S内,并成为栈顶元素(上面已经判断过栈是否满了,这里不用再担心栈顶top溢出的问题)
    return OK;
}

4.8、元素出栈

同样,元素出栈也只能从栈顶一段出。

/***************************************************************** 
* 作者:xx
* 时间:2022.10.28
* 函数名:Pop
* 参数:*S :SqStack类型的结构体的地址    e:需要删除的元素的地址
* 功能:将栈顶元素删除,删除成功返回OK,否则返回ERROR
* 修改记录:无 
********************************************************************/
bool Pop(SqStack *S,SElemType *e)
{
    if(S->top == -1)
    {
        printf("栈为空\n");
        return ERROR;
    }
    *e = S->data[S->top--];         //将栈顶元素赋值给e后,栈顶向前移动
    return OK;
}

4.9、获取栈里的元素个数

栈的元素个数和数组里的元素个数一样,最后一个下标加1即为元素个数,即 top+1 。

/***************************************************************** 
* 作者:xx
* 时间:2022.10.28
* 函数名:Pop
* 参数:S :SqStack类型的结构体
* 功能:返回栈S的元素个数
* 修改记录:无 
********************************************************************/
int StackLength(SqStack S)
{
    return (S.top + 1);
}

至此,顺序存储结构的栈的知识就讲完啦,不知道你现在对栈是否有了更深的认识呢,接下来讲解链式存储结构的栈。

5、栈的链式存储结构及实现

栈的链式存储结构和链表的链式存储结构时一样的,只是栈的链式存储结构没有了头指针而是换成了栈顶指针top,栈的链式存储结构没有头结点,因为栈顶就在头部。链式存储结构的内存不在向顺序存储结构那样是连续的,而是在入栈时由系统随机分配的,各个结点由next指针链接起来,就像串珠子一样,如下图:
在这里插入图片描述

5.1、栈的结构定义代码

typedef int SElemType;              //链栈数据类型,根据需要更改
//在操作StackNode结构体时,一定要确定栈LinkStack不为空,否则在访问该结构体时由于top是野指针而使程序崩溃
typedef struct 
{
    /* data */
    SElemType data;                  //链栈里的数据
    struct StackNode *next;          //指向栈顶的后一结点的指针
}StackNode,*LinkStackPtr;            //注意这里的StackNode是结构体,而LinkStackPtr是指向结构体指针

typedef struct LinkStack
{
    LinkStackPtr top;   //栈顶,和next一样都是StackNode类型的指针(区别是top是指向当前节点的指针,而next是指向栈顶后一节点的指针)
    int count;                      //结点个数
}LinkStack;

5.2、栈的初始化

定义一个栈之后,一定要记得初始化它,否则很有可能由于栈顶指针top是一个野指针而错误更改其他内容而导致程序崩溃。

由于链式存储结构的栈是通过指针来实现访问里面的内容的,所以我们访问栈里数据时,必须先确定栈是否为空,否则在操作S.top.data和S.top.next的时候就会出错,这是由于栈为空的时候top并没有指向任何结点,所以这样操作会出错,严重的使程序崩溃。

/*******************************************************************************
 * 作者:xx
 * 时间:2022.10.28
 * 函数名:InitStack
 * 参数:*S :LinkStack结构体指针
 * 功能:初始化LinkStack栈顶top指针,并将链栈长度初始化为0
 * 备注:在操作链栈之前必须先将栈顶top指针初始化为NULL
 * 防止它们成为野指针,使程序崩溃,并且此时不能直接S.top.next和S.top.data来操作这两个数据
 * 因为此时的S.top还没有初始化,是一个野指针,他并不知道自己指向哪里,并且现在还没栈结点,
 * S.top.next和S.top.data也不存在。
********************************************************************************/

void InitStack(LinkStack *S)
{
    S->top = NULL;
    S->count = 0;

    #if DEBUG
        printf("初始化成功\n");
    #endif
}

5.3、栈的销毁

栈的销毁我也没有理解其意思,但是在清除栈时也会将其内存释放掉,所以感觉两者是一样的,因此这里贴的代码并没有实质性意义,只是为了文章的完整性。

/***************************************************************** 
* 作者:xx
* 时间:2022.10.28
* 函数名:DestroyStack
* 参数:S : SqStack类型的结构体的地址
* 功能:无
* 修改记录:无 
********************************************************************/
void DestroyStack(SqStack *S)
{
    
}

5.4、清空栈里的元素

在清空链式存储结构的链表时,必须在从栈顶开始释放结点的内存,在释放之前应该先定义一个LinkStackPtr 类型的临时结构体指针变量 p 用来保存即将要清除的栈结点,否则清除下一个节点时,由于内存以及被释放,next指针里的内容也就跟着被清除了,就无法访问后面的结点了。代码如下:

/************************************************
 * 作者:xx
 * 时间:2022.10.28
 * 函数名:ClearStack
 * 参数:*S :LinkStack结构体指针
 * 功能:将栈清空并释放内存
 * 备注:无
***************************************************/
void ClearStack(LinkStack *S)
{
    LinkStackPtr p;
    if(NULL == S->top)
    {
        #if DEBUG
            printf("栈已空\n");
        #endif

        return;
    }

    while(S->top)
    {
        p = S->top;            //使p指向栈顶
        S->top = S->top->next; //栈顶向后移
        free(p);               //释放栈顶
        S->count--;            //栈结点个数减一
    }
    #if DEBUG
        printf("栈销毁成功\n");
    #endif
}

5.5、判断栈是否为空

判断链式存储结构的栈是否为空,只需判断栈顶指针top是否等于NULL即可。这里需要注意的是,以栈顶指针top是否等于NULL前提是,我们在定义一个链式存储结构变量时,一定要紧接着初始化它,否则第一个结点的栈顶top并不一定是NULL,可能是任何值,这是系统随机分配的地址,因此我们栈顶指针再移动到最初的位置时,top并不等于空,这时候就会出现栈实际上以及空了,但是判断结果却不为空,后面再操作栈里的数据就会使程序崩溃。

/************************************************
 * 作者:xx
 * 时间:2022.10.28
 * 函数名:StackEmpty
 * 参数:S :LinkStack结构体
 * 功能:检测栈是否为空,为空返回false,不为空返回true
 * 备注:无
***************************************************/
bool StackEmpty (LinkStack S)
{
    if(NULL == S.top)
    {
        #if DEBUG
            printf("栈已空\n");
        #endif
        return false;
    }
    else
    {
        #if DEBUG
            printf("栈不为空\n");
        #endif

        return true;
    }
}

5.6、获取栈顶元素

获取栈顶元素比较简单,但首先我们需要判断一下栈是否为空,否则访问栈里的数据也会使程序崩溃。

/************************************************
 * 作者:xx
 * 时间:2022.10.28
 * 函数名:GetTop
 * 参数:S :LinkStack结构体,*e : 栈顶元素值
 * 功能:获取栈顶元素值
 * 备注:无
***************************************************/
void GetTop (LinkStack S, SElemType *e) 
{
    if(NULL == S.top)
    {
        #if DEBUG
            printf("栈已空\n");
        #endif
        return ERROR;
    }
    *e = S.top->data;
}

5.7、元素入栈

链式存储结构的栈一般不会出现栈满的情况,但是为了以防万一,我们还是做一个判断,这里判断malloc是否成功,一般情况下malloc不成功就是计算机系统内存满了。也就是栈满了。
在这里插入图片描述

/************************************************
 * 作者:xx
 * 时间:2022.10.28
 * 函数名:Push
 * 参数:*S :LinkStack结构体指针,e : 插入的元素
 * 功能:将元素e插入链栈,成功返回OK,否则返回ERROR
 * 备注:无
***************************************************/
bool Push(LinkStack *S, SElemType e)
{
    LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
    if(NULL == s)
    {
        #if DEBUG
            printf("入栈失败\n");
        #endif

        return ERROR;
    }

    s->data = e;
    s->next = S->top;       //将新元素的next指向之前的栈顶
    S->top = s;             //将新元素结点赋值给栈顶指针
    S->count++;
    #if DEBUG
        printf("入栈成功\n");
    #endif

    return OK;
}

5.8、元素出栈

元素出栈就是,先判断栈是否为空,然后从栈顶元素开始出栈,元素出栈之后也要将其内存释放掉。
在这里插入图片描述

/************************************************
 * 作者:xx
 * 时间:2022.10.28
 * 函数名:Pop
 * 参数:*S :LinkStack结构体指针,*e : 删除的元素的指针
 * 功能:将栈顶元素删除,成功返回OK,否则返回ERROR
 * 备注:无
***************************************************/
bool Pop(LinkStack *S, SElemType *e)
{
    LinkStackPtr p;
    if(NULL == S->top)
    {
        #if DEBUG
            printf("栈已空\n");
        #endif

        return ERROR;
    }

    *e = S->top->data;                  //将即将删除的元素赋值给e,并返回
    p = S->top;
    S->top = S->top->next;              //是栈顶指向后一结点
    free(p);                            //释放结点p
    S->count--;                         //链栈结点个数减1
    #if DEBUG
        printf("出栈成功\n");
    #endif

    return OK;
}

5.9、获取栈的元素个数

获取栈的元素个数直接返回count的值即可。

/************************************************
 * 作者:xx
 * 时间:2022.20.28
 * 函数名:StackLength
 * 参数:S :LinkStack结构体
 * 功能:获取栈结点个数
 * 备注:无
***************************************************/
int StackLength (LinkStack S)
{
    return S.count;
}

好啦,至此,栈的顺序存储结构和链式存储结构都讲完啦,众所周知指针是C语言里最难搞的一部分,因此链式存储结构由于涉及到指针所以是比较容易出错了,所以在设计程序时一定要注意,确保函数没有问题,在操作栈里的数据时,一定要确保栈不为空,还有就是一定要初始化、初始化、初始化。顺序存储结构其实就是操作数组,因此只要注意数组不越界就好了

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值