当前位置: 首页 > news >正文

18.5【保姆级教程】用队列进行模拟:从数据结构到现实世界的“预言机”

📢专栏持续更新中!关注博主不迷路,跟着专栏系统学C语言底层开发,从语法入门到工程实战,逐章拆解,保姆级讲解,刚入门的同学跟着学,全程零压力~

上一节我们掌握了抽象数据类型(ADT)的设计方法,学会了把数据结构和操作打包成模块,对外只暴露干净利落的函数接口。今天,我们要把这项技能用在一个非常有趣的地方——用队列来模拟现实生活中的排队场景

你可能觉得“队列”只是个简单的先进先出(FIFO)数据结构,但它在真实世界中的应用无处不在:银行柜台前的顾客、机场跑道上等待起飞的飞机、操作系统里排队等待CPU处理的任务、你刷短视频时后台的请求队列……掌握了队列模拟,你就能用C语言回答这样的问题

  • 一个咨询摊位平均每小时能接待多少顾客?
  • 每位顾客平均要等多久?
  • 排队等待的顾客平均有多少人?
  • 摊位需要设置多少个座位才够用?

这些问题不需要真的去街上蹲一天——用程序模拟几千个顾客,几分钟就能得到有统计意义的答案。这就是数据结构 + 编程的威力。

本节核心知识点梳理(提前划重点,方便后续对照学习):

  1. 问题建模:如何把现实中的排队场景抽象为程序中的数据结构;
  2. 随机数生成:用rand()模拟顾客到达间隔和服务时长的随机性;
  3. 队列 ADT 的复用:直接使用之前学过的队列模块,不必重复造轮子;
  4. 模拟循环的核心逻辑:以“分钟”为单位推进仿真时钟,处理到达、服务、离开事件;
  5. 数据统计与分析:从模拟中提取平均等待时间、平均队列长度、每小时服务人数等指标。

一、问题描述:商业街上的咨询摊位

1.1 场景设定

假设 Sigmund Landers 在商业街设置了一个提供付费建议的摊位。规则如下:

  • 顾客可以购买1分钟、2分钟或3分钟的建议(咨询服务时长);
  • 为确保商业街人流畅通,每个摊位前排队等待的顾客最多为10人(即队列最大长度为10);
  • 顾客是随机出现的,到达间隔不确定;
  • 每位顾客的服务时长也是随机选择的(1、2或3分钟);
  • 如果顾客到达时队列已满(10人),该顾客直接离开(损失一位顾客)。

我们要回答的核心问题

  1. Sigmund 平均每小时能接待多少名顾客?
  2. 每位顾客平均花在咨询上的时间是多少?
  3. 排队等待的顾客平均有多少人?
  4. 有多少顾客因为队列满而流失?

1.2 为什么队列是最合适的数据结构?

这个问题天然符合队列的“先进先出”特性:

  • 先来的顾客先接受服务;
  • 后来的顾客排在队尾;
  • 服务完的顾客从队首离开。

队列在这里就是顾客排队的数字化身。

1.3 模拟策略:以“分钟”为单位推进

我们将模拟时间以分钟为最小单位进行推进。每一分钟里,会发生以下事件:

步骤事件模拟操作
1是否有新顾客到达?用随机数决定,如果到达且队列未满则入队
2当前是否有顾客正在接受服务?如果是,剩余服务时间减1分钟
3服务是否刚好完成?如果是,让该顾客出队,统计其等待时间
4如果服务完成且队列非空从队列中取出下一位顾客,开始服务

这就是离散事件模拟的核心循环。程序运行几千个这样的“分钟”后,就能得出有统计意义的结论。

二、随机数的艺术:让模拟更真实

2.1 顾客到达的随机性

现实中,顾客不是排着队准时出现的。假设平均每 3 分钟来一位顾客,但实际间隔可能是1分钟、5分钟、甚至连续来两位。我们怎么模拟这种“随机感”?

C 语言的rand()函数生成一个 0 到RAND_MAX之间的伪随机整数。通过与运算,可以得到指定范围内的随机数:

#include<stdlib.h>#include<time.h>// 在 main 开头调用一次,用当前时间做种子,确保每次运行结果不同srand((unsignedint)time(NULL));// 生成 1 到 3 之间的随机整数,代表 1分钟、2分钟 或 3分钟intservice_time=rand()%3+1;

2.2 到达概率的设定

