简介:这份数据结构期末作业资源面向计算机专业学生与C++初学者,围绕队列这一先进先出结构,实现带VIP优先级的银行排队模拟系统,帮助读者把课堂上的队列知识落到可运行的项目中。压缩包共9个文件、约1.03MB,以cpp源码、txt数据文件、cbp工程配置及exe可执行文件为主,另含编译中间产物与项目布局文件,构成一个可直接打开调试的Code::Blocks工程。已有3369人学习下载,说明其作为课程作业参考具有一定认可度。读者可从中获得双队列优先级调度的完整实现思路、用户信息文件的读取与解析方式、STL queue容器的实际用法,以及从源码到可执行程序的工程组织范例,便于对照理解控制流程与多队列设计,并在此基础上改写扩展。
1. 银行排队系统这道作业题,考的是你会不会用队列把业务流跑通
银行排队系统是数据结构期末作业里出现频率极高的一道综合题,表面看是"模拟叫号",实际考的是你对队列的完整掌握:顺序队列入队出队、循环队列判空判满、多窗口调度、以及队列和链表的取舍。很多人一上来就写界面,结果核心逻辑跑不通,分数直接腰斩。这类题通常占期末作业 20%~30% 的分值,评分点集中在"队列结构定义是否正确、入队出队边界是否处理、多窗口是否公平调度、统计指标是否算对"四块。适合正在赶期末作业、或者想把这题吃透拿去应对课程设计答辩的人。下面按考点分布、高频实现、真题演练、易错点、答题模板的顺序拆开讲,照着练就能把这道题从"能跑"做到"能拿高分"。
2. 考点分布:队列结构定义与基本操作是拿分底盘
2.1 顺序队列、循环队列、链队列怎么选
银行排队系统的底层就是一个队列,但队列有三种常见实现,选错实现方式会在判空判满上翻车。顺序队列用数组加两个指针,问题是"假溢出"——队头出队后前面空出来,队尾却顶到数组末尾,明明还有空间却报满。循环队列把数组首尾接起来,用(rear + 1) % MAXSIZE == front判满,牺牲一个存储单元换判满的简洁。链队列用带头结点的单链表,入队尾插、出队头删,不存在溢出问题,但每次出队要释放结点。
这道作业题里,如果题目明确要求"固定窗口数、顾客数已知",循环队列是标准答案;如果要求"顾客随时到达、数量不定",链队列更稳。面试官或评卷人最想看到的是你能说清为什么选它,而不是背定义。
| 实现方式 | 判空条件 | 判满条件 | 适用场景 |
|---|---|---|---|
| 顺序队列 | front == rear | rear == MAXSIZE | 不推荐,有假溢出 |
| 循环队列 | front == rear | (rear+1)%MAXSIZE == front | 顾客数有上限 |
| 链队列 | front == rear(带头结点) | 无需判满 | 顾客数不定 |
提示:循环队列判满牺牲一个单元是常规做法,如果题目要求"不浪费空间",可以加一个 count 计数器记录元素个数,判满改成
count == MAXSIZE,答辩时主动提这一点是加分项。
2.2 入队出队边界:三个必须写对的判断
基本操作看着简单,但边界处理是评分重灾区。入队前必须判满,出队前必须判空,取队头前必须判空。循环队列的入队是rear = (rear + 1) % MAXSIZE; data[rear] = x;,出队是front = (front + 1) % MAXSIZE; x = data[front];,顺序不能反。链队列出队时要判断"是不是最后一个结点",如果是,出队后要把 rear 重新指向头结点,否则 rear 变成野指针。
// 循环队列入队,返回 1 成功 0 失败 int enqueue(CircularQueue *q, Customer c) { if ((q->rear + 1) % MAXSIZE == q->front) { return 0; // 队满,拒绝入队 } q->rear = (q->rear + 1) % MAXSIZE; q->data[q->rear] = c; // 先移指针再存数据 return 1; } // 循环队列出队 int dequeue(CircularQueue *q, Customer *c) { if (q->front == q->rear) { return 0; // 队空,无法出队 } q->front = (q->front + 1) % MAXSIZE; *c = q->data[q->front]; // 先移指针再取数据 return 1; }这段代码的关键在"先移指针再存取"的顺序,和普通数组的data[rear++] = x不一样。参数MAXSIZE是队列容量,实际可用容量是MAXSIZE - 1。如果入队返回 0,调用方要处理"顾客被拒"的情况,很多作业直接忽略返回值,导致队满时数据被覆盖,这是典型的失分点。
2.3 多窗口调度:队列怎么分配给窗口
银行排队系统的核心难点不是单个队列,而是"多个窗口怎么从队列里取顾客"。常见有两种模型:一是所有顾客排一个大队,哪个窗口空了就从队头取一个,这叫单队列多服务台,公平性最好;二是每个窗口各有一个队列,顾客进队时选最短的队,这叫多队列多服务台,实现简单但可能出现"某队很长、某窗口却闲着"的不均衡。
期末作业里,如果题目写"顾客取号后排成一队,叫号系统按窗口空闲分配",就是单队列模型,用一个队列加一个窗口状态数组即可。如果写"顾客自行选择窗口排队",就是多队列模型,需要数组的队列。评卷时看的是你有没有把"窗口空闲判断"和"出队时机"写对——窗口服务时间到了才出队,而不是每个时间单位都出队。
// 单队列多窗口:每个时间单位检查各窗口是否空闲 for (int t = 0; t < TOTAL_TIME; t++) { for (int w = 0; w < WINDOW_NUM; w++) { if (windows[w].busy_time > 0) { windows[w].busy_time--; // 窗口正在服务,时间递减 } if (windows[w].busy_time == 0 && !is_empty(&queue)) { Customer c; dequeue(&queue, &c); // 窗口空闲,叫下一个 windows[w].busy_time = c.service_time; windows[w].served_count++; // 统计该窗口服务人数 } } }这段循环是整个模拟的骨架。busy_time记录窗口剩余服务时间,减到 0 表示空闲,此时才从队列取人。served_count用于最后统计各窗口工作量。常见错误是把出队写在时间递减之前,导致窗口还没空就又叫了一个人,服务时间被覆盖。
3. 高频考点:时间模拟、统计指标与链表应用
3.1 离散时间模拟:顾客到达和服务怎么推进
银行排队系统本质是一个离散事件模拟,时间以"分钟"或"时间单位"为步长推进。每个时间单位要做三件事:新顾客按到达概率或到达表入队、各窗口服务时间递减、空闲窗口出队叫号。顾客的到达可以按固定间隔(每 2 分钟来一个),也可以按随机数(用rand()判断是否到达),题目一般会给定到达规则。
这里的高频考点是"到达和服务的时间关系"。如果顾客到达时所有窗口都忙,他必须留在队列里等待,等待时间 = 开始服务时间 - 到达时间。如果队列也满了(循环队列容量有限),顾客被拒,要统计拒绝人数。很多作业只算了服务时间,漏了等待时间,而等待时间恰恰是评价系统性能的核心指标。
// 顾客结构体:记录到达时间、服务时长、等待时长 typedef struct { int arrive_time; // 到达时刻 int service_time; // 需要服务时长 int wait_time; // 排队等待时长 } Customer; // 出队叫号时计算等待时间 c.wait_time = current_time - c.arrive_time; total_wait += c.wait_time;wait_time在出队那一刻才能算,因为只有开始服务才知道等了多久。total_wait累加后除以服务总人数就是平均等待时间。这个指标几乎每份作业都要输出,算错直接丢分。
3.2 统计指标:平均等待、最大队长、窗口利用率
期末作业的评分表里通常有一栏"结果分析",要求输出至少三个统计量:平均等待时间、最大队列长度、各窗口服务人数或利用率。平均等待时间 = 总等待时间 / 总服务人数;最大队列长度在每次入队后更新max_len = max(max_len, queue_length);窗口利用率 = 窗口忙碌时间 / 总模拟时间。
这些指标不难,难在"统计时机"。最大队长要在入队之后立刻比较,而不是模拟结束才看;窗口忙碌时间要在服务期间累加,而不是只数服务人数。面试官问"你的系统性能怎么样",答的就是这几个数,所以作业里把它们算准,答辩时才有话可说。
3.3 链表在排队系统里的真实用途
除了队列本身,链表在这道题里还有两个常见用途:一是顾客记录链表,把所有服务过的顾客串起来,方便最后遍历输出每个人的到达、等待、离开时间;二是事件链表,按时间顺序存储"到达事件"和"离开事件",做真正的离散事件模拟(事件驱动法)。
事件驱动法是高分做法:不按固定时间步长循环,而是每次取出最早发生的事件处理,时间直接跳到该事件时刻。这样模拟效率高,也更能体现你对"优先队列/有序链表"的理解。如果作业允许自由发挥,用有序链表存事件、每次取最小时间事件,是拉开差距的地方。
// 事件结点:按发生时间有序插入链表 typedef struct EventNode { int time; // 事件发生时刻 int type; // 0 到达 1 离开 struct EventNode *next; } EventNode; // 有序插入:保证链表按 time 升序 void insert_event(EventNode **head, EventNode *e) { while (*head && (*head)->time <= e->time) { head = &(*head)->next; } e->next = *head; *head = e; }有序插入的关键是二级指针head的移动,它让插入点自然落在第一个时间更大的结点之前。这样每次取事件只需取链表头,时间复杂度 O(1),插入 O(n)。参数type区分到达和离开,处理到达事件时生成新的离开事件插入链表,形成事件循环。
4. 真题演练:三道典型作业题的标准答法
4.1 题目一:用循环队列实现单窗口叫号
题目描述:银行只有一个窗口,顾客按到达顺序排队,实现入队、出队、查询队头、统计队列长度四个操作,队列容量为 10。
答题思路:先定义循环队列结构体,包含data数组、front、rear。四个操作分别对应enqueue、dequeue、get_front、get_length。get_length用(rear - front + MAXSIZE) % MAXSIZE计算,这是循环队列长度公式,很多人写成rear - front导致负数。
int get_length(CircularQueue *q) { return (q->rear - q->front + MAXSIZE) % MAXSIZE; }加MAXSIZE是为了处理 rear 在 front 前面的情况,取模后得到正确长度。这道题面试官最想听的是你能解释"为什么长度不能直接相减",答出假溢出和指针回绕就到位了。
4.2 题目二:多窗口模拟并输出平均等待时间
题目描述:3 个窗口,顾客按每 2 分钟到达一个,服务时间随机 1~5 分钟,模拟 60 分钟,输出平均等待时间和各窗口服务人数。
答题思路:用单队列存顾客,窗口数组存忙碌状态。主循环按分钟推进,每到偶数分钟生成一个顾客入队,然后遍历窗口递减服务时间、空闲则出队。出队时算等待时间累加。最后输出total_wait / served_total和每个窗口的served_count。
这道题的坑在"模拟结束队列里还有人的情况"。60 分钟到了,队列里可能还有没服务完的顾客,严格来说要继续模拟直到队列清空,否则平均等待时间偏小。答题时主动说明"模拟时间结束后继续处理剩余顾客",是体现严谨性的细节。
4.3 题目三:用链队列实现不限人数的排队
题目描述:顾客人数不定,要求排队系统不设上限,实现入队、出队,并支持遍历输出所有顾客信息。
答题思路:用带头结点的链队列,front指向头结点,rear指向最后一个结点。入队尾插并更新 rear,出队头删,若删的是最后一个结点则 rear 回指头结点。遍历从头结点的下一个开始。
// 链队列出队,注意最后一个结点的处理 int dequeue(LinkQueue *q, Customer *c) { if (q->front == q->rear) return 0; // 空队列 QueueNode *p = q->front->next; *c = p->data; q->front->next = p->next; if (q->rear == p) { // 删的是最后一个结点 q->rear = q->front; // rear 回指头结点 } free(p); return 1; }if (q->rear == p)这个判断是链队列最容易漏的一行,漏了之后 rear 指向已释放内存,下次入队就崩。面试官看到这行就知道你真写过链队列,不是抄的。
5. 易错点:五个让作业从 90 掉到 60 的坑
5.1 循环队列判满条件写错
错在哪:把判满写成rear == front,和判空条件一样,队列永远显示满或永远显示空。为什么错:循环队列里 front 和 rear 相等既可能是空也可能是满,必须用"牺牲一个单元"或"加计数器"区分。怎么答对:判空front == rear,判满(rear + 1) % MAXSIZE == front,并在注释里写明"牺牲一个单元"。
5.2 出队前不判空导致越界
错在哪:直接front = (front + 1) % MAXSIZE然后取数据,队列空时取到脏数据或越界。为什么错:空队列时 front 和 rear 相等,移动 front 后指向的是无效位置。怎么答对:出队函数第一行判空,返回失败码,调用方检查返回值再使用数据。
5.3 多窗口模拟出队时机错误
错在哪:每个时间单位对每个窗口都执行一次出队,不管窗口是否在忙。为什么错:窗口服务没结束就叫下一个人,前一个顾客的服务时间被覆盖,统计全乱。怎么答对:先递减busy_time,减到 0 才出队,出队后立刻设置新的busy_time。
5.4 平均等待时间分母用错
错在哪:用总人数做分母,包括被拒绝的顾客。为什么错:被拒顾客没有等待时间,算进去会拉低平均值。怎么答对:分母用"实际服务人数",被拒人数单独统计输出。
5.5 链队列出队不处理尾指针
错在哪:出队只改front->next,不管 rear。为什么错:删到最后一个结点时 rear 变成悬空指针,后续入队写入已释放内存。怎么答对:出队后判断rear == 被删结点,成立则rear = front。
6. 答题模板:从结构定义到结果分析的固定套路
6.1 代码结构按"定义—初始化—操作—模拟—输出"五段写
期末作业评分很看重结构清晰,建议固定五段式:第一段定义结构体(Customer、Queue、Window),第二段写初始化函数,第三段写队列基本操作,第四段写模拟主循环,第五段写统计输出。每段之间用注释分隔,评卷人一眼能找到得分点。主函数只负责调用,不写业务逻辑,这样即使某段有 bug,其他段的分数也能保住。
6.2 答辩时主动讲三个"为什么"
老师或面试官问得最多的不是"你怎么写的",而是"你为什么这么写"。提前准备三个答案:为什么选循环队列而不是链队列(固定容量、判满简单、缓存友好);为什么用单队列多窗口而不是多队列(公平、不会出现窗口闲置);为什么等待时间在出队时算(只有开始服务才知道等了多久)。这三个问题答顺了,答辩基本稳。
6.3 结果分析用表格呈现
最后输出别只打印几行数字,用表格把"总人数、服务人数、拒绝人数、平均等待、最大队长、各窗口服务数"列出来,再配一句结论,比如"窗口 2 服务人数最多,说明分配基本均衡"。评卷人看到有分析、有结论,印象分直接上去。
| 指标 | 数值 | 说明 |
|---|---|---|
| 总到达人数 | 30 | 60 分钟内每 2 分钟一个 |
| 实际服务人数 | 28 | 2 人被拒 |
| 平均等待时间 | 3.5 分钟 | 总等待 / 服务人数 |
| 最大队列长度 | 6 | 入队后实时更新 |
| 窗口 1/2/3 服务数 | 9/10/9 | 分配较均衡 |
6.4 押题方向:事件驱动模拟和优先队列
如果这份作业还有拓展题或课程设计答辩,大概率会问"如果顾客有优先级(VIP 优先)怎么改"。答案是把普通队列换成优先队列,用堆或有序链表实现,按优先级出队。再进一步问"如果窗口服务时间不同怎么调度",答"按最早空闲窗口分配",本质是维护一个窗口空闲时间的最小堆。这两个方向提前想清楚,答辩时被追问也不慌。
我当年做这道作业,第一版循环队列判满写错,模拟跑出来队列永远是满的,调了一晚上才定位到那一行取模。从那以后我每次写循环队列,都先把判空判满两个条件写在纸上对照一遍再敲代码。希望这份拆解帮到你,把这道题的分稳稳拿住。
本文还有配套的精品资源,点击获取