数据结构—栈和队列
栈和队列都是线性表,核心区别在于元素的 “进出顺序”,以下是具体解析:
一、栈(Stack)
核心结论:栈是 “先进后出(LIFO,Last In First Out)” 的数据结构,仅允许在一端(栈顶)进行插入和删除操作。
关键特性
- 操作限制:插入(push)和删除(pop)仅在栈顶执行,栈底固定。
- 访问特性:只能访问栈顶元素,无法直接访问中间元素。
常见实现与应用
- 实现方式:数组栈(顺序存储)、链表栈(链式存储)。
- 典型应用:函数调用栈、表达式求值、括号匹配、回溯算法(如迷宫求解)。
一、栈(数组实现,顺序栈)
1. 结构定义与头文件
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100 // 栈的最大容量// 栈结构定义
typedef struct {int data[MAX_SIZE]; // 存储栈元素int top; // 栈顶指针(-1表示空栈)
} Stack;
2. 核心操作实现
(1)初始化栈
void initStack(Stack* stack) {stack->top = -1; // 空栈标识
}
(2)判断栈空
int isStackEmpty(Stack* stack) {return stack->top == -1;
}
(3)判断栈满
int isStackFull(Stack* stack) {return stack->top == MAX_SIZE - 1;
}
(4)入栈(push)
int push(Stack* stack, int value) {if (isStackFull(stack)) {printf("栈满,入栈失败\n");return 0;}stack->data[++stack->top] = value; // 栈顶指针上移,存入元素return 1;
}
(5)出栈(pop)
int pop(Stack* stack, int* value) {if (isStackEmpty(stack)) {printf("栈空,出栈失败\n");return 0;}*value = stack->data[stack->top--]; // 取出栈顶元素,栈顶指针下移return 1;
}
(6)取栈顶元素
int getTop(Stack* stack, int* value) {if (isStackEmpty(stack)) {printf("栈空,无栈顶元素\n");return 0;}*value = stack->data[stack->top]; // 仅读取栈顶元素,不改变栈结构return 1;
}
3. 测试示例
int main() {Stack stack;initStack(&stack);int val;push(&stack, 10);push(&stack, 20);push(&stack, 30);getTop(&stack, &val);printf("栈顶元素:%d\n", val); // 输出:30pop(&stack, &val);printf("出栈元素:%d\n", val); // 输出:30getTop(&stack, &val);printf("栈顶元素:%d\n", val); // 输出:20return 0;
}
使用栈计算表达式的值
概述
通过两个栈(数值栈和符号栈)实现中缀表达式求值。算法核心是:
遇到数字时,累加并入数值栈;
遇到运算符时,比较其与符号栈顶运算符的优先级:
若当前运算符优先级更高,则直接入栈;
否则,不断弹出符号栈顶运算符与两个数值进行计算,结果重新压入数值栈,直到满足入栈条件。
表达式结束后,处理剩余符号栈中的运算符。
目标表达式示例:20*3+5,预期结果为 65。
栈结构定义(链式栈)
#ifndef _LINKSTACK_H_
#define _LINKSTACK_H_// 定义栈中存储的数据类型,可存放数字或字符(运算符)
typedef struct
{int num; // 用于存储操作数char sym; // 用于存储运算符
} DATATYPE;// 链栈节点结构
typedef struct _linkstacknode
{DATATYPE data; // 当前节点数据struct _linkstacknode *next; // 指向下一个节点
} LinkStackNode;// 链栈整体结构
typedef struct
{LinkStackNode* top; // 栈顶指针int clen; // 当前栈中元素个数
} LinkStack;// 函数声明
LinkStack* CreateLinkStack(); // 创建空栈
int PushLinkStack(LinkStack* ls, DATATYPE* data); // 元素入栈
int PopLinkStack(LinkStack* ls); // 栈顶元素出栈
DATATYPE* GetTopLinkStack(LinkStack* ls); // 获取栈顶元素(不弹出)
int IsEmptyLinkStack(LinkStack* ls); // 判断栈是否为空
int GetSizeLinkStack(LinkStack* ls); // 获取栈中元素个数
int DestroyLinkStack(LinkStack*); // 销毁整个栈(释放内存)#endif // !_LINKSTACK_H_
说明:该头文件定义了一个通用链式栈,支持存储整数和字符类型数据,适用于数值栈和符号栈。
链栈实现(LinkStack.c)
#include "LinkStack.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>/*** 创建一个新的链栈* @return 成功返回栈指针,失败返回 NULL*/
LinkStack* CreateLinkStack()
{LinkStack* ls = malloc(sizeof(LinkStack)); // 分配栈控制块内存if (NULL == ls){perror("CreateLinkStack malloc error\n");return NULL;}ls->top = NULL; // 初始化栈顶为空ls->clen = 0; // 初始元素个数为 0return ls;
}/*** 元素入栈(头插法)* @param ls 待操作的栈* @param data 要入栈的数据(指针)* @return 0 成功,非 0 失败*/
int PushLinkStack(LinkStack* ls, DATATYPE* data)
{LinkStackNode* newnode = malloc(sizeof(LinkStackNode));if (NULL == newnode){perror("PushLinkStack malloc error\n");return 1;}memcpy(&newnode->data, data, sizeof(DATATYPE)); // 复制数据newnode->next = ls->top; // 新节点指向原栈顶ls->top = newnode; // 更新栈顶ls->clen++; // 元素个数加一return 0;
}/*** 出栈操作(头删法)* @param ls 待操作的栈* @return 0 成功,非 0 失败(栈空)*/
int PopLinkStack(LinkStack* ls)
{if (IsEmptyLinkStack(ls)){printf("linkstack is empty\n");return 1;}LinkStackNode* tmp = ls->top; // 临时保存栈顶节点ls->top = ls->top->next; // 栈顶下移free(tmp); // 释放原栈顶节点ls->clen--; // 元素个数减一return 0;
}/*** 获取栈顶元素(不弹出)* @param ls 待操作的栈* @return 指向栈顶数据的指针,栈空时返回 NULL*/
DATATYPE* GetTopLinkStack(LinkStack* ls)
{if (IsEmptyLinkStack(ls)){return NULL;}return &ls->top->data; // 返回栈顶数据地址
}/*** 判断栈是否为空* @param ls 待检查的栈* @return 1 表示空,0 表示非空*/
int IsEmptyLinkStack(LinkStack* ls)
{return 0 == ls->clen;
}/*** 获取栈中元素个数* @param ls 待查询的栈* @return 元素个数*/
int GetSizeLinkStack(LinkStack* ls)
{return ls->clen;
}/*** 销毁整个链栈(释放所有节点及控制块)* @param ls 要销毁的栈* @return 0(固定返回值)*/
int DestroyLinkStack(LinkStack* ls)
{while (!IsEmptyLinkStack(ls)){PopLinkStack(ls); // 循环出栈,自动释放节点}free(ls); // 释放栈控制块return 0;
}
表达式求值主程序(main.c)
#include <stdio.h>
#include <string.h>
#include "LinkStack.h"int num = 0; // 用于临时存储正在解析的数字/*** 将字符数字累加到全局变量 num 中* @param c 当前字符('0'-'9')*/
void get_num(char c)
{num = num * 10 + c - '0'; // 构造多位整数
}/*** 获取运算符优先级* @param c 运算符字符* @return 优先级:+,- 为 1;*,/ 为 2;其他为 0*/
int get_priority(char c)
{switch (c){case '+':case '-':return 1;case '*':case '/':return 2;default:return 0;}
}/*** 执行两个数之间的基本运算* @param num1 第一个操作数* @param num2 第二个操作数* @param c 运算符* @return 计算结果*/
int get_result(int num1, int num2, char c)
{switch (c){case '+':return num1 + num2;case '-':return num1 - num2;case '*':return num1 * num2;case '/':return num1 / num2;}return 0; // 默认返回值(理论上不会执行)
}/*** 主函数:计算中缀表达式 "20*3+5"*/
int main(int argc, char** argv)
{char* express = "20*3+5"; // 输入表达式LinkStack* ls_num = CreateLinkStack(); // 数值栈LinkStack* ls_sym = CreateLinkStack(); // 符号栈char* tmp = express; // 遍历指针DATATYPE* top; // 临时指针DATATYPE data; // 临时数据变量while (*tmp){bzero(&data, sizeof(data)); // 清空临时数据// 处理数字字符if (*tmp >= '0' && *tmp <= '9'){get_num(*tmp);tmp++;continue;}// 遇到运算符前,将已解析的数字压入数值栈data.num = num;num = 0;PushLinkStack(ls_num, &data);// 处理当前运算符,与符号栈顶比较优先级while (1){top = GetTopLinkStack(ls_sym);// 条件1:符号栈为空,直接入栈// 条件2:当前运算符优先级高于栈顶,直接入栈if (IsEmptyLinkStack(ls_sym) ||(top != NULL && get_priority(top->sym) < get_priority(*tmp))){bzero(&data, sizeof(data));data.sym = *tmp;PushLinkStack(ls_sym, &data);break;}else{// 否则:弹出两个数值和一个运算符进行计算top = GetTopLinkStack(ls_num);int num2 = top->num;PopLinkStack(ls_num);top = GetTopLinkStack(ls_num);int num1 = top->num;PopLinkStack(ls_num);top = GetTopLinkStack(ls_sym);char op = top->sym;PopLinkStack(ls_sym);int result = get_result(num1, num2, op);bzero(&data, sizeof(data));data.num = result;PushLinkStack(ls_num, &data); // 结果压回数值栈}}tmp++;}// 处理最后一个数字(循环外)data.num = num;num = 0;PushLinkStack(ls_num, &data);// 处理剩余符号栈中的运算符(从左到右)while (!IsEmptyLinkStack(ls_sym)){top = GetTopLinkStack(ls_num);int num2 = top->num;PopLinkStack(ls_num);top = GetTopLinkStack(ls_num);int num1 = top->num;PopLinkStack(ls_num);top = GetTopLinkStack(ls_sym);char op = top->sym;PopLinkStack(ls_sym);int result = get_result(num1, num2, op);bzero(&data, sizeof(data));data.num = result;PushLinkStack(ls_num, &data);}// 最终结果在数值栈顶top = GetTopLinkStack(ls_num);int result = top->num;PopLinkStack(ls_num);printf("result %d\n", result); // 输出结果// 释放资源DestroyLinkStack(ls_num);DestroyLinkStack(ls_sym);return 0;
}
理想运行结果:
result 65
说明:表达式
20*3+5按照优先级先算乘法20*3=60,再加5,最终得65。
二、队列(Queue)
核心结论:队列是 “先进先出(FIFO,First In First Out)” 的数据结构,元素从一端(队尾)插入,从另一端(队头)删除。
作用:做缓冲区(速度不匹配),控制速度
2.特点:①顺序表,循环表
循环方法:用长度取余%
②队空的条件:头和尾的位置重合(tail==head)
③队满的条件:尾的位置加一等于头的位置(tail+1=head)
④允许增加的是队尾,允许删除的一端是队头
关键特性
- 操作限制:插入(enqueue)在队尾,删除(dequeue)在队头,两端各司其职。
- 访问特性:只能访问队头元素,无法直接访问中间元素。
- 主要类型:
- 顺序队列
- 循环队列(避免假溢出)
队列(循环队列,数组实现):循环队列,解决假溢出:利用取模运算(循环移动)
循环队列结构定义(seqque.h)
#ifndef __SEQQUE__H__
#define __SEQQUE__H__typedef int DATATYPE; // 数据类型别名/*** 循环队列结构体* array: 存储数据的数组* head: 队头索引(指向第一个元素)* tail: 队尾“下一个空位”索引* tlen: 数组总长度*/
typedef struct
{DATATYPE* array;int head;int tail;int tlen;
} SeqQue;// 函数声明
SeqQue* CreateSeqQue(int len); // 创建队列
int EnterSeqQue(SeqQue* sq, DATATYPE* data); // 入队
int QuitSeqQue(SeqQue* sq); // 出队
DATATYPE* GetHeadSeqQue(SeqQue* sq); // 获取队头元素
int IsEmptySeqQue(SeqQue* sq); // 判断空
int IsFullSeqQue(SeqQue* sq); // 判断满
int DestroySeqQue(SeqQue* sq); // 销毁队列#endif // !__SEQQUE__H__
循环队列实现(seqque.c)
#include "seqque.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>/*** 创建长度为 len 的循环队列* @param len 队列容量(实际可存 len-1 个元素)* @return 成功返回队列指针,失败返回 NULL*/
SeqQue* CreateSeqQue(int len)
{SeqQue* sq = malloc(sizeof(SeqQue));if (NULL == sq){perror("CreateSeqQue malloc");return NULL;}sq->array = malloc(sizeof(DATATYPE) * len);if (NULL == sq->array){perror("CreateSeqQue malloc2");free(sq);return NULL;}sq->head = 0;sq->tail = 0;sq->tlen = len;return sq;
}/*** 元素入队(队尾)* @param sq 队列* @param data 要插入的数据* @return 0 成功,1 失败(队满)*/
int EnterSeqQue(SeqQue* sq, DATATYPE* data)
{if (IsFullSeqQue(sq)){printf("queue is full\n");return 1;}memcpy(&sq->array[sq->tail], data, sizeof(DATATYPE));sq->tail = (sq->tail + 1) % sq->tlen; // 循环移动return 0;
}/*** 元素出队(队头)* @param sq 队列* @return 0 成功,1 失败(队空)*/
int QuitSeqQue(SeqQue* sq)
{if (IsEmptySeqQue(sq)){printf("queue is empty\n");return 1;}sq->head = (sq->head + 1) % sq->tlen; // 循环移动return 0;
}/*** 获取队头元素地址(不删除)* @param sq 队列* @return 指向队头元素的指针*/
DATATYPE* GetHeadSeqQue(SeqQue* sq)
{return &sq->array[sq->head];
}/*** 判断队列是否为空* @param sq 队列* @return 1 为空,0 非空*/
int IsEmptySeqQue(SeqQue* sq)
{return sq->head == sq->tail;
}/*** 判断队列是否为满* @param sq 队列* @return 1 为满,0 非满*/
int IsFullSeqQue(SeqQue* sq)
{return (sq->tail + 1) % sq->tlen == sq->head;
}/*** 销毁队列(释放内存)* @param sq 队列* @return 0*/
int DestroySeqQue(SeqQue* sq)
{free(sq->array);free(sq);return 0;
}
队列测试程序(main.c)
#include <stdio.h>
#include "seqque.h"int main(int argc, char** argv)
{SeqQue* sq = CreateSeqQue(10); // 创建容量为 10 的队列(最多存 9 个)int i = 0;for (i = 0; i < 10; i++){EnterSeqQue(sq, &i); // 0~9 依次入队}// 第10次入队会失败,打印 "queue is full"i = 0;while (!IsEmptySeqQue(sq)){DATATYPE* tmp = GetHeadSeqQue(sq);printf("%d %d\n", i++, *tmp); // 打印序号和值QuitSeqQue(sq); // 出队}// 实际输出 0~8,共 9 个数(因队列满,最后一个未入)DestroySeqQue(sq);return 0;
}
理想运行结果:
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
说明:循环队列容量为 10,但最多只能存 9 个元素(
tail+1 == head判满),因此i=9时入队失败。
常见实现与应用
- 顺序队列(数组):存在 “假溢出” 问题,需用循环队列优化。
- 循环队列(数组):通过取模运算实现指针循环,提高空间利用率。
- 链队(链表):动态扩容,无队满问题,适合元素数量不确定场景。
- 典型应用:进程调度、任务队列、消息队列、缓冲处理(如打印机队列)。
三、核心区别对比
| 维度 | 栈(Stack) | 队列(Queue) |
|---|---|---|
| 进出顺序 | 先进后出(LIFO) | 先进先出(FIFO) |
| 操作端 | 仅栈顶一端 | 队头(删除)、队尾(插入)两端 |
| 核心操作 | push(入栈)、pop(出栈) | enqueue(入队)、dequeue(出队) |
| 访问限制 | 仅访问栈顶元素 | 仅访问队头元素 |
四、特殊扩展
- 双端队列(Deque):允许在两端进行插入和删除,兼具栈和队列的特性。
- 优先级队列:元素带优先级,出队时按优先级排序(非严格 FIFO,底层常用堆实现)。
