线 性 数 据 结 构 双 雄:栈 与 队 列 的 原 理、实 现 与 应 用
- 栈
- [栈 的 操 作 实 现](#栈 的 操 作 实 现)
-
- [代 码 全 貌 与 功 能 介 绍](#代 码 全 貌 与 功 能 介 绍)
- [代 码 效 果 展 示](#代 码 效 果 展 示)
- [队 列](#队 列)
- [队 列 的 操 作 实 现](#队 列 的 操 作 实 现)
-
- [代 码 全 貌 与 功 能 介 绍](#代 码 全 貌 与 功 能 介 绍)
- [代 码 效 果 展 示](#代 码 效 果 展 示)
- [栈 与 队 列 的 对 比 分 析](#栈 与 队 列 的 对 比 分 析)
- [总 结 与 进 阶 建 议](#总 结 与 进 阶 建 议)
-
- [核 心 知 识 点 回 顾](#核 心 知 识 点 回 顾)
- [学 习 进 阶 建 议](#学 习 进 阶 建 议)
- [总 结](#总 结)
💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 数据 结 构。
💡个 人 主 页:@笑口常开xpr 的 个 人 主 页
📚系 列 专 栏:硬 核 数 据 结 构 与 算 法
✨代 码 趣 语:栈 如 弹 夹,后 进 先 出;队 似 排 队,先 进 先 出。
💪代 码 千 行,始 于 坚 持,每 日 敲 码,进 阶 编 程 之 路。
📦gitee 链 接:gitee

在 数 据 结 构 的 世 界 里,每 一 种 设 计 都 可 能 孕 育 出 惊 人 的 效 率 变 革。你 是 否 深 思 过,一 组 精 心 组 织 的 数 据 究 竟 能 创 造 怎 样 的 奇 迹?每 一 次 挖 掘 底 层 原 理,都 是 与 计 算 机 智 慧 的 巅 峰 对 话;每 一 次 剖 析 存 储 模 式,都 在 破 解 数 据 世 界 的 终 极 密 码。准 备 好 迎 接 这 场 盛 宴 了 吗?让 我 们 一 同 探 寻 栈 和 队 列 的 无 尽 奥 秘,见 证 它 如 何 重 塑 数 字 时 代 的 运 行 法 则!
栈
定 义
栈 是 一 种 特 殊 的 线 性 表,其 只 允 许 在 固 定 的 一 端 (栈 顶)进 行 插 入 和 删 除 元 素 操 作。
基 本 概 念
栈 顶
进 行 数 据 插 入 和 删 除 操 作 的 一 端 称 为 栈 顶。
栈 底
另 一 端 称 为 栈 底。
入 栈
栈 的 插 入 操 作 叫 做 进 栈 / 压 栈 / 入 栈,数 据 从 栈 顶 进 入。将 一 个 元 素 添 加 到 栈 顶,当 执 行 入 栈 操 作 时,栈 顶 指 针 向 上 移 动 一 位,指 向 新 加 入 的 元 素。
出 栈
栈 的 删 除 操 作 叫 做 出 栈,数 据 从 栈 顶 删 除。移 除 并 返 回 栈 顶 的 元 素,出 栈 后,栈 顶 指 针 向 下 移 动 一 位,指 向 栈 中 的 下 一 个 元 素。
原 则
栈 中 的 数 据 元 素 遵 守 后 进 先 出 或 者 先 进 后 出 的 原 则。即 最 后 入 栈 的 元 素 会 先 出 栈,最 先 入 栈 的 元 素 后 出 栈。
下 面 分 别 以 元 素 为 1234 入 栈 和 出 栈 进 行 动 画 演 示
栈 的 操 作 实 现
代 码 全 貌 与 功 能 介 绍
整 个 栈 由 三 个 主 要 文 件 构 成:stack.h、stack.c 和 test.c。这 种 多 文 件 的 架 构 设 计,有 助 于 将 不 同 功 能 模 块 分 离,提 高 代 码 的 可 读 性、可 维 护 性 与 可 扩 展 性。
stack.h
stack.h 包 含 了 栈 所 需 的 头 文 件 引 用、常 量 定 义 以 及 函 数 声 明。
test.c
test.c 是 栈 的 主 逻 辑 文 件,负 责 处 理 用 户 输 入 和 代 码 流 程 的 控 制。
stack.c
stack.c 则 实 现 了 栈 的 具 体 功 能 函 数。
下 面 展 示完 整 代 码
。
读 者 可 以 将 这 段 代 码 复 制 到 自 己 的 编 译 器 中 运 行。
stack.h
javascript
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
//静态栈
//#define N 10
//struct Stack
//{
// int a[N];
// int top;
//};
//动态栈
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity; //容量
}ST;
//初始化
void STInit(ST* ps);
void STDestroy(ST* ps);
void STMiddle(void(*Pf)(ST* ps, STDataType x), ST* ps);
//只能在一端插入,即栈顶
void STPush(ST* ps,STDataType x);
void STPop(ST* ps);
int STSize(ST* ps);
bool STEmpty(ST* ps);
//访问栈顶元素
STDataType STTop(ST* ps);
void STPrint(ST* ps);
//保存栈中的元素到文件中
void STSave(ST* ps);
//从文件中加载
void STLoad(ST* ps);
test.c
javascript
#define _CRT_SECURE_NO_WARNINGS 1
#include "stack.h"
void menu()
{
printf("***************************************\n");
printf("***** 0. STExit 1. STPush *****\n");
printf("***** 2. STPop 3. STSize *****\n");
printf("***** 4. STEmpty 5. STTop *****\n");
printf("***** 6. STPrint *****\n");
printf("***************************************\n");
}
void test()
{
int input = 0;
ST st;
STInit(&st);
do
{
menu();
printf("请输入你想要进行的操作:>");
scanf("%d", &input);
switch (input)
{
case 0:
STSave(&st);
STDestroy(&st);
break;
case 1:
STMiddle(STPush, &st);
break;
case 2:
STPop(&st);
STPrint(&st);
break;
case 3:
printf("%d\n", STSize(&st));
break;
case 4:
if (STEmpty(&st))
{
printf("栈为空\n");
}
else
{
printf("栈不为空\n");
}
break;
case 5:
printf("栈顶元素是:%d\n", STTop(&st));
break;
case 6:
STPrint(&st);
break;
default:
printf("输入无效,请重新输入\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
stack.c
javascript
#define _CRT_SECURE_NO_WARNINGS 1
#include "stack.h"
void STInit(ST* ps)
{
assert(ps);
ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL)
{
perror("ps->a");
return;
}
ps->capacity = 4;
ps->top = 0; //top是栈顶元素的下一个位置
//ps->top = -1; //top是栈顶元素
STLoad(ps);
}
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);
if (ps->top == 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] = x;
ps->top++;
}
void STPop(ST* ps)
{
assert(ps);
assert(!STEmpty(ps));
ps->top--;
}
int STSize(ST* ps)
{
assert(ps);
return ps->top;
}
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
STDataType STTop(ST* ps)
{
assert(ps);
assert(!STEmpty(ps));
return ps->a[ps->top - 1];
}
void STPrint(ST* ps)
{
assert(ps);
int i = 0;
while (i < ps->top)
{
printf("%d ", ps->a[i]);
i++;
}
printf("\n");
}
void STSave(ST* ps)
{
assert(ps);
FILE* pf = fopen("stack.txt", "w");
if (pf == NULL)
{
perror("fopen fail");
return;
}
int i = 0;
for (i = 0;i < ps->top;i++)
{
fprintf(pf, "%d ", ps->a[i]);
}
fclose(pf);
pf = NULL;
printf("保存数据成功\n");
}
void STLoad(ST* ps)
{
assert(ps);
FILE* pf = fopen("stack.txt", "r");
if (pf == NULL)
{
perror("fopen fail");
return;
}
int data = 0;
while (fscanf(pf, "%d", &data) == 1)
{
STPush(ps, data);
}
fclose(pf);
pf = NULL;
}
void STMiddle(void(*pf)(ST* ps, STDataType x),ST* ps)
{
assert(ps);
int x = 0;
printf("请输入你想要插入的数字:>");
scanf("%d", &x);
pf(ps, x);
STPrint(ps);
}
代 码 效 果 展 示
菜 单 展 示
每 次 循 环 开 始 时 会 显 示 菜 单,内 容 包 括:
STExit :退 出 程 序 并 将 栈 中 的 元 素 保 存 到 文 件 中
STPush :入 栈
STPop :出 栈
SLSize :栈 中 元 素 的 个 数
STEmpty :栈 是 否 为 空
STTop :栈 顶 元 素
STPrint:输 出 栈 中 的 元 素

退 出 栈(STExit)
输 入 0 后,程 序 会 将 栈 中 的 信 息 保 存 到 文 件 "stack.txt" 中。释 放 内 存 并 销 毁 栈, 然 后 退 出 程 序。

入 栈(STPush)
输 入 1 后,程 序 会 提 示 用 户 输 入 插 入 的 数 字。输 入 完 成 后,数 字 被 添 加 到 栈,然 后 输 出 修 改 后 的 栈。
出 栈(STPop)
输 入 2 后,先 入 栈 的 数 字 会 被 删 除,然 后 输 出 修 改 后 的 栈。

栈 中 有 效 元 素 个 数(STSize)
输 入 3 后,程 序 会 输 出 栈 中 的 元 素 个 数。
检 查 栈 是 否 为 空(STEmpty)
输 入 4 后,程 序 会 检 查 栈 是 否 为 空,如 果 栈 为 空,则 输 出 栈 是 空,反 之,则 输 出 栈 不 为 空。

输 出 栈 顶 元 素(STTop)
输 入 5 后,程 序 会 输 出 栈 顶 元 素。

输 出 栈 中 的 元 素(STPrint)
输 入 6 后,程 序 会 在 屏 幕 上 显 示 目 前 已 经 保 存 的 栈 中 的 元 素。

队 列
定 义
队 列 是 一 种 线 性 表。队 列 只 允 许 在 线 性 表 的 一 端 进 行 插 入 元 素,而 在 另 一 端 删 除 元 素。
基 本 概 念
队 尾
队 列 中 允 许 插 入 的 一 端 被 称 作 队 尾。
队 头
队 列 中 允 许 删 除 的 一 端 被 称 作 队 头。
出 队
出 队 是 从 队 列 的 头 部 移 除 并 返 回 一 个 元 素 的 操 作。
入 队
入 队 是 将 一 个 新 元 素 添 加 到 队 列 的 尾 部 的 操 作。
队 空
队 列 中 没 有 元 素
队 满
队 列 中 元 素 数 量 达 到 上 限

原 则
队 列 是 一 种 遵 循 先 进 先 出 或 者 后 进 后 出 原 则 的 重 要 数 据 结 构。

队 列 的 操 作 实 现
代 码 全 貌 与 功 能 介 绍
整 个 队 列 由 三 个 主 要 文 件 构 成:Queue.h、Queue.c 和 test.c。这 种 多 文 件 的 架 构 设 计,有 助 于 将 不 同 功 能 模 块 分 离,提 高 代 码 的 可 读 性、可 维 护 性 与 可 扩 展 性。
Queue.h
Queue.h 包 含 了 队 列 所 需 的 头 文 件 引 用、常 量 定 义 以 及 函 数 声 明。
test.c
test.c 是 队 列 的 主 逻 辑 文 件,负 责 处 理 用 户 输 入 和 代 码 流 程 的 控 制。
Queue.c
Queue.c 则 实 现 了 队 列 的 具 体 功 能 函 数。
下 面 展 示
完 整 代 码
。
读 者 可 以 将 这 段 代 码 复 制 到 自 己 的 编 译 器 中 运 行。
Queue.h
javascript
#include<stdio.h>
#include<stdbool.h>
#include<assert.h>
#include<stdlib.h>
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
//初始化队列
void QueueInit(Queue* pq);
//销毁队列
void QueueDestory(Queue* pq);
//队尾入队列
void QueuePush(Queue* pq,QDataType x);
//队头出队列
void QueuePop(Queue* pq);
//获取队列中有效元素个数
int QueueSize(Queue* pq);
//检查队列是否为空
bool QueueEmpty(Queue* pq);
//取出队头的数据
QDataType QueueFront(Queue* pq);
//取出队尾的数据
QDataType QueueBack(Queue* pq);
//输出队列
void QueuePrint(Queue* pq);
void QueueMiddlePush(void(*pf)(Queue* pq, QDataType x), Queue* pq);
void QueueMiddle(Queue* pq, int num);
//保存队列到文件中
void QueueSave(Queue* pq);
//从文件中加载队列
void QueueLoad(Queue* pq);
test.c
javascript
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
void menu()
{
printf("**********************************************\n");
printf("***** 0. QueueExit 1. QueuePush *****\n");
printf("***** 2. QueuePop 3. QueueSize *****\n");
printf("***** 4. QueueFront 5. QueueBack *****\n");
printf("***** 6. QueueEmpty 7. QueuePrint *****\n");
printf("**********************************************\n");
}
void test()
{
int input = 0;
Queue q;
QueueInit(&q);
do
{
menu();
printf("请输入你想要进行的操作:>");
scanf("%d", &input);
switch (input)
{
case 0:
QueueSave(&q);
QueueDestory(&q);
break;
case 1:
QueueMiddlePush(QueuePush, &q);
break;
case 2:
QueueMiddle(&q, 2);
break;
case 3:
printf("队列元素个数:%d\n", QueueSize(&q));
break;
case 4:
QueueMiddle(&q, 4);
break;
case 5:
QueueMiddle(&q, 5);
break;
case 6:
QueueMiddle(&q, 6);
break;
case 7:
QueuePrint(&q);
break;
default:
printf("输入无效,请重新输入\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
Queue.c
javascript
#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
QueueLoad(pq);
}
void QueueDestory(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("newnode");
return;
}
newnode->data = x;
newnode->next = NULL;
if (pq->head == NULL)
{
assert(pq->tail == NULL);
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->head != NULL);
//方法1
//QNode* next = pq->head->next;
//free(pq->head);
//pq->head = next;
//if (pq->head == NULL)
//{
// pq->tail = NULL;
//}
//方法2
if (pq->head->next == NULL)
{
free(pq->head);
pq->tail = pq->head = NULL;
}
else
{
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
pq->size--;
}
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
//return pq->size == 0;
return pq->head == NULL && pq->tail == NULL;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
void QueuePrint(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
if (QueueEmpty(pq))
{
printf("队列为空\n");
return;
}
else
{
while (cur)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
}
void QueueMiddlePush(void(*pf)(Queue* pq, QDataType x), Queue* pq)
{
int x = 0;
printf("请输入你想要插入的元素:>");
scanf("%d", &x);
pf(pq, x);
QueuePrint(pq);
}
void QueueMiddle(Queue* pq, int num)
{
if (num == 2)
{
if (QueueEmpty(pq))
{
printf("队列已空,无法出队\n");
}
else
{
printf("出队元素:%d\n", QueueFront(pq));
QueuePop(pq);
}
}
else if (num == 4)
{
if (QueueEmpty(pq))
{
printf("队列已空,无队头元素\n");
}
else
{
printf("队头元素:%d\n", QueueFront(pq));
}
}
else if (num == 5)
{
if (QueueEmpty(pq))
{
printf("队列已空,无队尾元素\n");
}
else
{
printf("队尾元素:%d\n", QueueBack(pq));
}
}
else
{
if (QueueEmpty(pq))
{
printf("队列已空\n");
}
else
{
printf("队列不为空\n");
}
}
}
void QueueSave(Queue* pq)
{
assert(pq);
FILE* pf = fopen("queue.txt", "w");
if (pf == NULL)
{
perror("无法打开文件");
return;
}
QNode* cur = pq->head;
while (cur)
{
fprintf(pf, "%d ", cur->data);
cur = cur->next;
}
fclose(pf);
pf = NULL;
printf("保存文件成功\n");
}
void QueueLoad(Queue* pq)
{
assert(pq);
FILE* pf = fopen("queue.txt", "r");
if (pf == NULL)
{
// 文件不存在或无法打开,可能是第一次运行
return;
}
int data = 0;
while (fscanf(pf, "%d", &data) != EOF)
{
QueuePush(pq, data);
}
fclose(pf);
pf = NULL;
}
代 码 效 果 展 示
菜 单 展 示
每 次 循 环 开 始 时 会 显 示 菜 单,内 容 包 括:
QueueExit :退 出 程 序 并 将 队 列 元 素 保 存 到 文 件 中
QueuePush :入 队 列
QueuePop :出 队 列
QueueSize :获 取 队 列 中 有 效 元 素 个 数
QueueFront :取 出 队 头 的 数 据
QueueBack :取 出 队 尾 的 数 据
QueueEmpty :检 查 队 列 是 否 为 空
QueuePrint:输 出 队 列 元 素

退 出 程 序(QueueExit)
输 入 0 后,程 序 会 将 队 列 中 的 信 息 保 存 到 文 件 "queue.txt" 中。释 放 内 存 并 销 毁 队 列, 然 后 退 出 程 序。

入 队 列(QueuePush)
输 入 1 后,程 序 会 提 示 用 户 输 入 插 入 的 数 字。输 入 完 成 后,数 字 被 添 加 到 队 列 的 末 尾,然 后 输 出 修 改 后 的 队 列。

出 队 列(QueuePop)
输 入 2 后,队 列 的 头 部 的 数 字 会 被 删 除,并 输 出 删 除 的 元 素(出 队 的 元 素)。

队 列 中 有 效 元 素 个 数(QueueSize)
输 入 3 后,程 序 会 输 出 队 列 中 的 元 素 个 数。

取 出 队 头 元 素(QueueFront)
输 入 4 后,程 序 会 输 出 队 列 的 头 部 元 素(队 列 的 第 一 个 元 素)。

取 出 队 尾 元 素(QueueBack)
输 入 5 后,程 序 会 输 出 队 列 的 尾 部 元 素(队 列 的 最 后 一 个 元 素)。

检 查 队 列 是 否 为 空(QueueEmpty)
输 入 6 后,程 序 会 检 查 队 列 是 否 为 空,如 果 队 列 为 空,则 输 出 队 列 是 空,如 果 队 列 不 为 空,则 输 出 队 列 不 为 空。

输 出 队 列 元 素(QueuePrint)
输 入 7 后,程 序 会 在 屏 幕 上 显 示 目 前 已 经 保 存 的 队 列 元 素。


栈 与 队 列 的 对 比 分 析
对比 | 栈(Stack) | 队列(Queue) |
---|---|---|
操作限制 | 仅在栈顶进行插入和删除 | 队尾插入,队头删除 |
数据原则 | 后进先出 | 先进先出 |
典型操作 | Push(入栈)、Pop(出栈) | Enqueue(入队)、Dequeue(出队) |
实现方式 | 数组或链表 | 数组或链表(通常链表更优) |
内存管理 | 通常需要考虑扩容 | 链表实现时动态分配节点 |
总 结 与 进 阶 建 议
核 心 知 识 点 回 顾
1、栈 是 一 种 先 进 后 出 的 数 据 结 构,所 有 操 作 集 中 在 栈 顶,适 合 需 要 "回 退" 逻 辑 的 场 景。
2、队 列 是 一 种 先 进 先 出 的 数 据 结 构,插 入 在 队 尾,删 除 在 队 头,适 合 需 要 保 持 顺 序 的 场 景。
3、两 者 都 是 线 性 数 据 结 构,但 操 作 方 式 截 然 不 同。
4、代 码 实 现 上,栈 可 以 用 数 组 或 链 表,队 列 更 倾 向 于 链 表 实 现 以 避 免 "假 溢 出"。
学 习 进 阶 建 议
深 入 理 解 原 理 :尝 试 分 析 不 同 场 景 下 栈 和 队 列 的 效 率 差 异。
拓 展 实 现 方 式:尝 试 用 不 同 数 据 结 构 实 现 栈 和 队 列 (如 双 向 循 环 链 表)。
总 结
至 此,关 于 栈 和 队 列 的 探 索 暂 告 一 段 落,但 你 的 编 程 征 程 才 刚 刚 启 航。编 写 代 码 是 与 计 算 机 逻 辑 深 度 对 话,过 程 中 虽 会 在 结 构 设 计、算 法 实 现 的 困 境 里 挣 扎,但 这 些 磨 砺 加 深 了 对 代 码 逻 辑 和 数 据 组 织 的 理 解。愿 你 合 上 电 脑 后,灵 感 不 断,在 数 据 结 构 的 世 界 里 持 续 深 耕,书 写 属 于 自 己 的 编 程 传 奇,下 一 次 开 启,定 有 全 新 的 精 彩 等 待。小 编 期 待 重 逢,盼 下 次 阅 读 时 见 证 你 们 更 大 的 进 步,共 赴 代 码 之 约!