假设平均每 3 分钟来一位顾客,则每一分钟有 1/3 的概率来一位新顾客。我们可以这样模拟:

// 每一分钟执行一次:if(rand()%3==0){// 概率约为 1/3// 来了一位新顾客if(queue_size<MAX_QUEUE_SIZE){queue_enqueue(q,service_time);}else{customers_lost++;// 队列满,顾客流失}}

2.3 服务时长的随机性

每位顾客购买的建议时长是随机的(1、2或3分钟):

intservice_time=rand()%3+1;// 1, 2, 或 3

三、模拟程序的完整实现

3.1 需要统计的数据

在开始编写模拟循环前,先明确我们要收集哪些数据:

统计指标变量名含义
接待顾客总数total_served成功接受服务并离开的顾客数量
流失顾客总数total_lost因队列满而直接离开的顾客数量
总等待时间total_wait_time所有顾客在队列中等待的分钟数之和
总服务时间total_service_time所有顾客接受咨询的分钟数之和
模拟总时长SIMULATION_MINUTES模拟多少分钟(如 480 分钟 = 8小时)

通过这些原始数据,可以计算出:

  • 平均每小时接待人数 =total_served / (SIMULATION_MINUTES / 60)
  • 平均等待时间 =total_wait_time / total_served
  • 平均队列长度 =total_wait_time / SIMULATION_MINUTES(这段时间内累积的等待分钟数 ÷ 总分钟数,近似等于任一时刻的平均排队人数)
  • 流失率 =total_lost / (total_served + total_lost)

3.2 完整代码

#include<stdio.h>#include<stdlib.h>#include<time.h>#defineMAX_QUEUE_SIZE10#defineARRIVAL_PROB3// 平均每3分钟来一位顾客(概率 1/3)#defineSIMULATION_MINUTES480// 模拟8小时(480分钟)// ---------- 队列 ADT 的简单实现(内嵌版本,便于教学) ----------structCustomer{intservice_time;// 该顾客需要的服务时长(1~3分钟)intwait_time;// 该顾客在队列中等待的分钟数(入队时记录当前时间)};structQueue{structCustomeritems[MAX_QUEUE_SIZE];intfront;intrear;intsize;};voidqueue_init(structQueue*q){q->front=0;q->rear=0;q->size=0;}intqueue_is_full(structQueue*q){returnq->size>=MAX_QUEUE_SIZE;}intqueue_is_empty(structQueue*q){returnq->size==0;}intqueue_enqueue(structQueue*q,structCustomerc){if(queue_is_full(q))return-1;q->items[q->rear]=c;q->rear=(q->rear+1)%MAX_QUEUE_SIZE;q->size++;return0;}structCustomerqueue_dequeue(structQueue*q){structCustomerc=q->items[q->front];q->front=(q->front+1)%MAX_QUEUE_SIZE;q->size--;returnc;}// ---------- 模拟主程序 ----------intmain(void){structQueueq;queue_init(&q);srand((unsignedint)time(NULL));// 统计变量inttotal_served=0;// 成功服务的顾客总数inttotal_lost=0;// 因队列满而流失的顾客总数inttotal_wait_time=0;// 所有顾客的总等待分钟数inttotal_service_time=0;// 所有顾客的总服务分钟数intcurrent_service_remaining=0;// 当前正在服务的顾客还剩余多少分钟(0 表示空闲)intcurrent_minute;// 模拟时钟的当前分钟数// 模拟主循环:每分钟迭代一次for(current_minute=1;current_minute<=SIMULATION_MINUTES;current_minute++){// 事件1:是否有新顾客到达?if(rand()%ARRIVAL_PROB==0){// 概率 1/ARRIVAL_PROBif(!queue_is_full(&q)){structCustomerc;c.service_time=rand()%3+1;// 随机 1, 2, 3 分钟c.wait_time=current_minute;// 记录到达时刻,后续计算等待时间queue_enqueue(&q,c);}else{total_lost++;// 队列满了,顾客直接离开}}// 事件2:当前是否有顾客正在接受服务?if(current_service_remaining>0){current_service_remaining--;// 服务时间减少 1 分钟// 事件3:服务是否刚好完成?if(current_service_remaining==0){total_served++;}}// 事件4:如果当前空闲,且队列非空,从队列中取下一位顾客开始服务if(current_service_remaining==0&&!queue_is_empty(&q)){structCustomerc=queue_dequeue(&q);current_service_remaining=c.service_time;total_wait_time+=(current_minute-c.wait_time);// 累计该顾客的等待分钟数total_service_time+=c.service_time;}}// 模拟结束,输出统计结果printf("========== 模拟结果 ==========\n");printf("模拟总时长:%d 分钟(约 %.1f 小时)\n",SIMULATION_MINUTES,SIMULATION_MINUTES/60.0);printf("接待顾客总数:%d 人\n",total_served);printf("流失顾客总数:%d 人(队列满)\n",total_lost);printf("平均每小时接待:%.1f 人\n",(double)total_served/(SIMULATION_MINUTES/60.0));if(total_served>0){printf("每位顾客平均等待时间:%.1f 分钟\n",(double)total_wait_time/total_served);printf("每位顾客平均服务时间:%.1f 分钟\n",(double)total_service_time/total_served);}printf("平均队列长度:%.2f 人\n",(double)total_wait_time/SIMULATION_MINUTES);printf("流失率:%.1f%%\n",(double)total_lost/(total_served+total_lost)*100.0);return0;}

