深入浅出讲解栈

深入浅出讲解栈

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

相关数据

细腻湿润的戚风蛋糕(新手必看)
365bet提款规则

细腻湿润的戚风蛋糕(新手必看)

📅 07-21 👁️ 6485
赚钱最快的方法有哪些?
365审核要多久

赚钱最快的方法有哪些?

📅 09-05 👁️ 195
滴滴一单可以赚多少钱
365足彩推荐

滴滴一单可以赚多少钱

📅 08-20 👁️ 8754