c语言实现栈【由浅入深-数据结构】
文章目录
- 前言
- C语言实现栈的详细解析
- 一、栈的基本概念
- 栈的核心特性
- 栈的两个经典操作
- 二、栈的实现方式
- 1. 顺序栈(基于数组实现)
- 结构体定义
- 代码实现
- 顺序栈的特点
- 完整数组实现
- 2. 链式栈(基于链表实现)
- 结构体定义
- 代码实现
- 链式栈的特点
- 三、两种实现方式的对比
- 四、栈的应用场景
- 五、实际应用示例
- 括号匹配检查
- 六、总结
前言
本文介绍c语言实现栈的相关内容。
(【由浅入深】是一个系列文章,它记录了我个人作为一个小白,在学习c++技术开发方向计相关知识过程中的笔记,欢迎各位彭于晏刘亦菲从中指出我的错误并且与我共同学习进步,作为该系列的第一部曲-c语言,大部分知识会根据本人所学和我的助手——通义,DeepSeek等以及合并网络上所找到的相关资料进行核实誊抄,每一篇文章都可能会因为一些错误在后续时间增删改查,因为该系列按照我的网络课程学习笔记形式编写,我会使用绝大多数人使用的讲解顺序编写,所以基础框架和大部分内容案例会与他人一样,基础知识不会过于详细讲述)
C语言实现栈的详细解析
栈(Stack)一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端,称为栈顶,另一端称为栈底。具有后进先出(LIFO, Last In First Out)的特性。它在函数调用、表达式求值、括号匹配等场景有广泛应用。下面我将详细讲解C语言中实现栈的两种主要方式:顺序栈(基于数组-最常用)和链式栈(基于链表)。
一、栈的基本概念
栈的核心特性
- 后进先出:最后进入的元素最先被取出
- 操作限制:只能在栈顶进行插入(push)和删除(pop)操作
- 基本操作时间复杂度:均为O(1)
栈的两个经典操作
- 压栈(Push):栈的插入操作,将元素放入栈顶
- 出栈(Pop):栈的删除操作,将栈顶元素移除
二、栈的实现方式
1. 顺序栈(基于数组实现)
结构体定义
typedef struct {int* data; // 存储元素的数组int size; // 栈的总容量int top; // 栈顶指针(初始为-1,表示空栈)
} Stack;
关键点:
top初始值为-1表示空栈size限制栈的最大容量- 使用动态数组实现顺序存储
代码实现
初始化栈
Stack* initStack(int n) {Stack* s = (Stack*)malloc(sizeof(Stack));s->data = (int*)malloc(sizeof(int) * n);s->size = n;s->top = -1; // 初始化栈顶指针return s;
}
入栈操作
int push(Stack* s, int val) {if (s->top == s->size - 1) {// 栈已满,返回错误return -1;}s->top++;s->data[s->top] = val;return 0; // 成功
}
出栈操作
int pop(Stack* s, int* val) {if (s->top == -1) {// 栈为空,返回错误return -1;}*val = s->data[s->top];s->top--;return 0; // 成功
}
判空操作
int empty(Stack* s) {return s->top == -1;
}
获取栈顶元素
int top(Stack* s) {if (empty(s)) {return -1; // 空栈返回错误值}return s->data[s->top];
}
顺序栈的特点
- 优点:实现简单,内存连续,访问速度快
- 缺点:容量固定,可能有空间浪费;满栈时需要扩容(增加复杂性)
完整数组实现
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>// 定义栈中存储的数据类型为整型
typedef int STDataType;// 定义栈结构体
typedef struct Stack
{STDataType* a; // 动态数组,用于存储栈元素int top; // 栈顶指针(指向栈顶元素的下一个位置)int capacity; // 栈的容量(当前分配的数组大小)
}ST;// 函数声明(栈操作接口)
void STInit(ST* ps); // 初始化栈
void STDestroy(ST* ps); // 销毁栈(释放内存)void STPush(ST* ps, STDataType x); // 入栈(压栈)
void STPop(ST* ps); // 出栈(弹栈)
STDataType STTop(ST* ps); // 获取栈顶元素
int STSize(ST* ps); // 获取栈中元素个数
bool STEmpty(ST* ps); // 判断栈是否为空// 初始化栈
void STInit(ST* ps)
{// 确保传入的指针有效assert(ps);// 初始化栈成员:// 1. 将动态数组指针置为NULL(表示尚未分配内存)// 2. 栈顶指针初始化为0(表示栈为空,栈顶元素在位置-1,但实际存储从0开始)// 3. 栈容量初始化为0ps->a = NULL;ps->top = 0;ps->capacity = 0;
}// 销毁栈(释放内存并重置状态)
void STDestroy(ST* ps)
{// 确保传入的指针有效assert(ps);// 释放动态数组内存free(ps->a);// 重置栈状态(避免野指针)ps->a = NULL;ps->top = 0;ps->capacity = 0;
}// 入栈操作(压栈)
void STPush(ST* ps, STDataType x)
{// 确保传入的指针有效assert(ps);// 检查栈是否已满(top等于容量表示已无可用空间)if (ps->top == ps->capacity){// 计算新容量:如果当前容量为0(空栈),则分配4个元素空间// 否则,容量翻倍(避免频繁扩容,提高效率)int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;// 重新分配内存,扩展栈容量STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));// 检查内存分配是否成功if (tmp == NULL){perror("realloc fail"); // 打印错误信息return; // 分配失败,退出函数}// 更新栈的数组指针和容量ps->a = tmp;ps->capacity = newcapacity;}// 将元素放入栈顶位置(top指向的位置)ps->a[ps->top] = x;// 栈顶指针后移(指向下一个空位置)ps->top++;
}// 出栈操作(弹栈)
void STPop(ST* ps)
{// 确保传入的指针有效assert(ps);// 检查栈是否为空(不能从空栈弹出元素)assert(!STEmpty(ps));// 栈顶指针前移(相当于移除栈顶元素)ps->top--;
}// 获取栈顶元素
STDataType STTop(ST* ps)
{// 确保传入的指针有效assert(ps);// 检查栈是否为空assert(!STEmpty(ps));// 栈顶元素位于top-1位置(因为top指向下一个空位置)return ps->a[ps->top - 1];
}// 获取栈中元素个数
int STSize(ST* ps)
{// 确保传入的指针有效assert(ps);// 栈中元素个数 = top(因为top表示已使用的元素数量)return ps->top;
}// 判断栈是否为空
bool STEmpty(ST* ps)
{// 确保传入的指针有效assert(ps);// 如果栈顶指针为0,则栈为空return ps->top == 0;
}int main()
{ST s;STInit(&s);STPush(&s, 1);STPush(&s, 2);STPush(&s, 3);int top = STTop(&s);printf("%d ", top);STPop(&s);STPush(&s, 4);STPush(&s, 5);while (!STEmpty(&s)){int top = STTop(&s);printf("%d ", top);STPop(&s);}STDestroy(&s);return 0;
}
主要掌握上面数组实现即可
2. 链式栈(基于链表实现)
结构体定义
typedef struct Node {int data;struct Node* next;
} Node;typedef struct {Node* top; // 栈顶指针int size; // 栈的大小
} Stack;
关键点:
- 栈顶即为链表的头节点
top指向栈顶元素size记录栈中元素个数
代码实现
初始化栈
Stack* initStack() {Stack* s = (Stack*)malloc(sizeof(Stack));s->top = NULL;s->size = 0;return s;
}
入栈操作
void push(Stack* s, int val) {Node* newNode = (Node*)malloc(sizeof(Node));newNode->data = val;newNode->next = s->top;s->top = newNode;s->size++;
}
出栈操作
int pop(Stack* s, int* val) {if (s->top == NULL) {return -1; // 栈为空}Node* temp = s->top;*val = temp->data;s->top = temp->next;free(temp);s->size--;return 0;
}
判空操作
int empty(Stack* s) {return s->top == NULL;
}
获取栈顶元素
int top(Stack* s) {if (empty(s)) {return -1; // 空栈返回错误值}return s->top->data;
}
链式栈的特点
- 优点:空间利用率高,无需扩容,动态增长
- 缺点:实现相对复杂,内存不连续,访问速度稍慢
三、两种实现方式的对比
| 特性 | 顺序栈 | 链式栈 |
|---|---|---|
| 实现基础 | 数组 | 链表 |
| 空间利用率 | 低(可能有浪费) | 高(动态分配) |
| 扩容 | 需要扩容(可能有性能开销) | 无需扩容 |
| 插入/删除效率 | O(1) | O(1) |
| 代码复杂度 | 较低 | 较高 |
| 适用场景 | 已知栈大小,需要频繁操作 | 不知道栈大小,需要动态增长 |
四、栈的应用场景
栈在C语言中有以下广泛应用:
-
表达式求值:
- 栈可以用于存储运算符和操作数
- 实现表达式的求值算法,如中缀表达式转后缀表达式并计算结果
-
函数调用:
- 函数调用时,需要保存函数的返回地址、参数和局部变量等信息
- 这些信息使用栈来保存和管理
-
括号匹配:
- 栈可以用于检查括号是否匹配
- 遇到左括号入栈,遇到右括号出栈,最终检查栈是否为空
-
逆波兰表达式求值:
- 逆波兰表达式是一种后缀表达式
- 栈可以实现逆波兰表达式的求值
-
递归算法:
- 递归算法中,每次递归调用时需要保存当前函数的状态
- 这些状态可以使用栈来保存和管理
-
深度优先搜索:
- 栈可以用于实现图的深度优先搜索算法
五、实际应用示例
括号匹配检查
#include <stdio.h>
#include <stdlib.h>typedef struct {char* data;int size;int top;
} Stack;Stack* initStack(int n) {Stack* s = (Stack*)malloc(sizeof(Stack));s->data = (char*)malloc(sizeof(char) * n);s->size = n;s->top = -1;return s;
}int push(Stack* s, char c) {if (s->top == s->size - 1) return -1;s->top++;s->data[s->top] = c;return 0;
}int pop(Stack* s, char* c) {if (s->top == -1) return -1;*c = s->data[s->top];s->top--;return 0;
}int empty(Stack* s) {return s->top == -1;
}int isMatching(char c1, char c2) {return (c1 == '(' && c2 == ')') ||(c1 == '[' && c2 == ']') ||(c1 == '{' && c2 == '}');
}int checkBrackets(char* str) {Stack* s = initStack(100);for (int i = 0; str[i] != '\0'; i++) {if (str[i] == '(' || str[i] == '[' || str[i] == '{') {push(s, str[i]);} else if (str[i] == ')' || str[i] == ']' || str[i] == '}') {char c;if (empty(s) || !isMatching(pop(s, &c), str[i])) {return 0; // 括号不匹配}}}return empty(s); // 检查栈是否为空
}int main() {char str1[] = "({[()])}";char str2[] = "({[]})";printf("str1: %s\n", checkBrackets(str1) ? "匹配" : "不匹配");printf("str2: %s\n", checkBrackets(str2) ? "匹配" : "不匹配");return 0;
}
六、总结
-
栈的实现:
- 顺序栈:基于数组,实现简单,但容量固定
- 链式栈:基于链表,空间利用率高,动态增长
-
选择建议:
- 如果已知栈的大小,且对性能要求高,选择顺序栈
- 如果栈的大小不确定,需要动态增长,选择链式栈
-
栈的核心价值:
- 通过后进先出的特性,简化了复杂问题的解决
- 为表达式求值、括号匹配、函数调用等提供了高效解决方案
-
重要原则:
- 所有栈操作必须检查栈是否为空(避免空栈出栈错误)
- 顺序栈需注意容量限制,满栈时需要处理扩容
- 链式栈需注意内存管理,出栈时需释放节点内存
栈作为基础数据结构,理解其原理和实现方式对学习更复杂的数据结构和算法至关重要。希望这篇详细解析能帮助你深入理解C语言中栈的实现和应用。
