当前位置:   article > 正文

【数据结构】栈的概念、结构和实现详解

【数据结构】栈的概念、结构和实现详解

本文来介绍一下数据结构中的栈,以及如何用C语言去实现。

 1. 栈的概念及结构

栈:一种特殊的线性表,它只允许在固定的一端进行插入和删除元素的操作。

       进行数据插入和删除操作的一端称为栈顶,另一端称为栈底

       栈中元素遵循后进先出LIFO(Last In First Out)的原则

压栈:栈的插入操作叫做进栈/入栈/压栈,入数据在栈顶

出栈:栈的删除操作叫做出栈,出数据也在栈顶

2. 实现栈的底层方法选择

没有规定栈的哪端是栈顶,只说了数据插入和删除的一端是栈顶,所以我们栈的底层实现可以用链表或者数组 。

 虽然数组和单链表都可以实现栈,但是单链表能很好入数据不好删除数据,这里单链表要删除数据就是尾删,尾删需要找到前一个结点,不是很方便。

非要用链表的话有两个解决方法,1.可以用双向链表 2.我们把单链表的头节点当作栈顶,也就是把左边当栈顶,右边当栈底,对单链表进行头插和头删的操作。

 现在有3种方法实现栈,数组,单链表,双链表,我们应该如何选?

首先排除双链表,用双链表不如用单链表,双链表因为一个节点存两个指针,比单链表的一个节点多了4个字节或者8个字节。数组实现栈和单链表实现栈有什么区别?基本没区别,都可以,非要说选一个,我们还是更倾向于数组,因为数组的唯一缺点就是内存不足时需要扩容,扩容的影响也不是特别大,最重要的是数组的缓存效率更高。所以我们就用数组实现栈。

3. 栈的实现

提前说明,如果本篇看不太懂可以先看看【数据结构】顺序表-CSDN博客,我们栈的实现和顺序表的实现差不多。

还是一样,新建一个头文件和两个源文件

 点开Stack.h文件,在这个文件里面我们要定义栈的结构,以及给类型和栈的结构取别名。

  1. typedef int STDateType;
  2. typedef struct Stack
  3. {
  4. STDateType* a;//动态申请空间 调大小
  5. int top; //用栈顶记录元素个数
  6. int capacity; //数组实现要扩容,记录空间大小
  7. }ST;

栈一共要实现下面这7个接口,我们将一个一个来看.

  1. void STInit(ST* pst);//栈初始化
  2. void STDistroy(ST* pst);//栈的销毁
  3. void STPush(ST* pst, STDateType x);//压栈
  4. void STPop(ST* pst);//出栈
  5. STDateType STTopDate(ST* pst);//获取栈顶元素
  6. bool STEmpty(ST* pst);//判断栈是否为空
  7. int STSize(ST* pst);//获取栈元素个数

这里是会用到的头文件,且标注了是什么会用到,被包含的头文件全放在Stack.h

  1. #include <stdio.h>
  2. #include <stdlib.h> //空间申请
  3. #include <stdbool.h> //布尔类型
  4. #include <assert.h> //断言

Stack.c中只需要包含Stack.h

#include "Stack.h"

 准别工作做好后我们开始实现栈。

3.1 栈的初始化和销毁

Stack.h中进行函数的声明。这里的参数需要传指针。

  1. void STInit(ST* pst);//栈初始化
  2. void STDistroy(ST* pst);//栈的销毁

SeqList.c中进行函数的实现

  1. void STInit(ST* pst)//栈初始化
  2. {
  3. assert(pst); //判断pst是否为空
  4. pst->capacity = 0;
  5. pst->top = 0;
  6. pst->a = NULL;
  7. }
  1. void STDistroy(ST* pst)//栈的销毁
  2. {
  3. assert(pst);
  4. free(pst->a);
  5. pst->a = NULL;
  6. pst->capacity = pst->top = 0;
  7. }

 这里和顺序表差不多,很简单就不多说了。

3.2 压栈和出栈

Stack.h中进行函数的声明。

  1. void STPush(ST* pst, STDateType x);//压栈
  2. void STPop(ST* pst);//出栈

这里的参数需要传指针,压栈的函数参数还有要插入的数据。因为栈插入数据就是从栈顶插入,这里就没有什么头插尾插的概念,直接就是Push,删除数据也是,直接栈顶删除Pop。

SeqList.c中进行函数的实现

先说压栈

先分析空间足够的情况,初始化我们把top置为0,放进一个元素,top就是1,但是这个元素在数组中的下标为0,所以栈顶元素数组下标是top-1,而top指向的是栈顶元素的下一个位置,而不是栈顶元素。

所以我们放数据就是直接放下标为top的位置。

  1. void STPush(ST* pst, STDateType x)//压栈
  2. {
  3. assert(pst);
  4. pst->a[pst->top] = x; //先放数据
  5. pst->top++; //然后top++
  6. }

然后考虑扩容。capacity是数组大小,分析一下数组满了的情况

 

