当前位置: 首页 > news >正文

大话数据结构之 < 栈>(C语言)

引言:栈(Stack)是一种重要的线性数据结构,它遵循 **“后进先出”(Last In, First Out,简称 LIFO)** 的原则,即最后放入栈中的元素会最先被取出。这种特性使得栈在很多场景中都有广泛应用,比如程序调用的栈帧管理、表达式求值、括号匹配等。本章介绍栈,包括其实现。

1.栈的概念及结构

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

2.栈的实现

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。(即栈是一种基于数组或链表实现的数据结构,其拥有自身的特性)

 

 


数组实现(静态顺序栈)

#include <stdio.h>
#include <stdbool.h>#define MAX_SIZE 100  // 栈的最大容量// 数组实现的栈结构
typedef struct {int data[MAX_SIZE];  // 静态数组存储栈元素int top;             // 栈顶指针
} ArrayStack;// 初始化栈
void initArrayStack(ArrayStack *stack) {stack->top = -1;  // 初始化为-1表示空栈
}// 判断栈是否为空
bool isArrayStackEmpty(ArrayStack *stack) {return stack->top == -1;
}// 判断栈是否已满
bool isArrayStackFull(ArrayStack *stack) {return stack->top == MAX_SIZE - 1;
}// 入栈操作
bool pushArrayStack(ArrayStack *stack, int value) {if (isArrayStackFull(stack)) {printf("栈已满,无法入栈!\n");return false;}stack->data[++stack->top] = value;return true;
}// 出栈操作
bool popArrayStack(ArrayStack *stack, int *value) {if (isArrayStackEmpty(stack)) {printf("栈为空,无法出栈!\n");return false;}*value = stack->data[stack->top--];return true;
}// 获取栈顶元素
bool peekArrayStack(ArrayStack *stack, int *value) {if (isArrayStackEmpty(stack)) {printf("栈为空!\n");return false;}*value = stack->data[stack->top];return true;
}// 打印栈内容
void printArrayStack(ArrayStack *stack) {if (isArrayStackEmpty(stack)) {printf("栈为空!\n");return;}printf("栈内容(从栈底到栈顶): ");for (int i = 0; i <= stack->top; i++) {printf("%d ", stack->data[i]);}printf("\n");
}

链表实现(静态链表栈)

