数据结构之栈与队列
一,栈和队列的区别
1、核心定义与特性
特性 | 栈(Stack) | 队列(Queue) |
---|---|---|
定义 | 仅允许在栈顶(表尾)进行插入和删除的线性表,遵循 后进先出(LIFO)。 | 允许在队尾插入、队头删除的线性表,遵循 先进先出(FIFO)。 |
操作限制 | 插入(入栈 / Push)和删除(出栈 / Pop)只能在栈顶进行。 | 插入(入队 / Enqueue)在队尾,删除(出队 / Dequeue)在队头。 |
典型场景 | 适合 “后处理先完成” 的场景,如撤销操作、函数调用栈。 | 适合 “先处理先完成” 的场景,如任务排队、消息缓冲。 |
2、操作对比
操作 | 栈 | 队列 |
---|---|---|
插入位置 | 栈顶(表尾),新元素成为新栈顶。 | 队尾,新元素追加到队列末尾。 |
删除位置 | 栈顶,删除最后插入的元素(LIFO)。 | 队头,删除最早插入的元素(FIFO)。 |
核心操作 | Push(入栈)、Pop(出栈)、GetTop(取栈顶)。 | Enqueue(入队)、Dequeue(出队)、GetHead(取队头)。 |
时间复杂度 | 均为 O(1)(仅修改栈顶指针)。 | 均为 O(1)(仅修改队头 / 队尾指针)。 |
3、数据结构实现
3.1. 存储方式
-
栈:
- 顺序栈:用数组实现,通过
top
指针指向栈顶(下标),栈满时top = MAXSIZE-1
,栈空时top = -1
。- 优化:两栈共享空间(利用数组两端作为栈底,减少空间浪费)。
- 链栈:用链表实现,头指针作为栈顶,插入 / 删除在头部进行(头插法),无栈满限制(仅受内存限制)。
- 顺序栈:用数组实现,通过
-
队列:
- 顺序队列(循环队列):用数组实现,通过
front
(队头)和rear
(队尾下一个位置)指针管理,通过取模运算实现循环,解决假溢出问题。- 队满条件:
(rear + 1) % MAXSIZE == front
(牺牲一个单元区分空和满)。
- 队满条件:
- 链队列:用链表实现,带头尾指针,入队在队尾(尾插法),出队在队头(删除头结点的后继)。
- 顺序队列(循环队列):用数组实现,通过
3.2. 结构示意图
- 栈:
plaintext
栈顶 → [top] 新元素(入栈)/ 旧元素(出栈) 栈底 → [bottom](固定端)
- 队列:
plaintext
队头 → [front] 旧元素(出队) ← 队尾 [rear] 新元素(入队)
4、典型应用场景
场景 | 栈的应用 | 队列的应用 |
---|---|---|
函数调用 | 存储函数调用的参数、返回地址(系统栈自动管理)。 | 无直接应用,函数调用本质是栈结构。 |
表达式处理 | 后缀表达式求值、中缀转后缀(处理运算符优先级)。 | 无相关应用。 |
括号匹配 | 检测括号是否正确嵌套(如{[()]} )。 | 无相关应用。 |
任务调度 | 无直接应用,适合 “逆序处理”(如撤销操作)。 | 打印机任务队列、操作系统进程调度(FIFO)。 |
数据遍历 | 深度优先搜索(DFS,递归本质是栈)。 | 广度优先搜索(BFS,逐层遍历图或树)。 |
缓冲区管理 | 无直接应用。 | 输入输出缓冲区(如键盘输入、网络数据接收)。 |
5、核心区别总结
-
操作顺序:
- 栈:后进先出(LIFO)—— 最后插入的元素最先被删除(如弹夹装弹,最后装入的子弹最先射出)。
- 队列:先进先出(FIFO)—— 最先插入的元素最先被删除(如排队买票,先来先服务)。
-
适用场景:
- 栈适合处理 “逆序依赖” 问题(如函数递归、表达式求值中的括号匹配)。
- 队列适合处理 “顺序依赖” 问题(如任务排队、消息缓冲、逐层遍历)。
-
数据结构差异:
- 栈的操作集中在一端,实现简单;队列需要两端操作,顺序存储需处理循环逻辑(避免假溢出)。
6、一句话区分
- 栈:像 “叠盘子”,最后放上去的最先被拿走(LIFO)。
- 队列:像 “排队候车”,最先排队的人最先上车(FIFO)
二、栈与递归的关系:递归的本质是栈的应用
1. 递归的定义与栈的底层原理
递归是指函数直接或间接调用自身的过程,其核心依赖系统栈(调用栈)来保存每次调用的状态(参数、局部变量、返回地址),确保函数能正确返回并恢复上下文。
- 压栈过程:每次递归调用时,系统将当前函数的参数、局部变量、返回地址压入栈,形成 “调用帧”。
- 出栈过程:当递归终止条件满足时,从栈顶取出最近的调用帧,恢复上下文并继续执行。
示例:斐波那契数列递归实现
int fib(int n) {if (n <= 1) return n; // 终止条件return fib(n-1) + fib(n-2); // 递归调用
}
- 栈行为:计算
fib(5)
时,系统栈会依次压入fib(5)
→fib(4)
→fib(3)
→fib(2)
→fib(1)
(终止),然后逐层出栈计算返回值,最终得到结果。 - 缺点:递归深度过大会导致栈溢出(如
fib(100)
),且重复计算效率低(可通过记忆化优化)。
2. 递归 vs 手动栈实现
特性 | 递归(系统栈) | 手动栈(用户实现) |
---|---|---|
管理方式 | 系统自动管理调用帧,无需手动操作。 | 需要手动压栈、出栈(如表达式求值)。 |
适用场景 | 适合树形 / 分治问题(如 DFS、回溯),代码简洁。 | 适合显式需要 LIFO 的场景(如表达式转换)。 |
风险 | 可能栈溢出(递归深度超过系统栈容量)。 | 需手动处理栈满 / 栈空逻辑。 |
三、栈在四则运算中的应用:中缀表达式→后缀表达式→求值
1. 表达式的三种表示形式
- 中缀表达式:操作符在操作数中间(如
3 + 4 * 2 - 1
),符合人类习惯,但计算机处理需要考虑优先级和括号。 - 后缀表达式(逆波兰式):操作符在操作数之后(如
3 4 2 * + 1 -
),无需括号,计算机可直接求值。 - 前缀表达式(波兰式):操作符在操作数之前(如
- + 3 * 4 2 1
),较少使用。
2. 中缀转后缀表达式(核心:栈处理运算符优先级)
规则:
- 初始化空栈,用于存储运算符。
- 遍历中缀表达式:
- 若为操作数:直接加入后缀表达式。
- 若为左括号
(
:压栈。 - 若为右括号
)
:弹出栈中运算符直到遇到左括号(左括号不加入后缀表达式)。 - 若为运算符:
- 若栈空或栈顶为左括号,直接压栈。
- 否则,若当前运算符优先级 ≥ 栈顶运算符优先级,弹出栈顶运算符加入后缀表达式,直到条件不满足,再压栈当前运算符。
- 遍历结束后:弹出栈中剩余运算符加入后缀表达式。
优先级定义(从高到低):
* / %
> + -
> (
)
(括号仅用于界定优先级,本身无优先级)。
示例:中缀表达式 3 + 4 * 2 - 1
转后缀
- 遍历过程:
3
:操作数,直接输出 →3
+
:栈空,压栈 → 栈:+
4
:操作数,输出 →3 4
*
:优先级高于栈顶+
,压栈 → 栈:+ *
2
:操作数,输出 →3 4 2
-
:优先级低于栈顶*
,弹出*
→ 输出3 4 2 *
,栈:+
;再比较-
与+
优先级相等,弹出+
→ 输出3 4 2 * +
,栈空,压栈-
1
:操作数,输出 →3 4 2 * + 1
- 遍历结束,弹出栈中
-
→ 最终后缀表达式:3 4 2 * + 1 -
3. 后缀表达式求值(核心:栈处理操作数)
规则:
- 初始化空栈,用于存储操作数。
- 遍历后缀表达式:
- 若为操作数:压栈。
- 若为运算符:弹出栈顶两个操作数(先弹出的是右操作数,后弹出的是左操作数),计算结果后压栈。
- 遍历结束后:栈顶即为最终结果。
示例:后缀表达式 3 4 2 * + 1 -
求值
- 遍历过程:
3
:压栈 → 栈:[3]
4
:压栈 → 栈:[3, 4]
2
:压栈 → 栈:[3, 4, 2]
*
:弹出2
和4
,计算4×2=6
,压栈 → 栈:[3, 6]
+
:弹出6
和3
,计算3+6=9
,压栈 → 栈:[9]
1
:压栈 → 栈:[9, 1]
-
:弹出1
和9
,计算9-1=8
,压栈 → 最终结果:8
五,栈的示例代码
① 栈的基本操作
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>#define MAX_SIZE 100// 定义栈结构体
typedef struct {int data[MAX_SIZE];int top;
} Stack;// 初始化栈
void initStack(Stack* s)
{s->top = -1;
}// 判断栈是否为空
bool isEmpty(Stack* s)
{return s->top == -1;
}// 判断栈是否已满
bool isFull(Stack* s)
{return s->top == MAX_SIZE - 1;
}// 入栈操作
bool push(Stack* s, int value)
{if (isFull(s)) //如果栈未满,先将 top 的值加 1(++(s->top)),然后将 value 存入 data 数组中 top 所指向的位置{printf("栈已满,无法入栈!\n");return false;}s->data[++(s->top)] = value;return true;
}// 出栈操作
bool pop(Stack* s, int* value)
{if (isEmpty(s)) {printf("栈为空,无法出栈!\n");return false;}*value = s->data[(s->top)--];//*value = s->data[(s->top)--];:如果栈不为空,先将 top 所指向的元素赋值给 *value(即 value 所指向的变量),然后将 top 的值减 1((s->top)--)return true;
}// 获取栈顶元素
bool peek(Stack* s, int* value) {if (isEmpty(s)) {printf("栈为空,无栈顶元素!\n");return false;}*value = s->data[s->top];return true;
}int main() {Stack s;initStack(&s);push(&s, 10);push(&s, 20);push(&s, 30);int value;if (peek(&s, &value)) {printf("栈顶元素是: %d\n", value);}if (pop(&s, &value)) {printf("出栈元素是: %d\n", value);}return 0;
}
② 栈的应用实例
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>#define MAX_SIZE 100// 定义栈结构体
typedef struct {char data[MAX_SIZE];int top;
} Stack;// 初始化栈
void initStack(Stack *s) {s->top = -1;
}// 判断栈是否为空
bool isEmpty(Stack *s) {return s->top == -1;
}// 判断栈是否已满
bool isFull(Stack *s) {return s->top == MAX_SIZE - 1;
}// 入栈操作
bool push(Stack *s, char value) {if (isFull(s)) {printf("栈已满,无法入栈!\n");return false;}s->data[++(s->top)] = value;return true;
}// 出栈操作
bool pop(Stack *s, char *value) {if (isEmpty(s)) {printf("栈为空,无法出栈!\n");return false;}*value = s->data[(s->top)--];return true;
}// 获取栈顶元素
bool peek(Stack *s, char *value) {if (isEmpty(s)) {printf("栈为空,无栈顶元素!\n");return false;}*value = s->data[s->top];return true;
}// 判断左右括号是否匹配
bool is_matching_pair(char left, char right) {if (left == '(' && right == ')') return true;if (left == '[' && right == ']') return true;if (left == '{' && right == '}') return true;return false;
}// 检查表达式括号是否匹配
bool is_balanced(const char *expression) {Stack s;initStack(&s);for (int i = 0; expression[i] != '\0'; i++) {if (expression[i] == '(' || expression[i] == '[' || expression[i] == '{') {// 如果是左括号,入栈push(&s, expression[i]);} else if (expression[i] == ')' || expression[i] == ']' || expression[i] == '}') {if (isEmpty(&s)) {// 如果栈为空,说明没有匹配的左括号return false;}char top;pop(&s, &top);if (!is_matching_pair(top, expression[i])) {// 如果弹出的左括号和当前右括号不匹配return false;}}}// 最后检查栈是否为空,如果为空则括号完全匹配return isEmpty(&s);
}int main() {const char *expressions[] = {"{[()]}", "{[(])}", "((()", "())"};int num_expressions = sizeof(expressions) / sizeof(expressions[0]);for (int i = 0; i < num_expressions; i++) {printf("表达式 %s 是否括号匹配: %s\n", expressions[i], is_balanced(expressions[i]) ? "是" : "否");}return 0;
}
③使用栈实现汉诺塔问题
#include <stdio.h>
#include <stdlib.h>#define MAX_SIZE 100/*汉诺塔问题的传统解法是递归,
这里使用栈来模拟递归调用的过程。栈可以保存每一步的状态,
包括盘子数量、柱子信息等,通过不断地入栈和出栈操作,实现盘子的移动。*/
// 定义栈结构体
typedef struct {int data[MAX_SIZE];int top;
} Stack;// 初始化栈
void initStack(Stack* s) {s->top = -1;
}// 判断栈是否为空
int isEmpty(Stack* s) {return s->top == -1;
}// 判断栈是否已满
int isFull(Stack* s) {return s->top == MAX_SIZE - 1;
}// 入栈操作
void push(Stack* s, int value) {if (isFull(s)) {printf("栈已满,无法入栈!\n");return;}s->data[++(s->top)] = value;
}// 出栈操作
int pop(Stack* s) {if (isEmpty(s)) {printf("栈为空,无法出栈!\n");return -1;}return s->data[(s->top)--];
}// 获取栈顶元素
int peek(Stack* s) {if (isEmpty(s)) {printf("栈为空,无栈顶元素!\n");return -1;}return s->data[s->top];
}// 移动盘子
void moveDisk(Stack* source, Stack* destination, char sourceName, char destinationName) {int disk = pop(source);push(destination, disk);printf("将盘子 %d 从 %c 移动到 %c\n", disk, sourceName, destinationName);
}// 汉诺塔问题
void hanoi(int n, Stack* source, Stack* auxiliary, Stack* destination, char sourceName, char auxiliaryName, char destinationName) {Stack stack;initStack(&stack);// 初始状态入栈push(&stack, n);push(&stack, (int)sourceName);push(&stack, (int)auxiliaryName);push(&stack, (int)destinationName);push(&stack, (int)source);push(&stack, (int)auxiliary);push(&stack, (int)destination);while (!isEmpty(&stack)) {destination = (Stack*)pop(&stack);auxiliary = (Stack*)pop(&stack);source = (Stack*)pop(&stack);destinationName = (char)pop(&stack);auxiliaryName = (char)pop(&stack);sourceName = (char)pop(&stack);n = pop(&stack);if (n == 1) {moveDisk(source, destination, sourceName, destinationName);}else {// 模拟递归调用,按相反顺序入栈push(&stack, n - 1);push(&stack, (int)auxiliary);push(&stack, (int)source);push(&stack, (int)destination);push(&stack, (int)auxiliaryName);push(&stack, (int)sourceName);push(&stack, (int)destinationName);push(&stack, 1);push(&stack, (int)source);push(&stack, (int)auxiliary);push(&stack, (int)destination);push(&stack, (int)sourceName);push(&stack, (int)auxiliaryName);push(&stack, (int)destinationName);push(&stack, n - 1);push(&stack, (int)source);push(&stack, (int)destination);push(&stack, (int)auxiliary);push(&stack, (int)sourceName);push(&stack, (int)destinationName);push(&stack, (int)auxiliaryName);}}
}int main() {int n = 3;Stack source, auxiliary, destination;initStack(&source);initStack(&auxiliary);initStack(&destination);// 初始化源柱子的盘子for (int i = n; i > 0; i--) {push(&source, i);}hanoi(n, &source, &auxiliary, &destination, 'A', 'B', 'C');return 0;
}
⑤ 栈的应用场景
1. 函数调用和递归
- 原理:在程序执行过程中,当调用一个函数时,系统会将当前函数的上下文信息(如局部变量、返回地址等)压入栈中,形成一个栈帧。当函数执行完毕后,系统会从栈中弹出栈帧,恢复上一个函数的上下文继续执行。递归函数的调用也是基于栈的机制,每一次递归调用都会创建一个新的栈帧。
- 示例:计算阶乘的递归函数,每次递归调用时都会将当前的参数和返回地址压入栈中,直到达到递归终止条件,然后依次从栈中弹出栈帧进行计算。
def factorial(n):if n == 0 or n == 1:return 1else:return n * factorial(n-1)
2. 表达式求值
- 原理:在计算中缀表达式的值时,通常会先将中缀表达式转换为后缀表达式(逆波兰表达式),然后利用栈来计算后缀表达式的值。在转换过程中,栈用于处理运算符的优先级;在计算过程中,栈用于存储操作数。
- 示例:对于中缀表达式
3 + 4 * 2
,转换为后缀表达式3 4 2 * +
后,使用栈进行计算。遇到操作数时将其压入栈,遇到运算符时从栈中弹出相应数量的操作数进行计算,并将结果压入栈。
3. 浏览器的后退功能
- 原理:浏览器会将用户访问的网页地址依次压入栈中。当用户点击后退按钮时,浏览器会从栈中弹出最近访问的网页地址,并显示该网页。
- 示例:用户依次访问了网页 A、网页 B、网页 C,浏览器的栈中依次压入 A、B、C。当用户点击后退按钮时,栈中弹出 C,显示网页 B。
4. 编辑器的撤销操作
- 原理:编辑器会将用户的每一步操作(如输入、删除、修改等)依次压入栈中。当用户执行撤销操作时,编辑器会从栈中弹出最近的操作,并将其反向执行,以恢复到上一个状态。
- 示例:用户在文本编辑器中输入了 “Hello”,然后又输入了 “ World”,编辑器的栈中依次压入 “输入 Hello”、“输入 World”。当用户点击撤销按钮时,栈中弹出 “输入 World”,并将其反向执行,即删除 “ World”。
五,队列的示例代码
①队列的基本操作
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>#define MAX_SIZE 100// 定义队列结构体
typedef struct {int data[MAX_SIZE];int front;int rear;
} Queue;// 初始化队列
void initQueue(Queue* q) {q->front = 0;q->rear = 0;
}// 判断队列是否为空
bool isEmpty(Queue* q) {return q->front == q->rear;
}// 判断队列是否已满
bool isFull(Queue* q) {return (q->rear + 1) % MAX_SIZE == q->front;
}// 入队操作
bool enqueue(Queue* q, int value) {if (isFull(q)) {printf("队列已满,无法入队!\n");return false;}q->data[q->rear] = value;q->rear = (q->rear + 1) % MAX_SIZE;return true;
}// 出队操作
bool dequeue(Queue* q, int* value) {if (isEmpty(q)) {printf("队列为空,无法出队!\n");return false;}*value = q->data[q->front];q->front = (q->front + 1) % MAX_SIZE;return true;
}// 获取队头元素
bool peek(Queue* q, int* value) {if (isEmpty(q)) {printf("队列为空,无队头元素!\n");return false;}*value = q->data[q->front];return true;
}int main() {Queue q;initQueue(&q);enqueue(&q, 10);enqueue(&q, 20);enqueue(&q, 30);int value;if (peek(&q, &value)) {printf("队头元素是: %d\n", value);}if (dequeue(&q, &value)) {printf("出队元素是: %d\n", value);}return 0;
}
②打印队列调度
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>#define MAX_SIZE 100// 定义队列结构体
typedef struct {char data[MAX_SIZE][50]; // 假设任务名最长为 50 个字符int front;int rear;
} Queue;// 初始化队列
void initQueue(Queue* q) {q->front = 0;q->rear = 0;
}// 判断队列是否为空
bool isEmpty(Queue* q) {return q->front == q->rear;
}// 判断队列是否已满
bool isFull(Queue* q) {return (q->rear + 1) % MAX_SIZE == q->front;
}// 入队操作
bool enqueue(Queue* q, const char* task) {if (isFull(q)) {printf("队列已满,无法入队!\n");return false;}// 修改 strcpy_s 函数调用,添加目标缓冲区大小参数strcpy_s(q->data[q->rear], sizeof(q->data[q->rear]), task);q->rear = (q->rear + 1) % MAX_SIZE;printf("任务 %s 已加入打印队列。\n", task);return true;
}// 出队操作
bool dequeue(Queue* q, char* task) {if (isEmpty(q)) {printf("队列为空,无法出队!\n");return false;}// 修改 strcpy_s 函数调用,添加目标缓冲区大小参数strcpy_s(task, sizeof(task), q->data[q->front]);q->front = (q->front + 1) % MAX_SIZE;printf("正在打印任务 %s。\n", task);return true;
}// 获取队列大小
int get_queue_size(Queue* q) {return (q->rear - q->front + MAX_SIZE) % MAX_SIZE;
}int main() {Queue printer;initQueue(&printer);enqueue(&printer, "文档1");enqueue(&printer, "文档2");enqueue(&printer, "文档3");printf("当前打印队列中有 %d 个任务。\n", get_queue_size(&printer));char task[50];dequeue(&printer, task);dequeue(&printer, task);dequeue(&printer, task);dequeue(&printer, task);return 0;
}
③ 队列的其他应用场景
1. 任务调度
- 原理:在操作系统中,多个任务需要按顺序执行,队列可用来管理这些任务。新任务会被添加到队列尾部,调度器按顺序从队列头部取出任务执行,确保任务按到达顺序处理。
- 示例:在多用户的计算机系统里,多个用户提交打印任务,操作系统会将这些任务存入一个队列。打印机按队列中任务的先后顺序依次打印,避免多个任务同时竞争打印机资源。
2. 网络数据传输
- 原理:在网络通信中,数据通常以数据包的形式传输。发送方和接收方都可能使用队列来缓冲数据。发送方将待发送的数据包加入队列,网络接口按顺序从队列中取出数据包发送;接收方将接收到的数据包存入队列,上层应用程序按顺序从队列中读取数据包进行处理。
- 示例:在 TCP 协议中,接收方使用接收缓冲区(队列)来存储接收到的数据包。当上层应用程序准备好处理数据时,从队列头部取出数据包进行解析。
3. 广度优先搜索(BFS)
- 原理:BFS 是一种用于遍历或搜索树或图的算法,它从根节点(或起始节点)开始,逐层地访问节点。队列在 BFS 中用于存储待访问的节点。首先将起始节点加入队列,然后不断从队列头部取出节点进行访问,并将其未访问的邻接节点加入队列尾部。
- 示例:在地图导航中,使用 BFS 算法可以找到从起点到终点的最短路径。将起点加入队列,然后依次访问队列中节点的邻接节点,直到找到终点。
4. 消息队列系统
- 原理:消息队列系统用于在不同组件或服务之间传递消息。生产者将消息发送到队列中,消费者从队列中取出消息进行处理。队列确保消息按发送顺序被处理,同时实现了生产者和消费者之间的解耦。
- 示例:在分布式系统中,多个微服务之间需要进行通信。一个微服务产生的消息可以放入消息队列,其他微服务可以从队列中获取消息并进行相应的处理,例如电商系统中,订单服务产生订单消息,库存服务从消息队列中获取消息并更新库存。
现实生活领域
1. 排队系统
- 原理:在各种排队场景中,如银行、超市、餐厅等,人们按到达顺序排队等待服务。队列可以模拟这种排队机制,新顾客加入队列尾部,服务人员按顺序从队列头部为顾客提供服务。
- 示例:在银行办理业务时,顾客取号后进入排队队列,柜员按队列顺序依次为顾客办理业务。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>#define MAX_SIZE 100// 定义队列结构体
typedef struct {int customers[MAX_SIZE];int front;int rear;
} Queue;// 初始化队列
void initQueue(Queue *q) {q->front = 0;q->rear = 0;
}// 判断队列是否为空
bool isEmpty(Queue *q) {return q->front == q->rear;
}// 判断队列是否已满
bool isFull(Queue *q) {return (q->rear + 1) % MAX_SIZE == q->front;
}// 入队操作
bool enqueue(Queue *q, int customer) {if (isFull(q)) {printf("队列已满,无法入队!\n");return false;}q->customers[q->rear] = customer;q->rear = (q->rear + 1) % MAX_SIZE;printf("顾客 %d 已加入排队队列。\n", customer);return true;
}// 出队操作
bool dequeue(Queue *q, int *customer) {if (isEmpty(q)) {printf("队列为空,没有顾客等待服务!\n");return false;}*customer = q->customers[q->front];q->front = (q->front + 1) % MAX_SIZE;printf("正在为顾客 %d 提供服务。\n", *customer);return true;
}// 获取队列大小
int getQueueSize(Queue *q) {return (q->rear - q->front + MAX_SIZE) % MAX_SIZE;
}int main() {Queue queue;initQueue(&queue);// 顾客入队enqueue(&queue, 1);enqueue(&queue, 2);enqueue(&queue, 3);printf("当前排队队列中有 %d 位顾客。\n", getQueueSize(&queue));// 服务顾客int customer;dequeue(&queue, &customer);dequeue(&queue, &customer);dequeue(&queue, &customer);dequeue(&queue, &customer);return 0;
}
2. 电梯系统
- 原理:电梯系统中,乘客按下楼层按钮后,请求会被加入一个队列。电梯按队列中请求的顺序依次停靠相应楼层,确保乘客按请求顺序被服务。
- 示例:在一栋大楼中,多个乘客在不同楼层按下电梯按钮,电梯控制系统将这些请求存入队列,电梯根据队列中的请求依次到达各个楼层。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>#define MAX_SIZE 100
#define MIN_FLOOR 1
#define MAX_FLOOR 10// 定义队列结构体
typedef struct {int requests[MAX_SIZE];int front;int rear;
} Queue;// 初始化队列
void initQueue(Queue *q) {q->front = 0;q->rear = 0;
}// 判断队列是否为空
bool isEmpty(Queue *q) {return q->front == q->rear;
}// 判断队列是否已满
bool isFull(Queue *q) {return (q->rear + 1) % MAX_SIZE == q->front;
}// 入队操作
bool enqueue(Queue *q, int floor) {if (isFull(q)) {printf("请求队列已满,无法添加新请求!\n");return false;}if (floor < MIN_FLOOR || floor > MAX_FLOOR) {printf("无效的楼层请求:%d。楼层范围为 %d - %d。\n", floor, MIN_FLOOR, MAX_FLOOR);return false;}q->requests[q->rear] = floor;q->rear = (q->rear + 1) % MAX_SIZE;printf("已收到前往 %d 层的请求。\n", floor);return true;
}// 出队操作
bool dequeue(Queue *q, int *floor) {if (isEmpty(q)) {printf("请求队列为空,没有请求需要处理!\n");return false;}*floor = q->requests[q->front];q->front = (q->front + 1) % MAX_SIZE;printf("电梯正在前往 %d 层。\n", *floor);return true;
}// 获取队列大小
int getQueueSize(Queue *q) {return (q->rear - q->front + MAX_SIZE) % MAX_SIZE;
}int main() {Queue elevatorQueue;initQueue(&elevatorQueue);// 乘客发出请求enqueue(&elevatorQueue, 3);enqueue(&elevatorQueue, 7);enqueue(&elevatorQueue, 12); // 无效请求printf("当前电梯请求队列中有 %d 个请求。\n", getQueueSize(&elevatorQueue));// 电梯处理请求int floor;dequeue(&elevatorQueue, &floor);dequeue(&elevatorQueue, &floor);dequeue(&elevatorQueue, &floor);return 0;
}
相关文章:
数据结构之栈与队列
一,栈和队列的区别 1、核心定义与特性 特性栈(Stack)队列(Queue)定义仅允许在栈顶(表尾)进行插入和删除的线性表,遵循 后进先出(LIFO)。允许在队尾插入、队…...
SSHv2 密钥交换(Key Exchange)详解
1. 算法协商 在密钥交换开始前,客户端和服务端会协商确定本次会话使用的算法组合。具体过程如下: 交换算法列表 客户端和服务端各自发送支持的算法列表,包括: 密钥交换算法(如 diffie-hellman-group14-sha256…...
从零开始学习three.js(15):一文详解three.js中的纹理映射UV
1. UV 映射基础概念 1.1 什么是 UV 坐标? 在三维计算机图形学中,UV 坐标是将二维纹理映射到三维模型表面的坐标系统。UV 中的 U 和 V 分别代表2D纹理空间的水平(X)和垂直(Y)坐标轴,与三维空间…...
解锁 Postgres 扩展日!与瀚高共探 C/Java 跨语言扩展技术的边界与未来
2025 年 5 月 13 日至 16 日(蒙特利尔时间),一年一度的 PostgreSQL 开发者大会 PGConf.dev(原 PGCON 会议)将在加拿大蒙特利尔盛大举行。同去年一样,在本次大会开幕的前一天同样会举办另外一个专场活动——…...
【Hive入门】Hive增量数据导入:基于Sqoop的关系型数据库同步方案深度解析
目录 引言 1 增量数据导入概述 1.1 增量同步与全量同步对比 1.2 增量同步技术选型矩阵 2 Sqoop增量导入原理剖析 2.1 Sqoop架构设计 2.2 增量同步核心机制 3 Sqoop增量模式详解 3.1 append模式(基于自增ID) 3.2 lastmodified模式(基…...
✍️【TS类型体操进阶】挑战类型极限,成为类型魔法师!♂️✨
哈喽类型战士们!今天我们要玩转TS类型体操,让你的类型系统像体操运动员一样灵活优雅~ 学会这些绝招,保准你的代码类型稳如老狗!(文末附类型体操段位表)🚀 一、什么是类型体操? &…...
部署Prometheus+Grafana简介、监控及设置告警(一)
部署PrometheusGrafana简介、监控及设置告警 一. 环境准备 服务器类型IP地址组件 Prometheus服务器、agent服务器、Grafana服务器192.168.213.7Prometheus 、node_expprter,Grafanaagent服务器192.168.213.8node_exporter 如果有防火请记得开启9090&am…...
k8s部署OpenELB
k8s部署OpenELB k8s部署OpenELB配置示例: layer2模式 k8s部署OpenELB 部署OpenELB至K8s集群 # k8s部署OpenELB kubectl apply -f https://raw.githubusercontent.com/openelb/openelb/refs/heads/master/deploy/openelb.yaml# 查看openelb的pod状态 kubectl get pods -n open…...
python打卡day18
聚类后的分析:推断簇的类型 知识点回顾: 推断簇含义的2个思路:先选特征和后选特征通过可视化图形借助ai定义簇的含义科研逻辑闭环:通过精度判断特征工程价值 作业:参考示例代码对心脏病数据集采取类似操作,并且评估特征…...
新品发布 | 96MHz主频 M0+内核低功耗单片机CW32L011产品介绍
CW32L011是基于 eflash 的单芯片低功耗微控制器,集成了主频高达 96MHz的 ARMCortex-M0内核、高速嵌入式存储器(多至 64K字节 FLASH 和多至 6K 字节 SRAM)以及一系列全面的增强型外设和 I/O 口。 所有型号都提供全套的通信接口(3路 UART、1路 SPI和1路12C)、12位高速…...
【面试 · 二】JS个别重点整理
目录 数组方法 字符串方法 遍历 es6 构造函数及原型 原型链 this指向 修改 vue事件循环Event Loop FormData 数组方法 改变原数组:push、pop、shift、unshift、sort、splice、reverse不改变原属组:concat、join、map、forEach、filter、slice …...
【详细教程】ROC曲线的计算方式与绘制方法详细介绍
《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…...
【神经网络与深度学习】VAE 在解码前进行重参数化
在 VAE 中,解码之前进行重参数化主要有以下几个重要原因: 可微分性 在深度学习里,模型是通过反向传播算法来学习的,而这需要计算梯度。若直接从潜在变量的分布 (q_{\theta}(z|x))(由编码器输出的均值 (\mu) 和方差 (…...
ai agent(智能体)开发 python3基础11: java 调用python waitfor卡死,导致深入理解操作系统进程模型和IPC机制
java 调用python waitfor 卡死 导致浏览器无法自动关闭,java ,python双发无限等待 根源在于还是没有理解 进程之间标准输入输出到底是什么含义 系统进程与跨语言调用的核心机制 在跨语言调用(如Java调用Python)时,理…...
大模型赋能:2D 写实数字人开启实时交互新时代
在数字化浪潮席卷全球的当下,人工智能技术不断突破创新,其中大模型驱动的 2D 写实数字人正成为实时交互领域的一颗新星,引领着行业变革,为人们带来前所未有的交互体验。 一、2D 写实数字人概述 2D 写实数字人是通过计算机图形学…...
CATIA高效工作指南——零件建模篇(二)
一、PowerCopy特征复用技术 1.1 智能特征封装 通过几何图形集(Geometrical Set)构建参数化特征组,将关联的草图、曲面、实体等元素进行逻辑封装。操作流程如下: 创建新几何图形集并完成特征建模激活PowerCopy命令,选择目标几何集定…...
QT人工智能篇-opencv
第一章 认识opencv 1. 简单概述 OpenCV是一个跨平台的开源的计算机视觉库,主要用于实时图像处理和计算机视觉应用。它提供了丰富的函数和算法,用于图像和视频的采集、处理、分析和显示。OpenCV支持多种编程语言,包括C、Python、Java等&…...
java实现一个操作日志模块功能,怎么设计
为了设计一个高效、可靠且可扩展的操作日志模块,可以结合 AOP(面向切面编程)、异步处理(多线程或MQ)以及合理的存储策略,具体方案如下: 1. 技术选型与架构设计 (1) AOP 实现非侵…...
音频相关基础知识
主要参考: 音频基本概念_音频和音调的关系-CSDN博客 音频相关基础知识(采样率、位深度、通道数、PCM、AAC)_音频2通道和8ch的区别-CSDN博客 概述 声音的本质 声音的本质是波在介质中的传播现象,声波的本质是一种波,是一…...
【Agent】使用 Python 结合 OpenAI 的 API 实现一个支持 Function Call 的程序,修改本机的 txt 文件
使用 Python 结合 OpenAI 的 API 来实现一个支持 Function Call 的程序,修改本机的 txt 文件。需要注意,在运行代码前,要确保已经安装了 openai 库,并且拥有有效的 OpenAI API Key。 import openai import os# 设置你的 OpenAI A…...
mint系统详解详细解释
Linux Mint是一款基于Ubuntu的开源桌面操作系统,以用户友好、稳定性强、功能全面著称,尤其适合从Windows迁移的新手和追求高效办公的用户。以下从技术架构、版本演进、生态体系、核心功能、应用场景等维度进行深度解析: 一、技术架构&#x…...
WordPress个人博客搭建(三):WordPress网站优化
前言 在之前的WordPress个人博客搭建(一)与WordPress个人博客搭建(二)文章中,我们已经在自己的非凡云云服务器上成功搭建了WordPress个人博客。现在让我们继续这场数字世界的耕耘,通过插件与主题的巧妙搭配…...
力扣1812题解
记录 2025.5.7 题目: 思路: 从左下角开始,棋盘的行数和列数(均从 1 开始计数)之和如果为奇数,则为白色格子,如果和为偶数,则为黑色格子。 代码: class Solution {pu…...
深入理解XGBoost(何龙 著)学习笔记(三)
原创 化心为海 微阅读札记https://mp.weixin.qq.com/s/vBE3fu9AZDjRFd5niJU0lg 2025年05月06日 18:17 北京 第三章 机器学习算法基础 摘要:本章首先介绍了基础的机器学习算法的实现原理和应用;然后对决策树模型做了详细介绍;最后࿰…...
一篇文章解析 H.264/AVC 视频编解码标准框架
古人有云: “不积跬步, 无以至千里; 不积小流, 无以成江海。” 本文给小伙伴们删繁就简介绍 H.264/AVC 视频编解码标准框架。 H.264/AVC框架 H.264/AVC 作为视频编码领域的里程碑标准,仍然沿用混合编码框架,但其通过模块化技术创新显著提升了压缩效率和网络适应性。H.264/AV…...
Sat2Density论文详解——卫星-地面图像生成
“Sat2Density: Faithful Density Learning from Satellite-Ground Image Pairs”,即从卫星-地面图像对中学习忠实的密度表示。论文的主要目标是开发一种能够准确表示卫星图像三维几何结构的方法,特别关注从卫星图像中合成具有3D意识的地面视图图像的挑战…...
【计算机架构】RISC(精简指令集计算机)架构
一、引言 在计算机科学技术飞速发展的长河中,计算机架构犹如一艘艘领航的巨轮,不断引领着计算技术朝着更高性能、更低功耗、更智能化的方向前行。RISC(精简指令集计算机)架构便是其中一艘极为独特且极具影响力的“巨轮”。从早期计…...
智算中心基础设施0-1建设全流程及投产后的运维
0 - 1 建设全流程 规划与设计 需求分析:与相关部门和用户沟通,了解智算中心的业务需求,包括计算能力、存储容量、网络带宽、应用场景等,为后续的设计提供依据。选址规划:考虑电力供应、网络接入、环境条件、安全因素等…...
用3D slicer 去掉影像中的干扰体素而还原干净影像(脱敏切脸处理同)
今天遇到一个特殊的影像,扫描被试的头颅被很多干扰阴影快给遮盖住了,3D 建模出来的头颅有很多干扰,非常影响处理和视觉体验,正好解锁一个3D slicer 的功能吧。 背景:有一个被试数据头顶有很多干扰,直接覆盖…...
滚动条样式
title: 滚动条样式 date: 2025-05-07 19:59:31 tags:css 滚动条样式完整定义 HTML 示例 以下是一个包含所有主流浏览器滚动条样式属性的完整HTML文件,涵盖了WebKit内核浏览器和Firefox的滚动条定制: <!DOCTYPE html> <html lang"zh-CN&…...
Prompt(提示词)工程师,“跟AI聊天”
提示词工程师这活儿早就不只是“跟AI聊天”那么简单了,特别是现在MetaGPT、LangChain这些框架出来后,整个赛道都升级成“AI指挥官”的较量了。 第一层:基础能力得打牢 AI语言学家的功底 别笑,真得像学外语一样研究大模型。比如GP…...
Java版ERP管理系统源码(springboot+VUE+Uniapp)
ERP系统是企业资源计划(Enterprise Resource Planning)系统的缩写,它是一种集成的软件解决方案,用于协调和管理企业内各种关键业务流程和功能,如财务、供应链、生产、人力资源等。它的目标是帮助企业实现资源的高效利用…...
金融小知识
📉 一、“做空”是啥? 通俗说法:押“它会跌”,赚钱! ✅ 举个例子: 有一天老王的包子涨价到 10 块一个,张三觉得这价格肯定撑不住,未来会跌到 5 块。于是他: 向朋友借了…...
高组装导轨的特点
高组装导轨通常是四列式单圆弧齿形接触直线导轨,具有整合化的结构设计,适用于重负荷和精密应用。与其它直线导轨高组装导轨提升了负荷与刚性能力,具备四方向等负载特色和自动调心功能,能够吸收安装面的装配误差,达到高…...
PE文件结构(导入表)
导入表 什么是导入表? 导入表就是pe文件需要依赖哪些模块以及依赖这些模块中的哪些函数 回想我们导出表的内容,导出表的位置和大小是保存在扩展pe头最后一个结构体数组当中的 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]第…...
AI 实践探索:辅助生成测试用例
背景 目前我们的测试用例主要依赖人工生成和维护,AI时代的来临,我们也在思考“AI如何赋能业务”,提出了如下命题: “探索通过AI辅助生成测试用例,完成从需求到测试用例生成的穿刺”。 目标 找全测试路径辅助生成测…...
2025年链游行业DDoS与CC攻击防御全解析:高带宽时代的攻防博弈
2025年,链游行业在元宇宙与Web3.0技术的推动下迎来爆发式增长,但随之而来的DDoS与CC攻击也愈发猖獗。攻击者瞄准链游的高频交易接口、NFT拍卖系统及区块链节点,通过混合型攻击(如HTTP FloodUDP反射)瘫痪服务࿰…...
LeetCode热题100--73.矩阵置零--中等
1. 题目 给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1: 输入:matrix [[1,1,1],[1,0,1],[1,1,1]] 输出:[[1,0,1],[0,0,0],[1,0,1]] 示例 2ÿ…...
51camera将参加第九届沥青路面论坛暨新技术新成果展示会
51camera志强视觉 51camera即将参加第九届沥青路面论坛暨新技术新成果展示会,届时会有相关动态应用展示,欢迎广大客户朋友莅临参观。 会议时间:2025 年5月16日-18日 会议地点:长沙国际会议中心一层多功能厅1-6厅(长…...
python 闭包获取循环数据经典 bug
问题代码 def create_functions():functions []for i in range(3):# 创建一个函数,期望捕获当前循环的i值functions.append(lambda: print(f"My value is: {i}"))return functions# 创建三个函数 f0, f1, f2 create_functions()# 调用这些函数 f0() # 期望输出 &…...
Java的HashMap面试题
目录 1. 说一下HashMap的工作原理?(1.7和1.8都是) 2. 了解的哈希冲突解决方法有哪些 3. JAVA8的 HashMap做了哪些优化 4. HashMap的数组长度必须是 2 的 n 次方 5. HashMap什么时候引发扩容 5.1 数组容量小于64的情况: 5.2…...
spring4.x详解介绍
一、核心特性与架构改进 全面支持Java 8与Java EE 7 Spring 4.x首次实现对Java 8的完整支持,包括: Lambda表达式与Stream API:简化代码编写,提升函数式编程能力; 新的时间日期API(如LocalDate、LocalTime&…...
从图灵机到量子计算:逻辑可视化的终极进化
一、图灵机:离散符号系统的奠基者 1.1 计算理论的数学根基 1936 年,艾伦・图灵在《论可计算数及其在判定问题中的应用》中提出的图灵机模型,本质上是一个由七元组\( M (Q, \Sigma, \Gamma, \delta, q_0, q_{accept}, q_{reject}) \)构成的…...
Python初学者笔记第九期 -- (列表相关操作及列表编程练习题)
第17节课 列表相关操作 无论是内置函数、对象函数,用起来确实很方便,但是作为初学者,你必须懂得它们背后的运行逻辑! 1 常规操作 (1)遍历 arr [1,2,3,4] # 以索引遍历:可以在遍历期间修改元素 for ind…...
设备指纹破解企业面临的隐私与安全双重危机
在数字经济高速发展的今天,黑灰产攻击如影随形,个人隐私泄露、金融欺诈、电商刷单等风险事件频发。芯盾时代 “觅迹” 设备指纹全新升级,以跨渠道识别能力打破行业壁垒,为金融、电商、游戏等多场景构筑安全屏障。 黑灰产肆虐隐私…...
多功能气体检测报警系统,精准监测,守护安全
在化学品生产、石油化工、矿山、消防、环保、实验室等领域,有毒有害气体泄漏风险严重威胁工作人员和环境安全。化工企业生产中易产生大量可燃有毒气体,泄漏达一定浓度易引发爆炸、中毒等重大事故;矿井下瓦斯、一氧化碳等有害气体的浓度实时监…...
【HarmonyOS 5】鸿蒙中常见的标题栏布局方案
【HarmonyOS 5】鸿蒙中常见的标题栏布局方案 一、问题背景: 鸿蒙中常见的标题栏:矩形区域,左边是返回按钮,右边是问号帮助按钮,中间是标题文字。 那有几种布局方式,分别怎么布局呢?常见的思维…...
登顶中国:基于 Trae AI与 EdgeOne MCP 的全国各省最高峰攀登攻略博客构建实践
一、背景与目标 随着户外运动和登山活动的日益流行,越来越多的人希望挑战自然,体验登顶的乐趣。中国幅员辽阔,34个省级行政区(包括23个省、5个自治区、4个直辖市和2个特别行政区)拥有众多壮丽的山峰,其…...
iOS蓝牙技术实现及优化
以下是针对2025年iOS蓝牙技术实现的核心技术要点的深度解析,结合当前iOS 18(推测版本)的最新特性与开发实践,分模块结构化呈现: 一、硬件与协议层适配 BLE 5.3 支持 iOS 18默认支持蓝牙5.3协议,需注意&…...
STC单片机--仿真调试
目录 一、仿真介绍二、仿真步骤 一、仿真介绍 通常单片机的仿真有ST-Link、JTAG等,连接好线路之后,在keil的debug选项设置好就可以仿真了。但是,STC需要在STC-ISP软件上的仿真界面进行配置,然后才能在keil里正常仿真 二、仿真步骤…...