在数据结构的知识体系中,栈和队列是两种基础且高频使用的线性结构。它们看似简单,却承载着诸多核心算法思想,在函数调用、表达式求值、进程调度等场景中发挥着关键作用。本文将从概念本质出发,结合C语言与Java的代码实现,搭配经典应用案例与面试真题,帮你彻底吃透栈与队列。
目录
一、核心概念:从特性看透本质
栈和队列的核心区别在于数据存取的顺序规则,这一规则直接决定了它们的应用场景差异。二者均为线性结构,元素之间存在明确的前后逻辑关系,但限制了数据的操作方位。
1. 栈:后进先出(LIFO)的"单向通道"
栈的操作特性可概括为"后进先出"(Last In First Out),如同我们日常生活中的叠盘子——最后叠放的盘子,会被最先取出。栈只允许在一端(称为栈顶)进行数据的插入(压栈,Push)和删除(出栈,Pop)操作,另一端(栈底)则固定不动。
常见术语:
-
栈顶:允许操作的一端,栈内元素变化时栈顶指针随之移动
-
栈底:固定的一端,栈初始化后栈底位置不变
-
空栈:栈内无元素时的状态
-
栈满:栈内元素达到存储上限时的状态(仅顺序栈有此概念)
2. 队列:先进先出(FIFO)的"排队通道"
队列的操作特性为"先进先出"(First In First Out),类似银行柜台前的排队人群——先排队的人先办理业务。队列限制数据在一端(队尾)进行插入(入队,Enqueue),在另一端(队头)进行删除(出队,Dequeue)。
常见术语:
-
队头:允许删除操作的一端
-
队尾:允许插入操作的一端
-
空队:队列中无元素的状态
-
循环队列:为解决顺序队列"假溢出"问题而设计的优化结构,将存储空间视为环形
二、底层实现:顺序存储与链式存储的取舍
栈和队列的实现均有两种主流方式:基于数组的顺序存储和基于链表的链式存储。二者各有优劣,需根据实际场景选择。
1. 栈的两种实现方式
栈的核心操作是Push和Pop,无论哪种实现,都需保证操作仅在栈顶进行。
(1)顺序栈(数组实现)
顺序栈利用数组存储数据,通过一个栈顶指针(index)标记栈顶位置。初始化时栈顶指针为-1(表示空栈),Push时指针递增并赋值,Pop时指针递减。当栈顶指针等于数组长度-1时,栈满,需进行扩容。
C语言实现代码:
#include <stdio.h>
#include <stdlib.h>
// 顺序栈结构体定义
typedef struct {
int *data; // 存储数据的数组
int top; // 栈顶指针(指向栈顶元素)
int capacity;// 栈的容量
} SeqStack;
// 初始化栈
SeqStack* initStack(int initCapacity) {
SeqStack *stack = (SeqStack*)malloc(sizeof(SeqStack));
stack->data = (int*)malloc(sizeof(int) * initCapacity);
stack->top = -1; // 空栈标记
stack->capacity = initCapacity;
return stack;
}
// 栈扩容
void expandStack(SeqStack *stack) {
int newCapacity = stack->capacity * 2;
int *newData = (int*)realloc(stack->data, sizeof(int) * newCapacity);
if (newData == NULL) {
printf("扩容失败\n");
exit(1);
}
stack->data = newData;
stack->capacity = newCapacity;
}
// 压栈操作
void push(SeqStack *stack, int value) {
// 栈满则扩容
if (stack->top == stack->capacity - 1) {
expandStack(stack);
printf("栈扩容至%d\n", stack->capacity);
}
stack->data[++stack->top] = value; // 栈顶指针先增再赋值
}
// 出栈操作
int pop(SeqStack *stack) {
if (stack->top == -1) {
printf("栈为空,无法出栈\n");
exit(1);
}
return stack->data[stack->top--]; // 先返回值再减指针
}
// 获取栈顶元素
int getTop(SeqStack *stack) {
if (stack->top == -1) {
printf("栈为空\n");
exit(1);
}
return stack->data[stack->top];
}
// 判断栈是否为空
int isEmpty(SeqStack *stack) {
return stack->top == -1;
}
// 销毁栈
void destroyStack(SeqStack *stack) {
free(stack->data);
free(stack);
}
// 测试代码
int main() {
SeqStack *stack = initStack(2);
push(stack, 10);
push(stack, 20);
push(stack, 30); // 触发扩容
printf("栈顶元素:%d\n", getTop(stack)); // 30
printf("出栈元素:%d\n", pop(stack)); // 30
printf("栈顶元素:%d\n", getTop(stack)); // 20
destroyStack(stack);
return 0;
}
顺序栈优点:存取速度快(随机访问),实现简单;缺点:初始化时需预估容量,扩容有性能开销。
(2)链式栈(链表实现)
链式栈利用单链表存储数据,通常以链表头部作为栈顶(操作更高效),新元素插入头部(Push),删除头部元素(Pop)。无需考虑容量问题,实现动态扩容。
C语言实现代码:
#include <stdio.h>
#include <stdlib.h>
// 链表节点结构体
typedef struct Node {
int data;
struct Node *next;
} Node;
// 链式栈结构体(仅需记录栈顶节点)
typedef struct {
Node *top;
int size; // 栈的大小(可选,方便统计)
} LinkStack;
// 初始化栈
LinkStack* initLinkStack() {
LinkStack *stack = (LinkStack*)malloc(sizeof(LinkStack));
stack->top = NULL;
stack->size = 0;
return stack;
}
// 压栈(头部插入)
void linkPush(LinkStack *stack, int value) {
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = value;
newNode->next = stack->top; // 新节点指向原栈顶
stack->top = newNode; // 栈顶更新为新节点
stack->size++;
}
// 出栈(头部删除)
int linkPop(LinkStack *stack) {
if (stack->top == NULL) {
printf("栈为空,无法出栈\n");
exit(1);
}
Node *temp = stack->top; // 暂存栈顶节点
int value = temp->data; // 获取数据
stack->top = temp->next; // 栈顶指向后一个节点
free(temp); // 释放节点内存
stack->size--;
return value;
}
// 销毁栈
void destroyLinkStack(LinkStack *stack) {
Node *cur = stack->top;
while (cur != NULL) {
Node *temp = cur;
cur = cur->next;
free(temp);
}
free(stack);
}
// 测试
int main() {
LinkStack *stack = initLinkStack();
linkPush(stack, 10);
linkPush(stack, 20);
printf("出栈元素:%d\n", linkPop(stack)); // 20
printf("栈大小:%d\n", stack->size); // 1
destroyLinkStack(stack);
return 0;
}
链式栈优点:动态扩容,无需预估容量;缺点:存取速度略慢于顺序栈(需遍历指针),实现稍复杂。
2. 队列的两种实现方式
队列的核心是保证入队和出队的顺序性,链式实现更灵活,顺序实现需解决"假溢出"问题。
(1)链式队列(链表实现)
链式队列用单链表存储,需记录队头(用于出队)和队尾(用于入队)节点。入队时在队尾插入新节点,出队时删除队头节点,操作效率均为O(1)。
C实现代码:
#include <stdio.h>
#include <stdlib.h>
// 链表节点结构体
typedef struct Node {
int data;
struct Node *next;
} Node;
// 链式队列结构体(记录队头和队尾)
typedef struct {
Node *front; // 队头指针(指向队头元素)
Node *rear; // 队尾指针(指向队尾元素)
int size; // 队列大小,方便统计
} LinkQueue;
// 初始化队列
LinkQueue* initLinkQueue() {
LinkQueue *queue = (LinkQueue*)malloc(sizeof(LinkQueue));
queue->front = NULL;
queue->rear = NULL;
queue->size = 0;
return queue;
}
// 入队操作(队尾插入)
void enqueue(LinkQueue *queue, int value) {
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = value;
newNode->next = NULL; // 新节点为队尾,next置空
if (queue->front == NULL) { // 空队列时,队头队尾都指向新节点
queue->front = newNode;
queue->rear = newNode;
} else {
queue->rear->next = newNode; // 原队尾节点指向新节点
queue->rear = newNode; // 队尾指针更新为新节点
}
queue->size++;
}
// 出队操作(队头删除)
int dequeue(LinkQueue *queue) {
if (queue->front == NULL) { // 队空判断
printf("队列为空,无法出队\n");
exit(1);
}
Node *temp = queue->front; // 暂存队头节点
int value = temp->data; // 获取队头数据
// 若队列中只有一个元素,出队后队尾也置空
if (queue->front == queue->rear) {
queue->rear = NULL;
}
queue->front = queue->front->next; // 队头指针后移
free(temp); // 释放原队头节点内存
queue->size--;
return value;
}
// 获取队头元素
int queuePeek(LinkQueue *queue) {
if (queue->front == NULL) {
printf("队列为空\n");
exit(1);
}
return queue->front->data;
}
// 判断队列是否为空
int queueIsEmpty(LinkQueue *queue) {
return queue->front == NULL;
}
// 获取队列大小
int queueSize(LinkQueue *queue) {
return queue->size;
}
// 销毁队列
void destroyLinkQueue(LinkQueue *queue) {
Node *cur = queue->front;
while (cur != NULL) {
Node *temp = cur;
cur = cur->next;
free(temp);
}
free(queue);
}
// 测试代码
int main() {
LinkQueue *queue = initLinkQueue();
enqueue(queue, 10);
enqueue(queue, 20);
printf("队头元素:%d\n", queuePeek(queue)); // 10
printf("出队元素:%d\n", dequeue(queue)); // 10
printf("队列大小:%d\n", queueSize(queue)); // 1
destroyLinkQueue(queue);
return 0;
}
(2)循环队列(顺序实现优化)
普通顺序队列中,队头出队后,前面的空间无法复用,会出现"假溢出"(数组未满但无法入队)。循环队列将数组视为环形,通过牺牲一个元素位置或使用计数器,判断队列空满状态。
核心逻辑:
-
队空:front == rear
-
队满:(rear + 1) % capacity == front(牺牲一个位置)
-
入队:rear = (rear + 1) % capacity
-
出队:front = (front + 1) % capacity
C实现代码:
#include <stdio.h>
#include <stdlib.h>
// 循环队列结构体
typedef struct {
int *data; // 存储数据的数组
int front; // 队头指针(指向队头元素)
int rear; // 队尾指针(指向队尾元素的下一个位置)
int capacity; // 队列的实际容量(物理数组大小)
} CircularQueue;
// 初始化循环队列
CircularQueue* initCircularQueue(int capacity) {
// 牺牲一个位置,所以物理数组大小为capacity+1
CircularQueue *queue = (CircularQueue*)malloc(sizeof(CircularQueue));
queue->capacity = capacity + 1;
queue->data = (int*)malloc(sizeof(int) * queue->capacity);
queue->front = 0;
queue->rear = 0;
return queue;
}
// 入队操作
int cqEnqueue(CircularQueue *queue, int value) {
// 判断队列是否已满
if ((queue->rear + 1) % queue->capacity == queue->front) {
printf("队列已满\n");
return 0; // 入队失败返回0
}
queue->data[queue->rear] = value;
queue->rear = (queue->rear + 1) % queue->capacity; // 环形移动
return 1; // 入队成功返回1
}
// 出队操作
int cqDequeue(CircularQueue *queue) {
// 判断队列是否为空
if (queue->front == queue->rear) {
printf("队列为空\n");
exit(1);
}
int value = queue->data[queue->front];
queue->front = (queue->front + 1) % queue->capacity; // 环形移动
return value;
}
// 获取队头元素
int cqPeek(CircularQueue *queue) {
if (queue->front == queue->rear) {
printf("队列为空\n");
exit(1);
}
return queue->data[queue->front];
}
// 判断队列是否为空
int cqIsEmpty(CircularQueue *queue) {
return queue->front == queue->rear;
}
// 判断队列是否已满
int cqIsFull(CircularQueue *queue) {
return (queue->rear + 1) % queue->capacity == queue->front;
}
// 销毁循环队列
void destroyCircularQueue(CircularQueue *queue) {
free(queue->data);
free(queue);
}
// 测试代码
int main() {
// 初始化逻辑容量为2的循环队列(物理容量为3)
CircularQueue *queue = initCircularQueue(2);
cqEnqueue(queue, 10);
cqEnqueue(queue, 20);
printf("入队结果:%d\n", cqEnqueue(queue, 30)); // 队列已满,返回0
printf("队头元素:%d\n", cqPeek(queue)); // 10
printf("出队元素:%d\n", cqDequeue(queue)); // 10
cqEnqueue(queue, 30); // 此时有空闲位置,可正常入队
printf("新队头元素:%d\n", cqPeek(queue)); // 20
destroyCircularQueue(queue);
return 0;
}
三、经典应用场景:理论落地实战
栈和队列的应用贯穿编程开发与算法设计,掌握其典型场景是理解数据结构价值的关键。
1. 栈的核心应用
(1)括号匹配问题
问题描述:判断字符串中的括号(()、[]、{})是否匹配,如"({})"匹配,"({)}"不匹配。
解题思路:遍历字符串,遇到左括号则压栈;遇到右括号则弹出栈顶元素,判断是否为对应的左括号。遍历结束后,若栈为空则匹配成功。
C核心代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 字符栈结构体(用于括号匹配)
typedef struct {
char *data;
int top;
int capacity;
} CharStack;
// 初始化字符栈
CharStack* initCharStack(int initCapacity) {
if (initCapacity <= 0) {
initCapacity = 8; // 默认初始容量为8
}
CharStack *stack = (CharStack*)malloc(sizeof(CharStack));
stack->data = (char*)malloc(sizeof(char) * initCapacity);
stack->top = -1;
stack->capacity = initCapacity;
return stack;
}
// 字符栈扩容
void expandCharStack(CharStack *stack) {
if (stack == NULL) return;
int newCapacity = stack->capacity * 2;
char *newData = (char*)realloc(stack->data, sizeof(char) * newCapacity);
if (newData != NULL) {
stack->data = newData;
stack->capacity = newCapacity;
} else {
printf("字符栈扩容失败\n");
exit(1);
}
}
// 字符栈压栈
void charPush(CharStack *stack, char value) {
if (stack == NULL) return;
if (stack->top == stack->capacity - 1) {
expandCharStack(stack);
}
stack->data[++stack->top] = value;
}
// 字符栈出栈(返回空字符表示栈空)
char charPop(CharStack *stack) {
if (stack == NULL || stack->top == -1) {
return '\0';
}
return stack->data[stack->top--];
}
// 判断字符栈是否为空
int charStackIsEmpty(CharStack *stack) {
return stack != NULL && stack->top == -1;
}
// 销毁字符栈
void destroyCharStack(CharStack *stack) {
if (stack != NULL) {
free(stack->data);
free(stack);
}
}
// 括号匹配核心函数(返回1表示匹配成功,0表示失败)
int isValidBracket(const char *s) {
if (s == NULL) return 0;
int len = strlen(s);
CharStack *stack = initCharStack(len);
for (int i = 0; i < len; i++) {
char c = s[i];
// 左括号压栈
if (c == '(' || c == '[' || c == '{') {
charPush(stack, c);
} else if (c == ')' || c == ']' || c == '}') {
// 右括号,弹出栈顶匹配
char topChar = charPop(stack);
// 判断括号是否匹配
if ((c == ')' && topChar != '(') ||
(c == ']' && topChar != '[') ||
(c == '}' && topChar != '{')) {
destroyCharStack(stack);
return 0;
}
}
// 忽略非括号字符
}
// 遍历结束后栈需为空才是完全匹配
int result = charStackIsEmpty(stack) ? 1 : 0;
destroyCharStack(stack);
return result;
}
// 测试代码
int main() {
const char *s1 = "({})";
const char *s2 = "({)}";
const char *s3 = "{[()]}123";
const char *s4 = "{{[[(())]]}}";
printf("字符串\"%s\"括号匹配结果:%s\n", s1, isValidBracket(s1) ? "成功" : "失败");
printf("字符串\"%s\"括号匹配结果:%s\n", s2, isValidBracket(s2) ? "成功" : "失败");
printf("字符串\"%s\"括号匹配结果:%s\n", s3, isValidBracket(s3) ? "成功" : "失败");
printf("字符串\"%s\"括号匹配结果:%s\n", s4, isValidBracket(s4) ? "成功" : "失败");
return 0;
}
(2)表达式求值(后缀表达式)
问题描述:计算后缀表达式(逆波兰表达式)的值,如"3 4 + 5 *"的结果为35。
解题思路:遍历表达式,遇到数字压栈;遇到运算符则弹出两个栈顶元素,计算结果后压栈,最终栈顶元素即为结果。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// 数值栈结构体
typedef struct {
int *data;
int top;
int capacity;
} NumStack;
// 初始化数值栈
NumStack* initNumStack(int initCapacity) {
if (initCapacity <= 0) initCapacity = 8;
NumStack *stack = (NumStack*)malloc(sizeof(NumStack));
stack->data = (int*)malloc(sizeof(int) * initCapacity);
stack->top = -1;
stack->capacity = initCapacity;
return stack;
}
// 数值栈扩容
void expandNumStack(NumStack *stack) {
if (!stack) return;
int newCap = stack->capacity * 2;
int *newData = (int*)realloc(stack->data, sizeof(int) * newCap);
if (newData) {
stack->data = newData;
stack->capacity = newCap;
} else {
printf("数值栈扩容失败\n");
exit(1);
}
}
// 数值栈压栈
void numPush(NumStack *stack, int val) {
if (!stack) return;
if (stack->top == stack->capacity - 1) expandNumStack(stack);
stack->data[++stack->top] = val;
}
// 数值栈出栈
int numPop(NumStack *stack, int *val) {
if (!stack || !val) return 0;
if (stack->top == -1) {
printf("数值栈为空\n");
return 0;
}
*val = stack->data[stack->top--];
return 1;
}
// 销毁数值栈
void destroyNumStack(NumStack *stack) {
if (stack) {
free(stack->data);
free(stack);
}
}
// 计算后缀表达式的值
int calculateRPN(const char *expr) {
if (!expr || strlen(expr) == 0) {
printf("表达式无效\n");
exit(1);
}
NumStack *stack = initNumStack(8);
int len = strlen(expr);
char numBuf[20]; // 存储多位数
int bufIdx = 0;
for (int i = 0; i < len; i++) {
char c = expr[i];
if (c == ' ') {
// 遇到空格,说明可能结束一个数字的读取
if (bufIdx > 0) {
numBuf[bufIdx] = '\0';
int num = atoi(numBuf);
numPush(stack, num);
bufIdx = 0; // 重置缓冲区索引
}
continue;
}
// 遇到运算符,进行计算
if (c == '+' || c == '-' || c == '*' || c == '/') {
int b, a;
// 先弹出右操作数,再弹出左操作数
if (!numPop(stack, &b) || !numPop(stack, &a)) {
destroyNumStack(stack);
printf("表达式格式错误\n");
exit(1);
}
int res;
switch (c) {
case '+': res = a + b; break;
case '-': res = a - b; break;
case '*': res = a * b; break;
case '/':
if (b == 0) {
destroyNumStack(stack);
printf("除数不能为0\n");
exit(1);
}
res = a / b;
break;
default:
destroyNumStack(stack);
printf("未知运算符:%c\n", c);
exit(1);
}
numPush(stack, res);
}
// 遇到数字或负号(负号需判断是否为数字开头)
else if (isdigit(c) || (c == '-' && bufIdx == 0 && i + 1 < len && isdigit(expr[i+1]))) {
numBuf[bufIdx++] = c;
} else {
destroyNumStack(stack);
printf("表达式包含无效字符:%c\n", c);
exit(1);
}
}
// 处理表达式末尾的数字
if (bufIdx > 0) {
numBuf[bufIdx] = '\0';
int num = atoi(numBuf);
numPush(stack, num);
}
// 最终栈中应只有一个元素,即为结果
int result;
if (stack->top != 0 || !numPop(stack, &result)) {
destroyNumStack(stack);
printf("表达式格式错误\n");
exit(1);
}
destroyNumStack(stack);
return result;
}
// 测试代码
int main() {
const char *expr1 = "3 4 + 5 *";
const char *expr2 = "10 2 3 + * 6 -";
const char *expr3 = "-5 3 * 2 -";
printf("表达式\"%s\"结果:%d\n", expr1, calculateRPN(expr1)); // 35
printf("表达式\"%s\"结果:%d\n", expr2, calculateRPN(expr2)); // 44
printf("表达式\"%s\"结果:%d\n", expr3, calculateRPN(expr3)); // -17
return 0;
}
(3)函数调用栈
编程语言的函数调用机制基于栈实现:主函数调用子函数时,主函数上下文(返回地址、局部变量)压栈;子函数执行完毕后,栈顶上下文弹出,恢复主函数执行。递归调用本质也是函数栈的不断压栈与弹栈。
2. 队列的核心应用
(1)进程/任务调度
操作系统的进程调度常采用队列结构,按"先进先出"原则分配CPU资源。例如,批处理系统中的作业队列,就绪状态的进程队列等。
(2)广度优先搜索(BFS)
BFS算法用于图或树的层次遍历,核心是用队列存储待访问节点:先入队根节点,然后依次出队节点,将其邻接节点入队,直至队空。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_VERTEX 100 // 最大顶点数
// 邻接表节点结构体
typedef struct AdjNode {
int vertex; // 邻接顶点索引
struct AdjNode *next; // 下一个邻接节点
} AdjNode;
// 顶点结构体
typedef struct Vertex {
char data; // 顶点数据
AdjNode *firstAdj; // 第一个邻接节点
} Vertex;
// 图结构体(邻接表存储)
typedef struct {
Vertex vertices[MAX_VERTEX]; // 顶点数组
int vertexNum; // 顶点数量
int edgeNum; // 边数量
} Graph;
// 队列结构体(用于BFS)
typedef struct {
int *data;
int front;
int rear;
int capacity;
} BFSQueue;
// 初始化BFS队列
BFSQueue* initBFSQueue(int capacity) {
BFSQueue *queue = (BFSQueue*)malloc(sizeof(BFSQueue));
queue->data = (int*)malloc(sizeof(int) * capacity);
queue->front = 0;
queue->rear = 0;
queue->capacity = capacity;
return queue;
}
// BFS队列入队
int bfsEnqueue(BFSQueue *queue, int val) {
if (!queue) return 0;
if ((queue->rear + 1) % queue->capacity == queue->front) {
printf("BFS队列已满\n");
return 0;
}
queue->data[queue->rear] = val;
queue->rear = (queue->rear + 1) % queue->capacity;
return 1;
}
// BFS队列出队
int bfsDequeue(BFSQueue *queue, int *val) {
if (!queue || !val) return 0;
if (queue->front == queue->rear) {
printf("BFS队列为空\n");
return 0;
}
*val = queue->data[queue->front];
queue->front = (queue->front + 1) % queue->capacity;
return 1;
}
// 判断BFS队列是否为空
int bfsQueueIsEmpty(BFSQueue *queue) {
return queue && queue->front == queue->rear;
}
// 销毁BFS队列
void destroyBFSQueue(BFSQueue *queue) {
if (queue) {
free(queue->data);
free(queue);
}
}
// 创建图
Graph* createGraph(int vertexNum) {
if (vertexNum <= 0 || vertexNum > MAX_VERTEX) {
printf("顶点数量无效\n");
return NULL;
}
Graph *graph = (Graph*)malloc(sizeof(Graph));
graph->vertexNum = vertexNum;
graph->edgeNum = 0;
// 初始化顶点
for (int i = 0; i < vertexNum; i++) {
printf("请输入第%d个顶点的数据:", i + 1);
scanf(" %c", &graph->vertices[i].data);
graph->vertices[i].firstAdj = NULL;
}
return graph;
}
// 添加无向边
void addUndirectedEdge(Graph *graph, int v1, int v2) {
if (!graph || v1 < 0 || v1 >= graph->vertexNum || v2 < 0 || v2 >= graph->vertexNum) {
printf("边添加失败,顶点索引无效\n");
return;
}
// 创建v2到v1的邻接节点
AdjNode *node1 = (AdjNode*)malloc(sizeof(AdjNode));
node1->vertex = v2;
node1->next = graph->vertices[v1].firstAdj;
graph->vertices[v1].firstAdj = node1;
// 创建v1到v2的邻接节点(无向边双向添加)
AdjNode *node2 = (AdjNode*)malloc(sizeof(AdjNode));
node2->vertex = v1;
node2->next = graph->vertices[v2].firstAdj;
graph->vertices[v2].firstAdj = node2;
graph->edgeNum++;
}
// BFS遍历图
void BFS(Graph *graph, int startIdx) {
if (!graph || startIdx < 0 || startIdx >= graph->vertexNum) {
printf("BFS遍历失败,参数无效\n");
return;
}
// 访问标记数组,0表示未访问,1表示已访问
int *visited = (int*)calloc(graph->vertexNum, sizeof(int));
BFSQueue *queue = initBFSQueue(graph->vertexNum);
// 起始顶点入队并标记为已访问
visited[startIdx] = 1;
bfsEnqueue(queue, startIdx);
printf("BFS遍历结果:");
while (!bfsQueueIsEmpty(queue)) {
int currIdx;
bfsDequeue(queue, &currIdx);
// 访问当前顶点
printf('%c ', graph->vertices[currIdx].data);
// 遍历当前顶点的所有邻接顶点
AdjNode *currAdj = graph->vertices[currIdx].firstAdj;
while (currAdj != NULL) {
int adjIdx = currAdj->vertex;
// 未访问的邻接顶点入队并标记
if (!visited[adjIdx]) {
visited[adjIdx] = 1;
bfsEnqueue(queue, adjIdx);
}
currAdj = currAdj->next;
}
}
printf("\n");
free(visited);
destroyBFSQueue(queue);
}
// 销毁图
void destroyGraph(Graph *graph) {
if (!graph) return;
// 释放邻接节点
for (int i = 0; i < graph->vertexNum; i++) {
AdjNode *curr = graph->vertices[i].firstAdj;
while (curr != NULL) {
AdjNode *temp = curr;
curr = curr->next;
free(temp);
}
}
free(graph);
}
// 测试代码
int main() {
// 创建含有5个顶点的图
Graph *graph = createGraph(5);
// 添加边:0-1, 0-2, 1-3, 1-4, 2-4
addUndirectedEdge(graph, 0, 1);
addUndirectedEdge(graph, 0, 2);
addUndirectedEdge(graph, 1, 3);
addUndirectedEdge(graph, 1, 4);
addUndirectedEdge(graph, 2, 4);
// 从顶点0开始BFS遍历
BFS(graph, 0); // 遍历结果:假设顶点数据为A,B,C,D,E,则结果为A B C D E
destroyGraph(graph);
return 0;
}
(3)缓冲区设计
键盘输入缓冲区、打印机输出缓冲区等,均采用队列结构。数据按产生顺序入队,按接收顺序出队,保证数据传输的有序性。
四、面试高频真题:栈与队列的转换
栈和队列的相互转换是面试中的经典问题,核心是利用两个栈/队列的组合,模拟目标结构的特性。
1. 用栈实现队列
思路:使用两个栈(入队栈inStack、出队栈outStack)。入队时直接压入inStack;出队时,若outStack为空,则将inStack所有元素弹出并压入outStack,再从outStack弹出元素(此时顺序已反转,符合队列特性)。
C实现代码:
#include <stdio.h>
#include <stdlib.h>
// 栈结构体定义(用于实现队列)
typedef struct {
int *data;
int top;
int capacity;
} Stack;
// 初始化栈
Stack* stackInit(int initCapacity) {
if (initCapacity <= 0) initCapacity = 4;
Stack *stack = (Stack*)malloc(sizeof(Stack));
stack->data = (int*)malloc(sizeof(int) * initCapacity);
stack->top = -1;
stack->capacity = initCapacity;
return stack;
}
// 栈扩容
void stackExpand(Stack *stack) {
if (!stack) return;
int newCap = stack->capacity * 2;
int *newData = (int*)realloc(stack->data, sizeof(int) * newCap);
if (newData) {
stack->data = newData;
stack->capacity = newCap;
} else {
printf("栈扩容失败\n");
exit(1);
}
}
// 栈压栈
void stackPush(Stack *stack, int val) {
if (!stack) return;
if (stack->top == stack->capacity - 1) stackExpand(stack);
stack->data[++stack->top] = val;
}
// 栈出栈
int stackPop(Stack *stack, int *val) {
if (!stack || !val) return 0;
if (stack->top == -1) return 0;
*val = stack->data[stack->top--];
return 1;
}
// 判断栈是否为空
int stackIsEmpty(Stack *stack) {
return stack && stack->top == -1;
}
// 销毁栈
void stackDestroy(Stack *stack) {
if (stack) {
free(stack->data);
free(stack);
}
}
// 用两个栈实现的队列结构体
typedef struct {
Stack *inStack; // 入队栈
Stack *outStack; // 出队栈
} StackQueue;
// 初始化队列
StackQueue* sqInit() {
StackQueue *queue = (StackQueue*)malloc(sizeof(StackQueue));
queue->inStack = stackInit(4);
queue->outStack = stackInit(4);
return queue;
}
// 数据转移:将入队栈元素转移到出队栈
void sqTransfer(StackQueue *queue) {
if (!queue || !stackIsEmpty(queue->outStack)) return;
int val;
while (stackPop(queue->inStack, &val)) {
stackPush(queue->outStack, val);
}
}
// 队列入队
void sqEnqueue(StackQueue *queue, int val) {
if (!queue) return;
stackPush(queue->inStack, val);
}
// 队列出队
int sqDequeue(StackQueue *queue, int *val) {
if (!queue || !val) return 0;
sqTransfer(queue);
return stackPop(queue->outStack, val);
}
// 获取队头元素
int sqGetFront(StackQueue *queue, int *val) {
if (!queue || !val) return 0;
sqTransfer(queue);
if (stackIsEmpty(queue->outStack)) return 0;
*val = queue->outStack->data[queue->outStack->top];
return 1;
}
// 判断队列是否为空
int sqIsEmpty(StackQueue *queue) {
return queue && stackIsEmpty(queue->inStack) && stackIsEmpty(queue->outStack);
}
// 销毁队列
void sqDestroy(StackQueue *queue) {
if (queue) {
stackDestroy(queue->inStack);
stackDestroy(queue->outStack);
free(queue);
}
}
// 测试代码
int main() {
StackQueue *queue = sqInit();
sqEnqueue(queue, 10);
sqEnqueue(queue, 20);
sqEnqueue(queue, 30);
int frontVal;
if (sqGetFront(queue, &frontVal)) {
printf("队头元素:%d\n", frontVal); // 10
}
int deqVal;
if (sqDequeue(queue, &deqVal)) {
printf("出队元素:%d\n", deqVal); // 10
}
if (sqGetFront(queue, &frontVal)) {
printf("队头元素:%d\n", frontVal); // 20
}
sqEnqueue(queue, 40);
while (!sqIsEmpty(queue)) {
sqDequeue(queue, &deqVal);
printf("出队元素:%d\n", deqVal); // 20,30,40
}
sqDestroy(queue);
return 0;
}
2. 用队列实现栈
思路:使用两个队列(主队列queue1、辅助队列queue2)。入栈时压入queue1;出栈时,将queue1中除最后一个元素外的所有元素转移到queue2,弹出queue1的最后一个元素,再将queue2元素转回queue1(或直接交换队列引用)。
C实现代码:
五、总结与拓展
栈和队列作为线性数据结构的"限制版",其价值在于通过约束操作规则,简化特定场景的问题解决。核心要点总结:
-
特性区分:栈是LIFO,队列是FIFO,操作方位均受限制
-
实现选择:顺序结构适合存取频繁场景,链式结构适合动态扩容场景
-
核心应用:栈对应括号匹配、函数调用,队列对应调度、BFS
-
面试重点:栈与队列的相互转换,需掌握两个容器的协同逻辑


1385

被折叠的 条评论
为什么被折叠?



