ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C语言银行排队系统实验报告:三窗口链式队列与平均逗留时间计算

C语言银行排队系统实验报告:三窗口链式队列与平均逗留时间计算 简介这份银行排队系统实验报告面向计算机专业学生及数据结构初学者以C语言编程实践为核心帮助读者将队列、链表等数据结构知识应用于模拟真实银行排队场景。资源包内含1个doc文档约191KB完整呈现实验的设计要求、实验目的、仪器与编译环境、设计思路及程序分析并附有主函数、VIP认证、客户到达与离开等关键模块的源程序与运行界面说明。读者可从中获取一套可参考的实验报告框架理解如何用SWITCH语句构建主菜单、用时间函数模拟客户到达与离开、用队列记录各窗口排队情况并计算客户平均逗留时间同时掌握头文件引用、随机事件生成与结构化编程的排错思路。目前已有901人学习适合需要完成课程实验、巩固C语言与数据结构综合应用能力的读者借鉴。1. 银行排队系统实验报告三窗口链式队列怎么把平均逗留时间算准很多人拿到“银行排队系统实验报告”这个题目第一反应是写个菜单、敲几个printf、把客户编号塞进数组就交差。但真正跑起来才发现客户一多窗口状态和排队队列就乱了平均逗留时间更是算得一塌糊涂。这份实验报告的核心其实是用 C 语言把“n 个窗口 一条等候队列”的离散事件模拟做扎实客户到达时先看窗口有没有空位有空位直接进线性表办理没空位才进链式队列等候客户离开时从队列里补位同时累计时间。它适合正在做数据结构课程设计的学生也适合想用 C 语言练手队列、链表、时间函数的开发者。下面我按“能编译、能跑通、能算对”的顺序把这份报告拆成可复现的步骤。2. 三窗口模型怎么落地线性表存窗口链式队列存等候2.1 为什么窗口用数组、排队用链表这份实验报告里窗口数量n被定义为 3窗口状态用一个结构体数组L.A[n1]表示下标 1 到 3 对应三个柜台元素值为 0 表示空闲非 0 表示正在办理的客户编号。等候队列则用带头结点的链式队列Linkqueuefront指向头结点rear指向队尾。这样选型的原因很直接窗口数量固定数组随机访问快L.A[i]直接定位柜台排队人数不确定链表动态申请内存不会像数组那样预设上限后溢出。常见做法是窗口数组从下标 1 开始用下标 0 空着不用避免“第 0 号窗口”这种反直觉的编号。结构体定义如下注意Lnode在原始代码里写成了Lnode *next在 C 语言里需要加struct关键字或者提前typedef否则 VC 6.0 会报错。#define n 3 struct List { int A[n 1]; // 下标1~3对应三个窗口0表示空闲 int len; // 当前正在办理业务的客户数 } L; struct Lnode { int data; // 客户编号 struct Lnode *next; // 下一个等候客户 }; struct Linkqueue { struct Lnode *front; // 队头指针指向头结点 struct Lnode *rear; // 队尾指针 } Q;参数说明n是窗口数改大改小都会影响L.A的有效下标范围L.len不是数组长度而是“当前占用窗口数”它和L.A里非零元素的个数必须保持一致否则查询界面会显示错乱。Q.front和Q.rear在初始化时都指向同一个头结点头结点不存数据只用来简化出队判断。2.2 初始化把窗口清空、把队列头结点建好初始化分两步Initshuzu()负责窗口数组Initqueue()负责链式队列。原始代码里Initshuzu()把L.A[1]到L.A[n]全部置 0L.len置 0Initqueue()用malloc申请一个头结点front和rear都指向它next置NULL。void Initshuzu() { for (int i 1; i n; i) { L.A[i] 0; // 0表示该窗口空闲 } L.len 0; } void Initqueue() { Q.front Q.rear (struct Lnode *)malloc(sizeof(struct Lnode)); Q.front-next NULL; }逻辑说明Initshuzu()必须在Initqueue()之前或之后调用都可以但两者都必须在main()进入菜单循环之前调用一次。如果忘记调用Initqueue()Q.front是野指针第一次Enqueue就会崩。参数上L.A的有效下标是 1 到n循环从 1 开始不要写成 0。2.3 客户到达先判断窗口再决定入队还是入数组到达事件的核心逻辑在daoda(int x)里。客户编号x由主函数里的计数器v生成每来一个普通客户就v然后调用daoda(v)。daoda先看L.len n是否成立成立说明还有空窗口把客户编号写入L.A[L.len 1]然后L.len不成立说明三个窗口都满了调用Enqueue(Q, x)把客户挂到链式队列尾部。void daoda(int x) { int i L.len 1; if (L.len n) { L.A[i] x; i; L.len; } else { Enqueue(Q, x); } } void Enqueue(struct Linkqueue *Q, int elem) { struct Lnode *s; s (struct Lnode *)malloc(sizeof(struct Lnode)); s-data elem; s-next NULL; Q-rear-next s; Q-rear s; }逻辑说明L.len 1就是下一个空闲窗口的下标因为L.len同时表示“已占用窗口数”当L.len为 0 时第一个客户进L.A[1]当L.len为 2 时第三个客户进L.A[3]。Enqueue是标准链式队列入队新结点挂在rear后面然后更新rear。参数上elem是客户编号必须是非零整数因为 0 被保留表示窗口空闲。2.4 客户离开出队补位与时间累计的联动离开事件由likai(int x)处理x是用户输入的离开客户编号。函数先检查x L.len是否成立成立就提示重新输入否则遍历L.A找到L.A[i] x的位置把该位置置 0L.len--然后判断队列是否非空如果Q.front ! Q.rear调用Dlqueue(Q)从队头取出一个等候客户编号y把y写回L.A[i]L.len。这样窗口不会空着等候队列也前进了一位。int Dlqueue(struct Linkqueue *Q) { struct Lnode *t; int x; if (Q-front Q-rear) { printf(队列为空\n); exit(1); } else { t Q-front-next; Q-front-next t-next; x t-data; free(t); return x; } } void likai(int x) { int i 0; do { if (x L.len) { printf(输入有误\n请重新输入); scanf(%d, x); } else { for (i 0; i L.len; i) { if (i x) { printf(尊敬的%d号顾客您好\n, x); L.A[i] 0; L.len--; if (Q.front ! Q.rear) { int y Dlqueue(Q); L.A[i] y; L.len; } } } } } while (i 0); }逻辑说明Dlqueue是标准链式队列出队取出头结点后面的第一个数据结点释放内存返回数据。likai里的for循环条件i L.len在原始代码里存在边界问题——如果L.len在循环中变化可能漏掉或越界。更稳妥的写法是先用一个临时变量保存L.len或者用while配合标志位。参数上x是客户编号不是窗口号用户输入时必须和到达时分配的编号一致。3. 时间统计与评分模块平均逗留时间怎么算才不翻车3.1 时间函数difftime 与 time_t 的正确用法原始代码里time1()函数写得很含糊time_t timep; time(timep); time difftime(end, start);这行甚至把函数名time当变量用在 VC 6.0 里会报错。正确的做法是在客户到达时记录start在客户离开时记录end然后用difftime(end, start)得到秒数累加到allsum。全局变量start和end声明为time_t类型allsum声明为float或double。#include time.h time_t start, end; float allsum 0, average 0; int w 0; // 已完成业务的普通客户数 // 客户到达时 time(start); // 客户离开时 time(end); allsum difftime(end, start); w; average allsum / w;逻辑说明time(start)把当前日历时间写入startdifftime(end, start)返回两个时间点之间的秒数类型是double。allsum累加所有普通客户的逗留时间w是完成业务的普通客户数average就是平均逗留时间。注意w不能包括 VIP 客户因为 VIP 不走排队流程原始代码里vip1单独计数。3.2 评分模块柜台号与分数的边界检查mygrade()函数在客户离开时调用先让用户输入柜台号z再输入评分y然后用switch(z)把评分累加到对应柜台的sum1到sum5并更新计数器i1到i5最后算平均分ave1到ave5。原始代码里guitai()检查柜台号时写的是if(y1||y5)但实际只有 3 个柜台应该改成if(y1||y3)否则用户输入 4 或 5 会进入不存在的柜台分支。int guitai() { int y 0; printf(请输入你所办理业务的柜台号1-3\n); scanf(%d, y); if (y 1 || y 3) { printf(你输入的柜台号有误请重新输入\n); printf(请输入你所办理业务的柜台号1-3\n); scanf(%d, y); } else { printf(你所办理业务的柜台为%d.\n, y); } return y; }逻辑说明guitai()的返回值z直接用于switch所以必须保证z在 1 到 3 之间。pingfeng()检查评分时范围是 1 到 5这个没问题。参数上sum1到sum5是浮点累加器i1到i5是整数计数器除法之前要确保计数器不为 0否则会出现除零错误。3.3 系统查询隐藏菜单与密码验证系统查询功能在主菜单里不显示但输入 6 可以进入。原始代码用char cool[3]存密码循环while(cool[k-1] ! )读取字符并打印星号然后判断卡号i 000且cool[0]到cool[3]都是1。这里有两个问题cool数组只有 3 个元素却访问了cool[3]越界密码判断只检查了前四位第五位没检查。更稳妥的做法是把cool定义为char cool[6]循环读取 5 个字符然后逐位比较。char cool[6]; int i 0, k 0; printf(请输入你的卡号); scanf(%d, i); printf(请您输入五位密码); while (k 5) { cool[k] getch(); k; printf(*); } cool[5] \0; if (i 000 cool[0] 1 cool[1] 1 cool[2] 1 cool[3] 1 cool[4] 1) { average allsum / w; printf(\n至今已有 %d 位普通用户%d 位vip用户成功办理业务!, w, vip1); printf(\n普通用户业务办理总共用时 %0.2f,平均用时 %0.2f\n, allsum, average); }逻辑说明getch()不回显字符所以手动printf(*)模拟密码输入。cool[5] \0保证字符串结束。参数上i 000在 C 语言里就是i 0前导零没有特殊含义但写成000可读性更好。average allsum / w之前要确保w 0否则除零。4. 避坑与排查VC 6.0 下跑通这份代码的五个血泪经验4.1 现象编译报错Lnode未定义原因结构体指针缺少struct关键字VC 6.0 对 C 语言标准支持较老struct Lnode { int data; Lnode *next; };里的Lnode *next会被认为是一个新的类型名而不是struct Lnode。解决方法是写成struct Lnode *next;或者在文件开头加typedef struct Lnode Lnode;。我一般会在定义结构体时直接写struct Lnode *next;避免后续所有地方都要加struct。4.2 现象客户离开后窗口显示 0但队列里的客户没有补位原因likai里for循环边界错误原始代码for(i0;iL.len;i)在L.len递减后可能提前退出导致L.A[i]置 0 后没有执行补位逻辑。解决方法是把查找和补位分开先遍历找到L.A[i] x的下标i记录在一个临时变量里退出循环后再做L.A[i] 0; L.len--;和出队补位。这样不受L.len变化影响。4.3 现象平均逗留时间显示为-nan或极大值原因w为 0 时做了除法如果用户一次都没完成离开操作就直接查系统w还是 0allsum / w就是除零。解决方法是在计算average之前加判断if (w 0) average allsum / w; else average 0;。另外allsum和average声明为float打印用%0.2f不要用%d。4.4 现象VIP 验证输入密码时程序卡死原因while(ch[k-1] ! )条件永远为真原始代码用空格字符 作为密码结束标志但用户输入时不会输入空格getch()读到的都是数字字符ch[k-1]永远不等于 循环无限执行直到数组越界。解决方法是改成固定次数循环while (k 5)读满 5 个字符就退出。如果非要支持可变长度可以用回车符\r作为结束标志。4.5 现象system(cls)在 VC 6.0 里不生效或闪屏原因头文件缺失或控制台缓冲区问题system(cls)需要#include stdlib.h原始代码已经包含。如果仍然闪屏可能是getch()没有配对使用清屏后没有等待用户按键就直接进入下一轮循环。解决方法是每次system(cls)之前确保有getch()或scanf暂停或者在main循环末尾加getch()。另外 VC 6.0 的控制台对cls支持正常不需要额外配置。5. 进阶技巧把手工输入改成随机事件流让实验报告更有说服力原始代码靠用户手动输入“1”和“2”来模拟到达和离开跑一次只能产生几个客户平均逗留时间的样本量太小实验报告里的数据看起来像玩具。我一般会加一个“自动模拟”模式用rand()生成到达间隔和业务办理时长循环 1000 次最后输出平均逗留时间、各柜台评分和队列最大长度。这样实验报告里的表格才有统计意义。#include stdlib.h #include time.h void auto_simulate(int total_customers) { int served 0; float total_wait 0; time_t sim_start, sim_end; time(sim_start); for (int i 1; i total_customers; i) { // 模拟到达直接调用 daoda daoda(i); // 模拟离开每隔 3 个客户离开一个 if (i % 3 0 L.len 0) { int leave_id L.A[1]; // 简化离开第一个窗口的客户 likai(leave_id); served; } } time(sim_end); total_wait difftime(sim_end, sim_start); printf(自动模拟完成共 %d 位客户%d 位完成业务总耗时 %.2f 秒\n, total_customers, served, total_wait); if (served 0) { printf(平均每位客户逗留时间%.2f 秒\n, total_wait / served); } }逻辑说明auto_simulate用循环批量生成客户daoda(i)负责到达i % 3 0时触发一次离开likai负责出队补位。served统计完成业务的客户数total_wait是模拟总耗时两者相除得到平均逗留时间。参数上total_customers建议设为 100 到 1000太小看不出统计规律太大在 VC 6.0 里跑得慢。常见做法是再加一个Sleep(10)模拟业务办理耗时但Sleep需要#include windows.h而且会拖慢模拟速度我一般只在演示时加。验证方法跑完自动模拟后手动进入“排队查询”界面看队列里是否还有残留客户再进入“系统查询”界面核对w和vip1是否与模拟输入一致。如果w小于served说明有客户离开时没有正确累加allsum检查likai里allsum difftime(end, start)是否在每次离开时都执行了。另一个验证点是柜台评分自动模拟里没有调用mygrade()所以评分数据不会更新这是正常的评分模块只适合手工演示。从那以后我每次跑这类排队模拟都强制先跑一遍 100 客户的自动模式确认队列没有内存泄漏、平均时间不是nan再手工点几次菜单看界面。这份银行排队系统实验报告的价值不在菜单多漂亮而在链式队列的入队出队、窗口数组的补位、时间差的累加这三件事有没有做对。希望帮到你。本文还有配套的精品资源点击获取
返回列表