3.3 运行结果示例

========== 模拟结果 ========== 模拟总时长:480 分钟(约 8.0 小时) 接待顾客总数:142 人 流失顾客总数:0 人(队列满) 平均每小时接待:17.8 人 每位顾客平均等待时间:0.7 分钟 每位顾客平均服务时间:2.0 分钟 平均队列长度:0.22 人 流失率:0.0%

新手重点:因为每次运行用了srand(time(NULL)),结果会稍有不同,但大致趋势一致。这正是模拟的意义——通过反复运行,得出一个统计范围,而不是精确的单一数值。

3.4 代码逐段拆解

① 数据结构定义

structCustomer{intservice_time;// 服务时长intwait_time;// 到达时刻(用于计算等待时间)};

wait_time记录的是顾客入队时的分钟数(当前模拟时钟的读数)。当这位顾客最终被服务时,用current_minute - c.wait_time就得到了他在队列中等待的总分钟数。

② 模拟主循环的四步事件

for(current_minute=1;current_minute<=SIMULATION_MINUTES;current_minute++){// 事件1:检查新到达// 事件2:推进当前服务// 事件3:检查服务完成// 事件4:取下一位顾客}

这个循环结构是离散事件模拟的通用模板,换个场景同样适用。

③ 统计变量的更新时机

变量何时更新
total_served服务完成时
total_lost到达但队列满时
total_wait_time顾客从队列中取出开始服务时
total_service_time顾客从队列中取出时

④ 平均队列长度的计算

平均队列长度 = 总等待分钟数 / 模拟总分钟数

这背后的原理是利特尔法则:在一个稳定系统中,平均排队人数 = 顾客到达率 × 平均等待时间。我们的计算方式等价于这个公式,统计意义上合理。

3.5 你可以调整的参数

参数当前值如果调整为……会有什么变化?
MAX_QUEUE_SIZE105流失率飙升,排队人数减少
ARRIVAL_PROB3(每3分钟来一位)2(每2分钟来一位)顾客更多,队列更长,等待更久
SIMULATION_MINUTES480(8小时)2880(48小时)样本更大,统计结果更稳定
服务时长范围1~3分钟1~5分钟每位顾客占用更长时间,队列积压更严重

动手试一试:改几个参数重新运行,观察结果如何变化。这就是模拟的魅力——不需要真的去街上开个摊位,就能提前预见各种情况。

四、本章总结(新手必看,快速掌握核心)

核心知识点一句话总结
问题建模把现实排队场景映射为队列数据结构,1分钟为模拟时钟的最小单位
随机数模拟rand() % N控制概率,srand(time(NULL))保证每次运行结果不同
四步事件循环到达检查 → 服务推进 → 完成处理 → 取下一位,这是离散事件模拟的通用模板
数据统计在关键事件点累计原始数据,模拟结束后统一计算平均指标
利特尔法则平均队列长度 = 总等待时间 / 总模拟时间
参数可调队列容量、到达率、服务时长范围等参数都可以自由调整,观察不同场景

入门行动清单

  1. 把上面的完整代码复制到 VS 中,编译运行,观察输出结果;
  2. 修改MAX_QUEUE_SIZE为 5,重新运行,对比流失率的变化;
  3. 修改ARRIVAL_PROB为 2(顾客来得更频繁),重新运行,观察等待时间的变化;
  4. 尝试将模拟时长设为60 * 24(模拟一整天),看看结果是否更稳定;
  5. 思考一下:你身边还有哪些场景可以用队列模拟?食堂打饭?地铁安检?试试动手改编代码。

队列模拟的魔法已经在你手里了。它不仅仅是一个数据结构练习,更是把编程能力应用于解决现实问题的思维方式——从观察世界到建模抽象,再到代码实现和数据分析,这条链路是高级程序员的必备素养。

👉关注博主,专栏持续更新,从基础到实战,保姆级讲解 C 语言核心特性与工程技巧。队列之后,我们将进入二叉树的世界——那是数据结构的又一个重要篇章。我们下一节见!

#C语言 #数据结构 #队列 #离散事件模拟 #随机数 #保姆级教程 #新手避坑 #嵌入式开发 #CSDN #C语言实战

🎁欢迎关注公众号,获取更多技术干货!

🚀 C语言宝藏资源包免费送!14 本 C++ 经典书 + 编译工具全家桶 + 高效编程技巧,搭配 C 语言精选书籍、20 + 算法源码 + 项目规范,还有 C51 单片机 400 例实战!从零基础到嵌入式开发全覆盖,学生党、职场人直接抄作业~ 关注文章末尾的博客同名公众号,回复【C 语言】一键解锁全部资源,手慢也有!​

http://www.gsyq.cn/news/1499561.html

相关文章:

  • 如何快速上手Duix Avatar:打造专属AI数字人的完整实践指南
  • 如何在5分钟内免费生成高质量3D资产?Hunyuan3D-2终极指南
  • 2026定制竹蜻蜓厂家推荐:金华市精彩塑胶制品有限公司,聚焦儿童玩具与文旅礼品定制配套 - 企师傅推荐官
  • 2026年郑州航空港区长短途搬家运输公司:设备搬迁、企业搬迁、机场货物搬卸分析报告 - 品研笔录
  • 2026 纺织服饰配套优选:复合型高周波热转印标定制厂家严选 - 变量人生001
  • 2026年必备收藏:解决AIGC烦恼的免费实用网站
  • 2026上海高端手表回收:江诗丹顿回收市场行情解析 - 奢侈品回收评测
  • 2026年郑州航空港区公司企业搬迁公司全景分析:深度测评选对团队少走弯路! - 品研笔录
  • [AI Agent 01]对话记忆、Agent 循环、Function Calling
  • 2026年怎么降低论文AIGC率?7种高效方法必收藏!
  • 宝塔面板如何设置网站伪静态 宝塔|Nginx网站部署 伪静态配置|静态资源访问配置
  • 2026年实测有效:4个指令+3个技巧助你把论文AI率从50%降到10%
  • 郑州人注意!闲置迪奥包别乱卖,看完少踩坑 - 奢侈品回收评测
  • 三、SCI熟词生意(一)
  • IEC 61850:GOOSE报文详细解析(下篇)
  • 2026年|知网、维普AIGC检测率差46%!同一论文AI率该信谁?必备降AI工具推荐
  • 2026标准数字时钟系统品牌排行与价格选购攻略 - 品研笔录
  • 视频水印处理三大场景总结,多款轻量化工具实测分享
  • 鸿蒙原生应用实战(二):首页开发与全局数据流设计
  • 宁波精装房石材改造指南:不砸不拆怎么提升质感(2026版) - 宁波融诚石业
  • 知识图谱 Graph Rag 方法横向对比
  • Web分布式网站架构之-Squid缓存【20260608】005篇-【传统代理】
  • 【UE5】雷达覆盖区域效果
  • 2026年 黑龙江铝塑铝门窗/哈尔滨保暖铝塑铝门窗推荐榜:高密封、抗老化、高性价比家装与老旧小区改造优选 - 品牌发掘
  • 闲置多年奢侈品腕表,2026无锡手表回收如何养护价值更高 - 奢侈品回收评测
  • SQL/NoSQL数据库为何成为TVA的记忆系统(7)
  • 2026年苏州定制家具厂家推荐榜:酒店餐饮、适老化、医养机构与养老院圆角防撞星级配套家具精选 - 品牌发掘
  • 数据分析进阶——经营分析指标字典
  • Web分布式网站架构之-Squid缓存【20260609】squid配置文件详解002篇
  • 伺服电机仿真(4):PMSM在d-q旋转坐标系下的状态方程与等效电路