应该是top和capacity相等,此时就要扩容。

  1. void STPush(ST* pst, STDateType x)//压栈
  2. {
  3. assert(pst);
  4. if (pst->top == pst->capacity)
  5. {
  6. int newcapacity = pst->capacity * 2;//原来空间2倍的扩容
  7. //扩容
  8. STDateType* tmp = (STDateType*)realloc(pst->a, newcapacity * sizeof(STDateType));
  9. if (tmp == NULL) //扩容失败
  10. {
  11. perror("realloc fail");
  12. return;
  13. }
  14. //扩容成功
  15. pst->a = tmp;
  16. pst->capacity = newcapacity;
  17. }
  18. pst->a[pst->top] = x;
  19. pst->top++;
  20. }

但是我们要注意这句代码 int newcapacity = pst->capacity * 2;  我们一开始capacity初始化为0,0乘任何数都是0,所以这句换我们要改一下,用一个三目操作符就能解决。

int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;

 再说出栈

出栈和顺序表是一样的,直接top--就行了,不理解的可以看看顺序表中数据的尾删

  1. void STPop(ST* pst)//出栈
  2. {
  3. assert(pst);
  4. assert(pst->top > 0);
  5. pst->top--;
  6. }

test.c中测试一下栈的初始化,压栈,出栈,栈的销毁。

  1. #include "Stack.h"
  2. int main()
  3. {
  4. ST s;
  5. STInit(&s); //初始化
  6. STPush(&s, 1);//压栈
  7. STPush(&s, 2);
  8. STPush(&s, 3);
  9. STPush(&s, 4);
  10. for (int i = 0; i < s.top; i++)
  11. {
  12. printf("%d ", s.a[i]);
  13. }
  14. printf("\n");
  15. STPop(&s);//出栈
  16. for (int i = 0; i < s.top; i++)
  17. {
  18. printf("%d ", s.a[i] );
  19. }
  20. printf("\n");
  21. STPop(&s);//出栈
  22. for (int i = 0; i < s.top; i++)
  23. {
  24. printf("%d ", s.a[i]);
  25. }
  26. STDistroy(&s);//销毁
  27. return 0;
  28. }

 遵循后进先出

3.3 获取栈顶元素

Stack.h中进行函数的声明。

STDateType STTopDate(ST* pst);//获取栈顶元素

SeqList.c中进行函数的实现

  1. STDateType STTopDate(ST* pst)//获取栈顶元素
  2. {
  3. assert(pst);
  4. assert(pst->top > 0);
  5. return pst->a[pst->top - 1];
  6. }

test.c中测试一下

  1. #include "Stack.h"
  2. int main()
  3. {
  4. ST s;
  5. STInit(&s); //初始化
  6. STPush(&s, 1);//压栈
  7. STPush(&s, 2);
  8. STPush(&s, 3);
  9. STPush(&s, 4);
  10. for (int i = 0; i < s.top; i++)
  11. {
  12. printf("%d ", s.a[i]);
  13. }
  14. printf("\n");
  15. printf("栈顶元素:%d\n", STTopDate(&s));
  16. STPop(&s);//出栈
  17. STPop(&s);
  18. for (int i = 0; i < s.top; i++)
  19. {
  20. printf("%d ", s.a[i] );
  21. }
  22. printf("\n");
  23. printf("栈顶元素:%d\n", STTopDate(&s));
  24. STDistroy(&s);//销毁
  25. return 0;
  26. }

3.4 判断栈是否为空

Stack.h中进行函数的声明。

bool STEmpty(ST* pst);//判断栈是否为空

这里用了bool类型,需要包含头文件stdbool.h

SeqList.c中进行函数的实现

  1. bool STEmpty(ST* pst)//判断栈是否为空
  2. {
  3. assert(pst);
  4. if (pst->top == 0) //为空,返回真
  5. return true;
  6. else //不为空,返回假
  7. return false;
  8. }

还有一个更简单的写法,如下

  1. bool STEmpty(ST* pst)//判断栈是否为空
  2. {
  3. assert(pst);
  4. return pst->top == 0;
  5. }

 为空,返回真,不为空返回假。

test.c中测试一下,用栈的后进先出的特点访问

  1. #include "Stack.h"
  2. int main()
  3. {
  4. ST s;
  5. STInit(&s); //初始化
  6. STPush(&s, 1);//压栈
  7. STPush(&s, 2);
  8. STPush(&s, 3);
  9. STPush(&s, 4);
  10. //栈标准的后进先出访问方式
  11. while (!STEmpty(&s))
  12. {
  13. printf("%d ", STTopDate(&s));//先访问栈顶元素
  14. STPop(&s); //然后把栈顶元素删除
  15. }
  16. STDistroy(&s);//销毁
  17. return 0;
  18. }

3.5 获取栈元素个数

Stack.h中进行函数的声明。

int STSize(ST* pst);//获取栈元素个数

SeqList.c中进行函数的实现

因为我们前面的top初始化为0,所以top就是栈的元素个数,直接返回top就行了。

  1. int STSize(ST* pst)//获取栈元素个数
  2. {
  3. assert(pst);
  4. return pst->top;
  5. }

test.c中自己测试一下,这里就不测试了

到这里这个栈就实现好了,本篇也就结束啦,拜拜~

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号