【数据结构】C语言实现栈(详细解读)(上)

简介: 【数据结构】C语言实现栈(详细解读)(上)

什么是

       栈的概念及结构

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

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

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

栈的结构:

b943b5fcb0814e6faa8b5bc072cc2efb.png


实现栈的方式


实现栈的方式有两种: 顺序表链表

链表的优缺点:

优点:       1、任意位置插入删除O(1)

       2、按需申请释放空间

缺点:

       1、不支持下标随机访问

       2、CPU高速缓存命中率会更低

   先说链表实现栈的缺点:

  1. 额外内存开销:链表实现的栈需要为每个节点分配内存空间来存储数据和指针。相比于数组实现的栈,链表实现需要额外的内存开销来维护节点之间的指针关系,可能导致内存碎片化。
  2. 动态内存分配:链表实现的栈需要通过动态内存分配来创建和释放节点。这涉及到频繁的内存分配和释放操作,可能导致内存管理的复杂性和性能开销。在某些情况下,可能会出现内存分配失败或内存泄漏的问题。
  3. 指针操作开销:链表实现的栈需要通过指针进行节点之间的连接操作。这包括插入和删除节点时的指针修改,可能涉及到多个指针的更新。相比于数组实现的栈,链表实现的栈需要更多的指针操作,可能会带来一定的性能开销。
  4. 随机访问的限制:链表是一种顺序访问的数据结构,无法像数组一样通过索引进行随机访问。如果需要在栈中进行随机访问元素,链表实现的栈可能不太适合,而数组实现的栈更具优势。


顺序表的优缺点:

优点:1、尾插尾删效率不错。

     2、下标的随机访问。

       3、CPU高速缓存命中率会更高

缺点:

       1、前面部分插入删除数据,效率是O(N),需要挪动数据。

       2、空间不够,需要扩容。a、扩容是需要付出代价的b、一般还会伴随空间浪费。


顺序表实现栈的优点

  1. 内存连续性:顺序表在内存中是连续存储的,相比于链表的动态内存分配,顺序表的元素在物理上更加紧凑。这样可以减少内存碎片化,提高内存的利用效率。
  2. 随机访问:顺序表可以通过索引直接访问栈中的元素,具有随机访问的能力。这意味着可以快速访问栈中任意位置的元素,而不需要遍历整个链表。
  3. 操作简单高效:顺序表的插入和删除操作只涉及元素的移动,不需要额外的指针操作和动态内存分配。这使得操作相对简单高效,并且在某些情况下比链表实现更快。
  4. 空间效率:相比于链表实现,顺序表不需要额外的指针来维护节点之间的连接关系,因此可以节省一定的空间开销。只需要存储元素本身和栈顶指针即可。

综上所述,用顺序表实现栈更好。


栈的实现


a.头文件的包含

#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<stdio.h>


b.栈的定义

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


c.接口函数    

// 初始化栈
void STInit(ST* pst); 
// 入栈
void STPush(ST* pst, STDataType data); 
// 出栈
void STPop(ST* pst); 
// 获取栈顶元素
STDataType STTop(ST* pst); 
// 获取栈中有效元素个数
int STSize(ST* pst); 
// 检测栈是否为空,如果为空返回true,如果不为空返回false
bool STEmpty(ST* pst); 
// 销毁栈
void STDestroy(ST* pst);


接口函数的实现


1.栈的初始化

       pst->top表示栈的顶部指针,通常情况下,它指向栈顶元素的下一个位置,而不是指向当前栈顶元素。通过 pst->top 可以确定栈中元素的个数,打印的时候记得将 top - 1。

void STInit(ST* pst)
{
  assert(pst);//防止敲代码的人传过来是NULL指针
    pst->a = NULL;//栈底
    //top不是数组下标,不能理解成数组下标,因为栈只能拿到栈顶的元素,而数组可以随机访问拿到中间元素
    //pst->top=-1;//指向栈顶元素
    pst->top = 0;//指向栈顶元素的下一个位置
  pst->capacity = 0;
}


分别解释一下各自的含义:

  1. pst 是指向栈的指针,指向栈的首节点或头节点。
  2. -> 是一个成员访问运算符,用于通过指针访问结构体或类的成员
  • pst ->a 是指向存储栈元素的数组的指针。栈中的元素通常被存储在数组中,通过 pst->a 可以访问和操作该数组。在 STInit 函数中, pst->a 被设置为 NULL,表示栈底为空,即栈中没有任何元素。
  • pst->capacity 表示栈的容量,即栈可以容纳的最大元素数量。当栈中元素的数量达到 pst->capacity 时,栈被认为已满,无法再进行入栈操作。在初始化时,pst->capacity 的值通常被设置为 0,表示栈的初始容量为 0。
  • pst->top 表示栈顶指针,它指向当前栈顶元素的下一个位置。在栈为空时,pst->top 的值为 0,表示栈底。随着元素的入栈和出栈操作,pst->top 的值会相应地增加或减少,指向栈中下一个元素的位置。


2.销毁栈

为了防止野指针的出现,栈销毁后记得将指针置空。

void STDestroy(ST* pst)
{
  assert(pst);
  free(pst->a);
  pst->a = NULL;
}


3.入栈

三元运算符

condition ? value1: value2

它的含义是,如果条件condition为真(非0),则整个表达式的值为value1;如果条件为假(0),则整个表达式的值为value2


解析:

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

这段代码的执行顺序如下:

  1. 首先,评估条件pst->capacity == 0 这将检查  pst 指针所指向的结构体中的 capacity 成员是否等于 0
  2. 如果条件为真(pst->capacity 等于 0),则表达式的值为 4,将其赋给 newCapacity  

 3、如果条件为假(pst->capacity不等于 0),则表达式的值为pst->capacity * 2,将其赋给 newCapacity


realloc函数:【C进阶】-- 动态内存管理_Dream_Chaser~的博客-CSDN博客

d573a9d0c97c414e99bb5c2d717c5a1a.png

动图:

455056f5d5f5487ab37ca98ff18677f6.gif

函数代码:

void STPush(ST* pst,STDataType x)
{
  if (pst->top == pst->capacity)
  {
    int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
    STDataType* tmp = (STDataType*)realloc(pst->a, newCapacity * sizeof(STDataType));
    if (tmp == NULL)
    {
      perror("realloc fail");
      return;
    }
    pst->a = tmp;//返回的是realloc出来的内存块的地址
    pst->capacity = newCapacity;//把扩容后的空间大小赋值给栈容量
  }
  pst->a[pst->top] = x;//先放值
  pst->top++;//再++
}


【注意事项】

1️⃣检查栈的顶部指针 top 是否等于栈的容量 capacity 。如果这两个值相等,那么说明栈已经满了,无法再添加新的元素。

2️⃣接着判断此时栈的容量是否是0,若是0,则把4赋值给newcapacity作为新的栈容量。此后若栈满了,则把此时栈满时的容量 * 2进行扩容,赋值给newcapacity作为新的栈容量。

3️⃣先放入新的元素入栈,接着pst->top指向栈顶元素的指针++

相关文章
|
26天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
122 9
|
17天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
22 1
|
4天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
25 5
|
20天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
25天前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
63 16
|
25天前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
82 8
|
23天前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
25天前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
47 4
|
28天前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
41 0