数据结构入门(C语言版)栈和队列之栈的介绍及实现

简介: 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

56f57565990048c7a17f3b9b04178ddf.png


栈的概念


栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

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

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


b42e01a5a1494410be55b4817e109ab9.jpg


栈的实现过程


栈可以使用两种主要的数据结构实现:数组和链表。


使用数组实现的栈称为顺序栈(或者静态栈),其基本操作的时间复杂度为 O(1)。在实现时需要预先分配一定大小的数组作为存储空间,当栈的元素个数超过数组的大小时,需要进行扩容操作。


使用链表实现的栈称为链式栈(或者动态栈),其基本操作的时间复杂度同样为 O(1)。相比顺序栈,链式栈没有固定的大小限制,可以动态添加和删除节点,因此实现上更为灵活。


除了以上两种主要的数据结构,还有一些其他的数据结构可以用于实现栈,例如双向链表等。但是在实际应用中,数组和链表是最常见的两种栈实现方式。


栈的结构体与接口的定义


1、静态栈结构


定长的静态栈结构

代码如下:


typedef int STDataType;
#define N 10
typedef struct Stack
{
 STDataType _a[N];
 int _top; // 栈顶
}Stack;


2、动态栈结构


静态结构实际中一般不实用,所以我们主要实现下面的支持动态增长的栈

代码如下:


typedef int STDataType;
typedef struct Stack
{
  STDataType* a;
  int top;     //栈顶
  int capacity;//容量
}ST;


虽然这里使用的是动态扩容

但还是用顺序结构来实现栈

以便于初学者的理解


3、栈的接口定义


代码如下:


void StackInit(ST* ps);//初始化栈
void StackPush(ST* ps, STDataType x);//入栈
void StackPop(ST* ps);// 出栈
STDataType StackTop(ST* ps);// 获取栈顶元素
int StackSize(ST* ps);// 获取栈中有效元素个数
bool StackEmpty(ST* ps);// 检测栈是否为空,如果为空返回ture,如果不为空返回false
void StackDestroy(ST* ps);// 销毁栈


可以看到栈的接口函数比起之前的顺序表和链表都要简单很多

其实实现起来也是非常的简单,接下来我们实现这些接口


栈的接口实现


①初始化栈(StackInit)


代码如下:


void StackInit(ST* ps)
{
  assert(ps);
  ps->a = NULL;
  ps->top = 0; // ps->top = -1;
  ps->capacity = 0;
}


栈的初始化先将元素置空,栈顶指向0,当然你也可以指向-1,后面的接口函数也要做相应调整,可以根据自己习惯来设定,没有固定标准,然后容量初始化为0。


②入栈(StackPush)


代码如下:


void StackPush(ST* ps, STDataType x)
{
  assert(ps);
  if (ps->top == ps->capacity)
  {
    int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
    STDataType* tmp = realloc(ps->a, sizeof(STDataType) * newCapacity);
    if (tmp == NULL)
    {
      printf("realloc fail\n");
      exit(-1);
    }
    ps->a = tmp;
    ps->capacity = newCapacity;
  }
  ps->a[ps->top] = x;
  ps->top++;
}


入栈的写法有点类似顺序表的尾插,首先是一个经典的三目操作符判断容量,容量不够则进行增容,再向内存申请空间,这里的if是为了防止申请失败而找不到中断原因写的一个提示,类似断言,将元素插入,增容,再将栈顶指向新元素,栈顶++完成入栈操作。


③出栈(StackPop)


代码如下:


void StackPop(ST* ps)
{
  assert(ps);
  assert(!StackEmpty(ps));//判断是否为空
  ps->top--;
}


这里的出栈就很简单了,先进行断言,这里的StackEmpty函数在后面实现,直接将top–就完成出栈操作了。


④栈顶(StackTop)


代码如下:


STDataType StackTop(ST* ps)
{
  assert(ps);
  assert(!StackEmpty(ps));
  return ps->a[ps->top - 1];
}


栈顶值返回只要将a[最高位下标]返回即可,如果你初始化定义的top=-1,那么这里就是

ps->a[ps->top]的值了。


⑤栈元素个数(StackSize)


代码如下:


int StackSize(ST* ps)
{
  assert(ps);
  return ps->top;
}


栈元素个数直接返回top值即可,如果你初始化定义的top=-1,那么这里就是top++的值了


⑥检测栈是否为空(StackEmpty)


代码如下:


bool StackEmpty(ST* ps)
{
  assert(ps);
  if (ps->top == 0)
  {
    return true;
  }
  else
  {
    return false;
  }
}


首先返回类型可以是int,这里我使用bool类型也是一样的,只不过我这返回的是逻辑值true或是false,如果为空返回ture,如果不为空返回false。这段代码还有更简洁的方式,

代码如下:


bool StackEmpty(ST* ps)
{
  assert(ps);
  return ps->top == 0;
}


同样返回的是逻辑值,更简洁高效。


⑦销毁栈(StackDestroy)


代码如下:


void StackDestroy(ST* ps)
{
  assert(ps);
  free(ps->a);
  ps->a = NULL;
  ps->capacity = ps->top = 0;
}


实现销毁栈,先释放元素所占空间,再置空进行初始化即可。


结语


其实不管是栈还是队列,实现方式也都很简单,虽然简单,但也是同样重要的知识点,一定要好好理解和掌握,这一节就到这里了,下一节我们再讲讲队列的概念及接口实现。


制作不易,如有不正之处敬请指出,感谢大家的来访,UU们的观看是我坚持下去的动力,在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!


cebe8da25558471fa72edfd6773f8754.png

相关文章
|
22天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
109 9
|
21天前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
60 16
|
21天前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
73 8
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
35 3
|
14天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
29 6
|
1月前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
39 10
|
27天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
1月前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
61 7
|
1月前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
30 4
|
1月前
|
存储 编译器 C语言
C语言函数的定义与函数的声明的区别
C语言中,函数的定义包含函数的实现,即具体执行的代码块;而函数的声明仅描述函数的名称、返回类型和参数列表,用于告知编译器函数的存在,但不包含实现细节。声明通常放在头文件中,定义则在源文件中。
下一篇
无影云桌面