#include <stdio.h>
#include <stdbool.h>#define MAX_NODES 100  // 最大节点数// 链表节点结构
typedef struct StackNode {int data;int next;  // 使用数组下标代替指针
} StackNode;// 链表实现的栈结构
typedef struct {StackNode nodes[MAX_NODES];  // 节点池int top;                     // 栈顶节点下标int freeList;                // 空闲节点链表头int size;                    // 当前栈大小
} LinkedStack;// 初始化栈
void initLinkedStack(LinkedStack *stack) {stack->top = -1;  // 空栈stack->size = 0;// 初始化空闲链表for (int i = 0; i < MAX_NODES - 1; i++) {stack->nodes[i].next = i + 1;}stack->nodes[MAX_NODES - 1].next = -1;  // 结束标记stack->freeList = 0;  // 第一个空闲节点
}// 从空闲链表分配一个节点
int allocateNode(LinkedStack *stack) {if (stack->freeList == -1) {return -1;  // 没有空闲节点}int nodeIndex = stack->freeList;stack->freeList = stack->nodes[nodeIndex].next;return nodeIndex;
}// 释放节点到空闲链表
void freeNode(LinkedStack *stack, int nodeIndex) {stack->nodes[nodeIndex].next = stack->freeList;stack->freeList = nodeIndex;
}// 判断栈是否为空
bool isLinkedStackEmpty(LinkedStack *stack) {return stack->top == -1;
}// 判断栈是否已满
bool isLinkedStackFull(LinkedStack *stack) {return stack->freeList == -1;
}// 入栈操作
bool pushLinkedStack(LinkedStack *stack, int value) {if (isLinkedStackFull(stack)) {printf("栈已满,无法入栈!\n");return false;}int newNode = allocateNode(stack);stack->nodes[newNode].data = value;stack->nodes[newNode].next = stack->top;stack->top = newNode;stack->size++;return true;
}// 出栈操作
bool popLinkedStack(LinkedStack *stack, int *value) {if (isLinkedStackEmpty(stack)) {printf("栈为空,无法出栈!\n");return false;}int topNode = stack->top;*value = stack->nodes[topNode].data;stack->top = stack->nodes[topNode].next;freeNode(stack, topNode);stack->size--;return true;
}// 获取栈顶元素
bool peekLinkedStack(LinkedStack *stack, int *value) {if (isLinkedStackEmpty(stack)) {printf("栈为空!\n");return false;}*value = stack->nodes[stack->top].data;return true;
}// 打印栈内容
void printLinkedStack(LinkedStack *stack) {if (isLinkedStackEmpty(stack)) {printf("栈为空!\n");return;}printf("栈内容(从栈顶到栈底): ");int current = stack->top;while (current != -1) {printf("%d ", stack->nodes[current].data);current = stack->nodes[current].next;}printf("\n");
}

这两种静态实现方式都不使用动态内存分配(malloc/free),适合在资源受限的环境中使用,如嵌入式系统。 


推荐优先使用动态数组实现栈,除非:
1. 栈大小变化极其剧烈且不可预测
2. 元素非常大(移动成本高)
3. 有严格的无上限要求
动态数组实现提供了更好的综合性能,而现代内存分配器和扩容策略(如倍增法)已经大大减少了其缺点。在C语言中,通过合理的realloc策略可以实现高效的动态数组栈。

选择哪种实现取决于具体应用场景、性能要求和资源限制。在大多数现代应用中,动态实现更为常用,因为它提供了更大的灵活性。而在嵌入式系统或对性能有严格要求的场景中,静态实现可能更合适。下面我们来看一下动态实现栈:

3. 动态实现栈(数组)

 #include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
//基本操作: 
// 1.初始化
// 2.销毁
// 3.判空
// 4.压栈
// 5.出栈
// 6.返回栈顶元素
// 7.返回栈中元素个数 //用顺序表实现栈
//栈的性质:后进先出 typedef int STDataType;typedef struct stack{STDataType* a;//指向栈底位置        int top;//已有栈顶元素的个数           int capacity;// 栈容量           }ST;//栈的初始化-- 和顺序表的一系列操作差不多 
void STInit(ST* ps)
{assert(ps); ps->a = (STDataType*)malloc(sizeof(STDataType)*4);if(ps->a == NULL){perror("malloc fail");return;} ps->capacity = 4;//初始化容量为4 ,不够再扩容//ps->top = 0;//top是栈顶元素下一个 ps->top = -1;//top是栈顶元素 }//栈的销毁 
void STDestroy(ST* ps)
{assert(ps);free(ps->a);ps->a=NULL;ps->top=ps->capacity = 0;}//判空 
bool STEmpty(ST* ps)
{assert(ps);return ps->a[ps->top] == 0;
}void STPush(ST* ps, STDataType x)
{assert(ps);if(ps->top + 1 == ps->capacity){STDataType* tmp = (STDataType*)realloc(ps->a,sizeof(STDataType)*ps->capacity*2);if(tmp == NULL){perror("realloc fail");return;}ps->a = tmp;ps->capacity *= 2; }ps->a[ps->top+1] = x;ps->top++;
}//出栈 
void STPop(ST* ps)
{assert(ps);assert(!STEmpty(ps));ps->top--;}//栈中元素个数 
int STSize(ST* ps)
{assert(ps);return ps->top+1;
}//栈顶元素 
STDataType STTop(ST* ps)
{assert(ps);assert(!STEmpty(ps));	return ps->a[ps->top];
}int main()
{ST st;STInit(&st);//初始化//压栈-- 向栈中放入数据STPush(&st,1); STPush(&st,2); STPush(&st,3); STPush(&st,4); STPush(&st,5); //取栈顶元素//根据栈的性质  top == 5 // 打印出来看一下printf("%d\n",STTop(&st));//出栈--弹出栈顶元素STPop(&st);STPop(&st);//Pop两次 -- 那么此时栈顶的元素应该为 3//打印出来看一下printf("%d\n",STTop(&st));STPop(&st);//此时栈中应该只有两个元素了//打印栈中元素的个数printf("%d\n",STSize(&st));//最终结果为 5 //           3//			   2 //销毁栈STDestroy(&st);//以上为一个栈从创建到销毁的完整的过程//能够完成栈的基本操作 return 0;} 

http://www.dtcms.com/a/299858.html

相关文章:

  • Java中mybatis 无参构造器?你会吗
  • Spring AI 项目实战(二十):基于Spring Boot + AI + DeepSeek的智能环境监测与分析平台(附完整源码)
  • 修改site-packages位置与pip配置
  • Kubernetes 与 Docker的爱恨情仇
  • 面试实战,问题十三,Redis在Java项目中的作用及使用场景详解,怎么回答
  • 面试问题总结——关于OpenCV(二)
  • 【电赛学习笔记】MaxiCAM 的OCR图片文字识别
  • 力扣404.左叶子之和
  • jxORM--查询数据
  • ART配对软件使用
  • Macast配置
  • ThreadLocal--ThreadLocal介绍
  • 7.26 cpu
  • 单片机ADC机理层面详细分析(一)
  • SSE (Server-Sent Events) 服务出现连接卡在 pending 状态的原因
  • 嵌入式软硬件开发入门工具推荐
  • `read`系统调用示例
  • java每日精进 7.26【流程设计5.0(中间事件+结束事件)】
  • 检索召回率优化探究一:基于 LangChain 0.3集成 Milvus 2.5向量数据库构建的智能问答系统
  • 全球化2.0 | 云轴科技ZStack亮相阿里云印尼国有企业CXO专家活动
  • FreeMarker模板引擎
  • Windows Server系统安装JDK,一直卡在“应用程序正在为首次使用作准备,请稍候”
  • Vibe Coding | 技术让我们回归了创造的本质
  • hot100-每日温度
  • 字符串缓冲区和正则表达式
  • I/O 软件层次结构
  • 分布式数据库的分布透明性详解
  • 【前端】Vue 3 课程选择组件开发实战:从设计到实现
  • 如何从自定义或本地仓库安装 VsCode 扩展
  • 手写PPO_clip(FrozenLake环境)