从零实现银行排队模拟器:用C语言玩转队列数据结构
银行大厅里此起彼伏的叫号声,窗口前井然有序的队伍——这些日常场景背后隐藏着计算机科学中一个基础而重要的数据结构:队列。本文将带你用C语言打造一个完整的银行排队模拟器,不仅解决PTA题目,更深入理解队列在实际系统中的应用。
1. 项目规划与设计思路
在开始编码前,我们需要明确模拟器的核心功能和架构。银行排队系统的本质是多个服务窗口以不同速率处理顾客请求,这正是队列"先进先出"特性的典型应用场景。
系统基本规则:
- A窗口处理速度是B窗口的2倍(A每处理2人,B处理1人)
- 奇数编号顾客前往A窗口,偶数编号前往B窗口
- 同时完成时A窗口顾客优先输出
- 不考虑顾客到达的时间间隔
为什么选择数组实现队列?对于这个规模的题目,数组足够高效且实现简单。后续我们可以探讨链表实现的优化空间。
2. 核心数据结构实现
我们先构建最基础的队列结构。虽然可以直接操作数组,但封装成队列结构能让代码更清晰。
#define MAX_SIZE 1000 typedef struct { int data[MAX_SIZE]; int front; // 队首指针 int rear; // 队尾指针 } Queue; void initQueue(Queue *q) { q->front = 0; q->rear = 0; } int isFull(Queue *q) { return (q->rear + 1) % MAX_SIZE == q->front; } int isEmpty(Queue *q) { return q->front == q->rear; } void enqueue(Queue *q, int item) { if (!isFull(q)) { q->data[q->rear] = item; q->rear = (q->rear + 1) % MAX_SIZE; } } int dequeue(Queue *q) { if (!isEmpty(q)) { int item = q->data[q->front]; q->front = (q->front + 1) % MAX_SIZE; return item; } return -1; // 表示队列为空 }3. 业务逻辑实现
有了队列结构,我们来实现银行的具体业务逻辑。注意处理不同窗口的处理速度差异和输出顺序规则。
void simulateBankService(Queue *qA, Queue *qB, int output[], int *outputIndex) { int count = 0; while (!isEmpty(qA) || !isEmpty(qB)) { count++; // A窗口处理规则 if (!isEmpty(qA) && (count % 3 != 0 || isEmpty(qB))) { output[(*outputIndex)++] = dequeue(qA); } // B窗口处理规则 else if (!isEmpty(qB)) { output[(*outputIndex)++] = dequeue(qB); } } }提示:这里的count计数器是关键,它决定了何时该从B窗口出队。每3次循环中,第3次从B出队,其余从A出队。
4. 完整系统实现与交互
现在我们将所有部分组合起来,创建一个完整的、可交互的模拟器。
#include <stdio.h> #include <stdlib.h> #include <time.h> // 前面定义的Queue结构和函数... int main() { Queue qA, qB; initQueue(&qA); initQueue(&qB); int N; printf("请输入顾客数量: "); scanf("%d", &N); printf("请输入%d个顾客编号(空格分隔): ", N); for (int i = 0; i < N; i++) { int num; scanf("%d", &num); if (num % 2 != 0) { enqueue(&qA, num); } else { enqueue(&qB, num); } } int output[MAX_SIZE]; int outputIndex = 0; simulateBankService(&qA, &qB, output, &outputIndex); printf("\n业务处理顺序: "); for (int i = 0; i < outputIndex; i++) { printf("%d", output[i]); if (i != outputIndex - 1) { printf(" "); } } printf("\n"); return 0; }可视化增强技巧:
- 添加
system("clear")或system("cls")清屏 - 使用
usleep或Sleep函数控制输出节奏 - 添加颜色区分不同窗口的输出
5. 测试与调试策略
确保模拟器正确性的关键在于全面的测试用例设计。考虑以下测试场景:
| 测试类型 | 输入样例 | 预期输出 | 验证点 |
|---|---|---|---|
| 常规情况 | 8 2 1 3 9 4 11 13 15 | 1 3 2 9 11 4 13 15 | 基本规则验证 |
| 只有A窗口 | 5 1 3 5 7 9 | 1 3 5 7 9 | 单一队列处理 |
| 只有B窗口 | 4 2 4 6 8 | 2 4 6 8 | 单一队列处理 |
| 交替极端 | 3 1 2 3 | 1 3 2 | 边界条件验证 |
调试时可以添加打印语句观察队列状态:
void printQueue(Queue *q, const char *name) { printf("%s队列: [", name); for (int i = q->front; i != q->rear; i = (i + 1) % MAX_SIZE) { printf("%d ", q->data[i]); } printf("]\n"); }6. 性能优化与扩展思路
虽然当前实现已经满足题目要求,但我们可以考虑更多优化和扩展方向:
内存优化:
- 使用动态数组替代固定大小数组
- 实现循环队列避免空间浪费
功能扩展:
- 添加顾客到达时间间隔模拟
- 实现可视化界面(使用ncurses库)
- 支持多窗口配置(3个或更多窗口)
- 添加服务时间统计功能
// 扩展的顾客结构体 typedef struct { int id; int arrivalTime; int serviceTime; } Customer; // 多窗口配置示例 typedef struct { Queue *queues; int count; int *serviceRates; // 各窗口服务速率 } WindowConfig;7. 教学价值与实际应用
这个项目虽然简单,但涵盖了数据结构学习的多个关键点:
- 队列的基本操作:入队、出队、判空、判满
- 多队列协同工作:处理不同速率的服务窗口
- 业务规则实现:将实际问题转化为程序逻辑
- 调试技巧:如何验证队列操作的正确性
在实际系统中,类似的队列管理应用于:
- 操作系统进程调度
- 网络数据包传输
- 打印机任务管理
- 客服中心呼叫分配
在实现银行模拟器的过程中,最容易被忽视的是队列指针的循环处理。记得测试队列满和空时的边界条件,这是面试中常见的考察点。