hello大家好 欢迎来到小四季豆的博客 
通过对链表的学习,我们清楚了链表是一种灵活的线性存储结构。
此篇我们将要学习两个受限线性数据结构---栈与队列。
一、基础概念
1.1 栈
栈是一种遵循"后进先出"(LIFO, Last In First Out)原则的线性数据结构。最后进入栈的数据先出栈。
- 核心结构:
- 栈顶(Top):栈操作的唯一入口
- 栈底(Bottom):栈的另一端
- 栈元素:存放在栈里每一个独立的数据单元
- 基础操作:
初始化、判空、判满、入栈、出栈、取栈顶、销毁
注意:所有插入、删除、读取操作仅在栈顶完成

1.2 队列
队列是一种遵循"先进先出"(FIFO, First In First Out)原则的线性数据结构。最先进入队列的数据先出队。
- 核心结构:
- 队头(Front):队列取出数据的一端
- 队尾(Rear):队列存入数据的一端
- 队列元素:存放在队列里每一个独立的数据单元
- 基础操作:初始化、判空、判满、入队、出队、取队头、销毁
注意:所有插入操作仅在队尾完成,所有删除、读取操作仅在队头完成

二、底层逻辑与实现
2.1 栈
栈的底层实现分为顺序栈(基于数组实现) 和**链式栈(基于链表实现)**两种逻辑结构
通常情况下优先用顺序栈
数组下标可以直接定位栈顶入栈出栈极其方便,顺序栈实现简单,代码量少,数组仅存数据空间开销小。
链表出栈入栈需要遍历寻址,程序运行还要管理结点,指针,内存的释放,代码复杂。
2.1.1 顺序栈
- 结构特点 :
- 底层使用一个数组。
- 维护一个指针或索引
top,始终指向栈顶元素(或者指向栈顶元素的下一个空位,具体取决于实现习惯)。 - 入栈(Push) :将元素放入
top指向的位置,然后top加 1。时间复杂度:O(1) - 出栈(Pop) :
top减 1,然后取出该位置的元素。时间复杂度:O(1)
- 头文件
cpp
#ifndef STACKSEQ_H
#define STACKSEQ_H
#include <stdio.h>
#include <stdlib.h>
typedef int STDataType;
typedef struct SeqStack
{
STDataType* arr; // 动态数组存储元素
int top; // 栈顶下标,top=-1代表栈空
int capacity; // 数组当前总容量
} SeqStack;
void SeqStackInit(SeqStack* st);
void SeqStackPush(SeqStack* st, STDataType x);
void SeqStackPop(SeqStack* st);
STDataType SeqStackTop(SeqStack* st);
int SeqStackEmpty(SeqStack* st);
void SeqStackDestroy(SeqStack* st);
void SeqStackPrint(SeqStack* st);
#endif
- 接口实现文件
cpp
#include "StackSeq.h"
void SeqStackInit(SeqStack* st)
{
// 初始容量直接写4,删除宏定义
st->arr = (STDataType*)malloc(sizeof(STDataType) * 4);
if (st->arr == NULL)
{
perror("malloc fail");
exit(EXIT_FAILURE);
}
st->top = -1; // 初始化栈顶,标识空栈
st->capacity = 4;
}
static void SeqStackExpand(SeqStack* st)
{
STDataType* tmp = (STDataType*)realloc(st->arr, sizeof(STDataType) * st->capacity * 2);
if (tmp == NULL)
{
perror("realloc fail");
exit(EXIT_FAILURE);
}
st->arr = tmp;
st->capacity *= 2;
}
void SeqStackPush(SeqStack* st, STDataType x)
{
// 判断是否栈满
if (st->top + 1 == st->capacity)
{
SeqStackExpand(st);
}
st->top++; // 栈顶上移
st->arr[st->top] = x; // 存入数据
}
void SeqStackPop(SeqStack* st)
{
// 空栈禁止出栈
if (SeqStackEmpty(st))
{
printf("栈为空,无法出栈\n");
return;
}
st->top--; // 栈顶下移完成出栈
}
STDataType SeqStackTop(SeqStack* st)
{
if (SeqStackEmpty(st))
{
printf("栈为空,无栈顶元素\n");
exit(EXIT_FAILURE);
}
return st->arr[st->top]; // 返回栈顶数据
}
int SeqStackEmpty(SeqStack* st)
{
return st->top == -1; // top=-1判定栈空
}
void SeqStackDestroy(SeqStack* st)
{
free(st->arr); // 释放堆数组
st->arr = NULL;
st->top = 0;
st->capacity = 0;
}
void SeqStackPrint(SeqStack* st)
{
if (SeqStackEmpty(st))
{
printf("栈为空\n");
return;
}
printf("栈元素(栈底→栈顶):");
// 遍历打印全部元素
for (int i = 0; i <= st->top; i++)
{
printf("%d ", st->arr[i]);
}
printf("\n");
}
2.1.2 链式栈
- 结构特点 :
- 底层使用单链表(通常不需要双向链表,因为栈只在头部操作)。
- 维护一个指针
top,始终指向链表的头节点。 - 入栈(Push) :在链表头部插入新节点,更新
top指针。 - 出栈(Pop) :删除头节点,更新
top指针指向下一个节点。
- 头文件
cpp
#ifndef STACKLINK_H
#define STACKLINK_H
#include <stdio.h>
#include <stdlib.h>
typedef int STDataType;
typedef struct StackNode
{
STDataType data; // 结点数据
struct StackNode* next; // 后继结点指针
} StackNode;
typedef struct LinkStack
{
StackNode* head; // 链表头指针(栈顶)
} LinkStack;
void LinkStackInit(LinkStack* st);
void LinkStackPush(LinkStack* st, STDataType x);
void LinkStackPop(LinkStack* st);
STDataType LinkStackTop(LinkStack* st);
int LinkStackEmpty(LinkStack* st);
void LinkStackDestroy(LinkStack* st);
void LinkStackPrint(LinkStack* st);
static StackNode* BuyNode(STDataType x);
#endif
- 接口实现文件
cpp
#include "StackLink.h"
static StackNode* BuyNode(STDataType x)
{
// 新建结点分配内存
StackNode* newnode = (StackNode*)malloc(sizeof(StackNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(EXIT_FAILURE);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void LinkStackInit(LinkStack* st)
{
st->head = NULL; // 头指针置空,代表空栈
}
void LinkStackPush(LinkStack* st, STDataType x)
{
StackNode* newnode = BuyNode(x);
newnode->next = st->head; // 新结点指向原头结点
st->head = newnode; // 更新头指针为新结点
}
void LinkStackPop(LinkStack* st)
{
// 空栈禁止出栈
if (LinkStackEmpty(st))
{
printf("栈为空,无法出栈\n");
return;
}
StackNode* del = st->head;
st->head = st->head->next; // 头指针后移
free(del); // 释放被删除结点
}
STDataType LinkStackTop(LinkStack* st)
{
if (LinkStackEmpty(st))
{
printf("栈为空,无栈顶元素\n");
exit(EXIT_FAILURE);
}
return st->head->data; // 返回头结点数据(栈顶)
}
int LinkStackEmpty(LinkStack* st)
{
return st->head == NULL; // 头指针为空判定栈空
}
void LinkStackDestroy(LinkStack* st)
{
StackNode* cur = st->head;
// 逐个释放所有结点
while (cur != NULL)
{
StackNode* next = cur->next;
free(cur);
cur = next;
}
st->head = NULL;
}
void LinkStackPrint(LinkStack* st)
{
if (LinkStackEmpty(st))
{
printf("栈为空\n");
return;
}
StackNode* cur = st->head;
printf("栈元素(栈顶→栈底):");
// 遍历链表打印
while (cur != NULL)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
2.2 队列
队列的底层实现分为顺序队列(基于数组实现) 和**链式队列(基于链表实现)**两种逻辑结构
队列的实现选择:
顺序队列:已知最大数据量、频繁创建销毁
链式队列:数据量不确定,频繁扩容缩容
2.2.1 顺序队列
- 底层结构:静态数组(如
int data[MAX_SIZE])。 - 指针管理:维护两个整型下标(或指针):
- 队头指针 (front):指向队列中第一个元素的位置。
- 队尾指针 (rear):指向队列中最后一个元素的下一个位置(即下一个新元素将要插入的位置)。
- 入队/出队方向:
- 入队 (Enqueue):在
rear指向的位置存入数据,然后rear向后移动一位。 - 出队 (Dequeue):取出
front指向位置的数据,然后front向后移动一位。
- 入队 (Enqueue):在
- 头文件
cpp
#ifndef SEQQUEUE_H //防止头文件重复包含
#define SEQQUEUE_H
#include <stdbool.h>
// 队列最大容量
#define MAX_SIZE 100
// 数据类型,重命名方便后续数据类型的修改
typedef int datatype;
// 顺序队列结构体(内部静态数组)
typedef struct SeqQueue
{
datatype arr[MAX_SIZE];
int front; // 队头下标
int rear; // 下一个入队位置下标
} SeqQueue;
// 函数声明
// 初始化队列
void InitSeqQueue(SeqQueue* q);
// 判断队列是否满
bool isFull(SeqQueue* q);
// 判断队列是否空
bool isEmpty(SeqQueue* q);
// 入队,成功返回true,队满返回false
bool enqueue(SeqQueue* q, datatype x);
// 出队,成功返回true,队空返回false
bool dequeue(SeqQueue* q);
// 获取队头元素,队空返回false
bool GetFront(SeqQueue* q, datatype* val);
// 遍历打印队列所有有效元素
void PrintSeqQueue(SeqQueue* q);
// 清空队列(静态数组无需释放内存,仅重置头尾指针)
void ClearSeqQueue(SeqQueue* q);
#endif
- 接口实现文件
cpp
#include "SeqQueue.h"
#include <assert.h>
#include <stdio.h>
//初始化队列
void InitSeqQueue(SeqQueue* q)
{
assert(q);
q->front = q->rear = 0;
}
//判满
bool isFull(SeqQueue* q)
{
assert(q);
return q->rear == MAX_SIZE;
}
//判空
bool isEmpty(SeqQueue* q)
{
assert(q);
return q->rear == q->front;
}
//入队
bool enqueue(SeqQueue* q, datatype x)
{
assert(q);
if (isFull(q))
{
return false;
}
q->arr[q->rear] = x;
++q->rear;
return true;
}
//出队
bool dequeue(SeqQueue* q)
{
assert(q);
if (isEmpty(q))
{
return false;
}
q->front++;
return true;
}
//获取队头元素
bool GetFront(SeqQueue* q, datatype* val)
{
assert(q && val);
if (isEmpty(q))
return false;
*val = q->arr[q->front];
return true;
}
//打印队列有效元素
void PrintSeqQueue(SeqQueue* q)
{
assert(q);
printf("队列元素:");
for (int i = q->front; i < q->rear; i++)
{
printf("%d ", q->arr[i]);
}
printf("\n");
}
//清空队列
void ClearSeqQueue(SeqQueue* q)
{
assert(q);
q->front = q->rear = 0;
}
在普通数组中,出队后前面的空间无法被复用,会发生假溢出(有内存而无法使用)


我们可以通过循环队列来对这个不足进行优化,底层依然是数组,但逻辑上首尾相连。
通过取模运算**%数组容量**来更新指针,让front和rear走到数组末尾后折返到数组头部,复用前面队空出来的位置
设数组最大容量
MaxSize
front:队头指针,指向队首元素位置rear:队尾指针,指向下一个待插入元素的位置

通过图片我们可以看出循环队列的判空和判满条件都是front == rear
我们可以牺牲一个空间来区分"队空"和"队满",队列最多储存**MaxSize-1**个元素
队空:front == rear
队满:(rear + 1) % MaxSize == front

- 头文件
cpp
#ifndef CIRCLEQUEUE_H
#define CIRCLEQUEUE_H
#include <stdio.h>
#include <assert.h>
#include <stdbool.h>
// 数组最大容量
#define MaxSize 8
// 存储数据类型:整型
typedef int CQDataType;
// 循环队列结构体
typedef struct
{
CQDataType arr[MaxSize];
int front; // 队头下标
int rear; // 队尾下一个位置下标
} CQueue;
// 初始化队列
void CQInit(CQueue* cq);
// 清空重置队列
void CQDestroy(CQueue* cq);
// 判断队空
bool CQEmpty(CQueue* cq);
// 判断队满(牺牲一个位置判满)
bool CQFull(CQueue* cq);
// 入队:成功返回true,满返回false
bool CQPush(CQueue* cq, CQDataType val);
// 出队:成功返回true,空返回false
bool CQPop(CQueue* cq);
// 获取队头元素
CQDataType CQFront(CQueue* cq);
// 获取有效元素个数
int CQSize(CQueue* cq);
// 遍历打印队列所有有效数据
void CQPrint(CQueue* cq);
#endif
- 接口实现文件
cpp
#include "CircleQueue.h"
// 初始化队列:将队头、队尾下标置0
void CQInit(CQueue* cq)
{
// 防止传入空指针,非法访问
assert(cq);
cq->front = 0;
cq->rear = 0;
}
// 重置清空队列,静态数组不用free,只需复位头尾下标
void CQDestroy(CQueue* cq)
{
assert(cq);
cq->front = cq->rear = 0;
}
// 判断队列是否为空
bool CQEmpty(CQueue* cq)
{
assert(cq);
// front与rear重合代表没有有效元素
return cq->front == cq->rear;
}
// 判断队列是否已满(牺牲一个空位判满方案)
bool CQFull(CQueue* cq)
{
assert(cq);
// rear往后走一格取模等于front,说明数组只剩一个空位,队列已满
return (cq->rear + 1) % MaxSize == cq->front;
}
// 入队:在队尾插入一个元素
bool CQPush(CQueue* cq, CQDataType val)
{
assert(cq);
// 队列满则入队失败
if (CQFull(cq))
{
return false;
}
// 将数据放入rear指向位置
cq->arr[cq->rear] = val;
// rear向后移动,取模实现循环
cq->rear = (cq->rear + 1) % MaxSize;
return true;
}
// 出队:删除队头元素
bool CQPop(CQueue* cq)
{
assert(cq);
// 队列为空无法出队
if (CQEmpty(cq))
{
return false;
}
// front向后移动完成出队,取模循环
cq->front = (cq->front + 1) % MaxSize;
return true;
}
// 获取队头元素值
CQDataType CQFront(CQueue* cq)
{
assert(cq);
// 断言:队列为空直接报错,禁止取队头
assert(!CQEmpty(cq));
return cq->arr[cq->front];
}
// 计算当前队列有效元素数量
int CQSize(CQueue* cq)
{
assert(cq);
// 加上MaxSize再取模,避免rear < front时出现负数
return (cq->rear - cq->front + MaxSize) % MaxSize;
}
// 遍历打印队列所有有效数据
void CQPrint(CQueue* cq)
{
assert(cq);
// 空队列直接提示
if (CQEmpty(cq))
{
printf("队列为空\n");
return;
}
// 从队头开始遍历
int cur = cq->front;
printf("队列元素:");
// 循环终止条件:走到rear结束,只遍历有效区间[front, rear)
while (cur != cq->rear)
{
printf("%d ", cq->arr[cur]);
// 下标后移,循环取模
cur = (cur + 1) % MaxSize;
}
printf("\n");
}
2.2.2 链式队列
- 底层结构:通常使用单向链表。
- 队头指针 (front):指向链表的第一个节点(队头)。
- 队尾指针 (rear):指向链表的最后一个节点(队尾)。
- 入队/出队方向:
- 入队 (Enqueue):在链表的尾部插入新节点(由 rear 指针操作)。
- 出队 (Dequeue):从链表的头部删除节点(由 front 指针操作)。
- 动态内存:不需要预先分配固定大小的数组空间,按需申请和释放节点内存。
- 头文件
cpp
#ifndef LINKQUEUE_H
#define LINKQUEUE_H
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
// 存储数据类型
typedef int LQDataType;
// 队列节点结构
typedef struct LinkNode
{
LQDataType data;
struct LinkNode* next;
} LinkNode;
// 链式队列结构体:保存头、尾指针,方便头尾操作
typedef struct
{
LinkNode* head; // 头结点
LinkNode* tail; // 尾指针
} LinkQueue;
// 初始化链式队列(创建哨兵头结点)
void LQInit(LinkQueue* q);
// 销毁整个队列,释放所有节点内存
void LQDestroy(LinkQueue* q);
// 判断队列是否为空
bool LQEmpty(LinkQueue* q);
// 队尾入队
bool LQPush(LinkQueue* q, LQDataType val);
// 队头出队
bool LQPop(LinkQueue* q);
// 获取队头元素
LQDataType LQFront(LinkQueue* q);
// 统计队列有效元素个数
int LQSize(LinkQueue* q);
// 遍历打印队列所有元素
void LQPrint(LinkQueue* q);
#endif
- 接口实现文件
cpp
#include "LinkQueue.h"
// 创建一个新节点
static LinkNode* BuyNode(LQDataType val)
{
LinkNode* newnode = (LinkNode*)malloc(sizeof(LinkNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->data = val;
newnode->next = NULL;
return newnode;
}
// 初始化队列:创建哨兵头结点,头尾都指向头结点
void LQInit(LinkQueue* q)
{
assert(q);
q->head = BuyNode(0);
q->tail = q->head;
}
// 释放全部节点,销毁队列
void LQDestroy(LinkQueue* q)
{
assert(q);
LinkNode* cur = q->head;
while (cur != NULL)
{
LinkNode* next = cur->next;
free(cur);
cur = next;
}
q->head = q->tail = NULL;
}
// 判断队空:头结点下没有有效节点
bool LQEmpty(LinkQueue* q)
{
assert(q);
return q->head == q->tail;
}
// 尾插入队
bool LQPush(LinkQueue* q, LQDataType val)
{
assert(q);
LinkNode* newnode = BuyNode(val);
q->tail->next = newnode;
q->tail = newnode;
return true;
}
// 头删出队
bool LQPop(LinkQueue* q)
{
assert(q);
if (LQEmpty(q))
{
return false;
}
// 第一个有效节点
LinkNode* del = q->head->next;
q->head->next = del->next;
// 如果删完最后一个元素,尾指针归位到头结点
if (q->tail == del)
{
q->tail = q->head;
}
free(del);
return true;
}
// 获取队头数据
LQDataType LQFront(LinkQueue* q)
{
assert(q);
assert(!LQEmpty(q));
return q->head->next->data;
}
// 统计节点个数
int LQSize(LinkQueue* q)
{
assert(q);
int count = 0;
LinkNode* cur = q->head->next;
while (cur != NULL)
{
count++;
cur = cur->next;
}
return count;
}
// 遍历打印队列
void LQPrint(LinkQueue* q)
{
assert(q);
if (LQEmpty(q))
{
printf("队列为空\n");
return;
}
LinkNode* cur = q->head->next;
printf("队列元素:");
while (cur != NULL)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
三、经典算法题
3.1 有效的括号
https://leetcode.cn/problems/valid-parentheses/
- 解题思路
遇到左括号直接入栈,遇到右括号,先判断栈是否为空,为空直接不匹配,返回错误。不为空取栈顶对比是否匹配,匹配弹出栈顶,不匹配直接返回错误。遍历结束后,栈为空说明括号全部匹配。

- 代码实现
cpp
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 自定义栈结构
typedef char STDataType;
typedef struct SeqStack
{
STDataType* arr;
int top;
int capacity;
} SeqStack;
void SeqStackInit(SeqStack* st)
{
st->arr = (STDataType*)malloc(sizeof(STDataType) * 4);
st->top = -1;
st->capacity = 4;
}
static void Expand(SeqStack* st)
{
STDataType* tmp = realloc(st->arr, sizeof(STDataType) * st->capacity * 2);
st->arr = tmp;
st->capacity *= 2;
}
void Push(SeqStack* st, STDataType x)
{
if (st->top + 1 == st->capacity)
Expand(st);
st->arr[++st->top] = x;
}
void Pop(SeqStack* st)
{
st->top--;
}
STDataType Top(SeqStack* st)
{
return st->arr[st->top];
}
bool Empty(SeqStack* st)
{
return st->top == -1;
}
void Destroy(SeqStack* st)
{
free(st->arr);
}
bool isValid(char * s){
SeqStack st;
SeqStackInit(&st);
int i = 0;
while(s[i] != '\0')
{
// 左括号入栈
if(s[i] == '(' || s[i] == '{' || s[i] == '[')
{
Push(&st, s[i]);
}
else
{
// 右括号但栈空,直接错误
if(Empty(&st))
{
Destroy(&st);
return false;
}
char topch = Top(&st);
if( (s[i]==')'&&topch=='(') || (s[i]=='}'&&topch=='{') || (s[i]==']'&&topch=='[') )
{
Pop(&st);
}
else
{
Destroy(&st);
return false;
}
}
i++;
}
bool ret = Empty(&st);
Destroy(&st);
return ret;
}
3.2 用栈实现队列
https://leetcode.cn/problems/implement-queue-using-stacks/
- 解题思路
使用两个栈 :in栈负责入队,out栈负责出队
入队:直接push到in栈 ;出队/取队头:如果out栈为空,将in栈的元素全部导入out栈中,出队Pop out栈,取队头取out栈栈顶;判空:两个栈同时为空,队列就为空。

- 代码实现
cpp
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 栈存储元素类型重命名,方便后续修改类型
typedef int STDataType;
// 顺序栈结构体定义
typedef struct SeqStack
{
STDataType* arr; // 动态数组,存放栈元素
int top; // 栈顶下标,这里约定:top=-1 代表栈空
int cap; // 当前数组总容量
} SeqStack;
// 栈初始化
void StackInit(SeqStack* st)
{
// 初始开辟4个int大小空间
st->arr = malloc(sizeof(int) * 4);
st->top = -1; // 栈空标记
st->cap = 4; // 初始容量4
}
// 入栈:把x压入栈顶
void StackPush(SeqStack* st, int x)
{
// 判断是否满:top+1等于容量,说明没有空余位置
if (st->top + 1 == st->cap)
{
// 扩容为原来2倍
st->arr = realloc(st->arr, st->cap * 2 * sizeof(int));
st->cap *= 2;
}
// 先top自增,再赋值
st->arr[++st->top] = x;
}
// 判断栈是否为空
bool StackEmpty(SeqStack* st)
{
// top=-1 栈为空,返回true;否则false
return st->top == -1;
}
// 获取栈顶元素(不删除)
int StackTop(SeqStack* st)
{
return st->arr[st->top];
}
// 出栈:删除栈顶元素(只移动下标,不释放空间)
void StackPop(SeqStack* st)
{
st->top--;
}
// 销毁栈:释放动态数组内存
void StackDestroy(SeqStack* st)
{
free(st->arr);
// 可选:置空防止野指针 st->arr = NULL;
}
// ====================== 两个栈实现队列 ======================
// 队列结构体:包含入栈、出栈两个栈
typedef struct {
SeqStack in; // 入队栈:所有新元素先压入这里
SeqStack out; // 出队栈:弹出队头元素用
} MyQueue;
// 创建队列,初始化两个内部栈
MyQueue* myQueueCreate() {
MyQueue* q = malloc(sizeof(MyQueue));
StackInit(&q->in);
StackInit(&q->out);
return q;
}
// 队列入队:直接往in栈压数据
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->in, x);
}
// 队列出队:删除并返回队首元素
int myQueuePop(MyQueue* obj) {
// 如果out栈为空,需要把in栈所有元素全部倒进out栈
if (StackEmpty(&obj->out))
{
// in不为空就持续搬运
while (!StackEmpty(&obj->in))
{
// 取出in栈顶,压入out栈
StackPush(&obj->out, StackTop(&obj->in));
// in栈弹出该元素
StackPop(&obj->in);
}
}
// out栈顶就是队列头部
int ret = StackTop(&obj->out);
StackPop(&obj->out);
return ret;
}
// 获取队首元素(不删除)
int myQueuePeek(MyQueue* obj) {
// out为空则先倒数据
if (StackEmpty(&obj->out))
{
while (!StackEmpty(&obj->in))
{
StackPush(&obj->out, StackTop(&obj->in));
StackPop(&obj->in);
}
}
return StackTop(&obj->out);
}
// 判断队列整体是否为空
bool myQueueEmpty(MyQueue* obj) {
// 两个栈同时为空,队列才是空
return StackEmpty(&obj->in) && StackEmpty(&obj->out);
}
// 释放队列所有内存
void myQueueFree(MyQueue* obj) {
// 先销毁两个内部栈的数组
StackDestroy(&obj->in);
StackDestroy(&obj->out);
// 再释放队列结构体本身
free(obj);
}
3.3 用队列实现栈
https://leetcode.cn/problems/implement-stack-using-queues/
- 解题思路
使用两个队列,q1,q2 入栈元素进入q1,出栈把 q1 中除最后一个元素外,全部转移到 q2,
弹出 q1 仅剩的元素(栈顶),每次出栈将q1与q2交换位置,保证q1始终为主队列。

- 代码实现
cpp
#include <stdio.h>
#include <stdlib.h>
// 队列结点结构体
typedef struct QNode {
int data;
struct QNode *next;
} QNode;
// 队列结构体:头尾指针
typedef struct {
QNode *front; // 队头,出队用
QNode *rear; // 队尾,入队用
} Queue;
// ===================== 队列基础函数 =====================
// 初始化空队列
void InitQueue(Queue *q) {
q->front = q->rear = NULL;
}
// 判断队列是否为空
int QueueEmpty(Queue *q) {
return q->front == NULL;
}
// 入队
int EnQueue(Queue *q, int val) {
QNode *newNode = (QNode*)malloc(sizeof(QNode));
if (newNode == NULL) return 0; // 内存分配失败
newNode->data = val;
newNode->next = NULL;
if (QueueEmpty(q)) {
q->front = q->rear = newNode;
} else {
q->rear->next = newNode;
q->rear = newNode;
}
return 1;
}
// 出队
int DeQueue(Queue *q, int *ret) {
if (QueueEmpty(q)) return 0;
QNode *temp = q->front;
*ret = temp->data;
q->front = q->front->next;
free(temp);
// 如果队列为空,尾指针置空
if (q->front == NULL) {
q->rear = NULL;
}
return 1;
}
// 获取队头元素(不删除)
int GetFront(Queue *q, int *ret) {
if (QueueEmpty(q)) return 0;
*ret = q->front->data;
return 1;
}
// 销毁队列,释放所有内存
void DestroyQueue(Queue *q) {
int tmp;
while (!QueueEmpty(q)) {
DeQueue(q, &tmp);
}
}
// ===================== 用两个队列模拟栈 =====================
Queue q1, q2; // q1主力存数据,q2中转临时队列
// 栈初始化
void MyStackInit(void) {
InitQueue(&q1);
InitQueue(&q2);
}
// 入栈操作
void MyStackPush(int x) {
EnQueue(&q1, x); // 新元素直接进入主队列q1
}
// 出栈操作,返回出栈元素
int MyStackPop(void) {
int val;
// 把q1除最后一个元素外,全部转移到q2
while (q1.front != q1.rear) {
DeQueue(&q1, &val);
EnQueue(&q2, val);
}
// q1仅剩栈顶元素,弹出
int topVal;
DeQueue(&q1, &topVal);
// 交换q1 q2,让q1继续作为主队列
Queue temp = q1;
q1 = q2;
q2 = temp;
return topVal;
}
// 获取栈顶元素(不出栈)
int MyStackTop(void) {
int val;
// 转移前n-1个元素到q2
while (q1.front != q1.rear) {
DeQueue(&q1, &val);
EnQueue(&q2, val);
}
// 取出栈顶
int topVal;
GetFront(&q1, &topVal);
// 把栈顶也移入q2
DeQueue(&q1, &val);
EnQueue(&q2, val);
// 交换队列
Queue temp = q1;
q1 = q2;
q2 = temp;
return topVal;
}
// 判断栈是否为空
int MyStackEmpty(void) {
return QueueEmpty(&q1);
}
// 销毁栈,释放内存
void MyStackDestroy(void) {
DestroyQueue(&q1);
DestroyQueue(&q2);
}
// ===================== 测试主函数 =====================
int main(void) {
MyStackInit();
MyStackPush(1);
MyStackPush(2);
MyStackPush(3);
printf("栈顶:%d\n", MyStackTop()); // 输出3
printf("出栈:%d\n", MyStackPop()); // 弹出3
printf("栈顶:%d\n", MyStackTop()); // 输出2
if (!MyStackEmpty()) {
printf("栈非空\n");
}
MyStackDestroy();
return 0;
}
到这里,栈与队列的基础概念、两种底层实现方式,以及三道高频 LeetCode 模拟习题就全部讲解完了
希望本篇内容能帮大家梳理巩固栈与队列~~ 我们下期再见
如果觉得这篇文章对你有帮助,别忘了点赞收藏哦~~