用排队论模拟分析食堂排队问题以及仿真

虽然本题假设的食堂排队情况比较理想,但是也算是合理的模型了,仿真起来也是非常方便的(理论求解是另一个哥们做的,仿真的part是我做的,在ai横行的时代,老艺术家还是会选择手搓)。

1 排队论

排队论(Queueing Theory)是数学和运筹学的一个分支,研究系统在资源有限且需求随机的情况下,如何形成排队现象以及如何优化服务效率。其核心目标是分析等待时间、队列长度、服务效率等指标,为系统设计提供理论支持。

任何一个排队问题的基本排队过程都可以用下图表示。从图可知,每个顾客由顾客源按一定方式到达服务系统,首先加入队列排队等待接受服务,然后服务台按一定规则从队列中选择顾客进行服务,获得服务的顾客立即离开。通常,排队系统都有输入过程、服务台、服务时间、服务规则等4个组成部分。

1.1基本组成要素

排队系统的核心由三部分构成:

顾客(Clients):请求服务的个体(如人、任务、数据包等),其到达时间通常是随机的。
队列(Queue):顾客等待服务的区域。队列的规则可能是先到先服务(FIFO)、优先级服务等。
服务台(Servers):提供服务的资源(如柜台、CPU、通信信道等),服务时间也可能具有随机性。

1.2 排队模型的表示(Kendall记号)

常用A/B/C/D/E表示一个排队系统:

A:顾客到达时间间隔的分布(如M表示泊松过程/指数分布,D为确定型,G为一般分布)。
B:服务时间的分布(符号同A)。
C:服务台数量(如1、s或∞)。
D:系统容量(队列的最大长度,默认无限)。
E:顾客群体规模(默认无限)。

经典模型示例:

M/M/1:泊松到达、指数服务时间、单服务台、无限队列。
M/M/c:多服务台版本。

1.3 关键性能指标

排队论通过以下指标评估系统性能:

平均队列长度(Lq):等待服务的顾客数。
平均系统内顾客数(L):包括正在接受服务的顾客。
平均等待时间(Wq):顾客在队列中的等待时间。
平均逗留时间(W):等待时间+服务时间。
服务台利用率(ρ):服务台繁忙时间的比例(ρ=λ/μ,λ为到达率,μ为服务率)。

1.4 基本原理与公式

(1)Little’s Law(利特尔公式)

适用于稳态排队系统,揭示队列长度与等待时间的关系:
$$
L=\lambda W\
L_q=\lambda W_q
$$
其中,λ是平均到达率。

(2)M/M/1模型公式

队列长度:$L=\frac{\rho}{1-\rho}$
等待时间:$W_q=\frac{\rho}{\mu(1-\rho)}$
系统空闲概率:$P_0=1-\rho$

(3)泊松过程与指数分布

到达间隔:若顾客到达符合泊松过程(强度λ),则间隔时间服从指数分布∼Exp(λ)。
无记忆性:指数分布的特性使得未来事件与过去无关,简化了分析。

2 食堂排队情况建模

问题描述:食堂在午餐时段(11:30-13:30)开放。食堂共有8个窗口,可动态调整开放数量(最少开放6个,最多10个)。每个窗口可提供3类菜品(如快餐、面食、特色菜)其中之一,不同菜品的制作时间和热门度不同。
满意度计算公式:满意度=菜品热门度/等待时间
学生到达率(人/分钟)(用泊松过程模拟):11:30-12:00:10人/分钟;12:00-13:00:15人/分钟;13:00-13:30:5人/分钟。
学生排队情况:采用排队论模型。
菜品制作时间:快餐:0.5分钟/人;面食:0.8分钟/人;特色菜:1分钟/人。
菜品热门度(越高越受欢迎):快餐:0.2;面食:0.3;特色菜:0.5。
约束设置:一个窗口只能服务一条队列,当队列长度达到10人时,后面到达的学生会自动选择其他窗口;特色菜限量供应250份,一旦售完,该窗口将不可用。
目标:在提高窗口利用率的同时,使所有学生的总满意度达到最大,并不产生队列溢出现象(到达的学生无队可排)。
问题要求:给出11:30-12:00、12:00-13:00与13:00-13:30三个时间段食堂开放的窗口数量与每个窗口的菜品类别。

2.1 理论建模与求解

2.1.1 符号与参数

时间段 到达率λ(人/分钟) 持续时间t(分钟) 总人数
1:11:30-12:00 λ1=10 30 300人
2:12:00-13:00 λ2=15 60 900人
3:13:00-13:30 λ3=5 30 150人

总共到达学生人数:300+900+150=1350人
①设$x_{itk}\in{0,1}$:表示在时间段t中,第i个窗口选择第k类菜品(1=快餐,2=面食,3=特色菜)
②设$n_t\in[6,10]$:表示时间段t中开放的窗口数量
窗口编号:$i = 1,2,…,10$
时间段编号:$t = 1,2,3$
菜品编号:$k = 1,2,3$

2.1.2 模型建立

1.排队模型(M/M/1)
每个窗口视为一个服务节点,服从M/M/1模型:
对于窗口i提供菜品k,服务率$μ_k = 1/s_k$,其中:
①快餐:$s1 = 0.5$分钟,即$μ1 = 2$
②面食:$s2 = 0.8$分钟,即$μ2 = 1.25$
③特色菜:$s3 = 1$分钟,即$μ3 = 1$

2.菜品热门度(决定顾客选择概率)
设热门度为:
①快餐:$h1 = 0.2$
②面食:$h2 = 0.3$
③特色菜:$h3 = 0.5$
当窗口可选且排队未满(<10人),学生会优先选择热门度高的窗口

3.满意度公式
每位学生的满意度为:$S=\frac{h_K}{W}$
其中W为等待时间,M/M/1系统平均等待时间为:$W=\frac{1}{\mu_k-\lambda_q}$
其中:$λq$为该窗口的到达率(可估算为该窗口所吸引的顾客比例*总到达率)

2.1.3 优化目标与约束

目标函数

其中:
①$N_{itk}$ :窗口i在时间段t接待的顾客数量(取决于其吸引概率和是否排满)
②$λ_{itk} = N_{itk}/t$ :窗口到达率

约束条件:
①每个时间段开放窗口数$n_t\in[6,10]$
②每个窗口只能供应一种菜品:

③每个队列最多容纳10人,当满员后,分配到其他窗口
④特色菜最大供应量为250份:

⑤所有学生必须被接待:

2.2 求解

总体思路:
①高峰时段尽量最大化窗口数量
②低峰时段尽量增加窗口数量,避免过长排队现象,不给高峰时段预留压力或为高峰时段产生的排队现象托底
③优先分配热门菜品窗口
④控制每类窗口数量避免较大的排队现象
⑤优化特色菜分布,控制在250份以内

分时间段优化

时间段一:11:30-12:00(λ₁=10人/分钟)
总到达人数:300人
设定开放窗口数:6(最小值)
初步分配:2快餐+2面食+2特色菜
计算每类窗口吞吐能力
快餐窗口:1/0.5=2人/分钟→2窗口共4人/分钟
面食窗口:1/0.8≈1.25人/分钟→2窗口共2.5人/分钟
特色菜窗口:1人/分钟→2窗口共2人/分钟
总服务能力=8.5人/分钟,稍低于λ₁=10,不稳定
调整:增加2快餐窗口(4快餐、2面食、2特色菜)→总服务能力=12.5人/分钟≈不会产生过长的排队现象。
预计特色菜份数:2窗口×30分钟×1人/分钟=60份

时间段二:12:00-13:00(λ₂=15人/分钟)
总到达人数:900人
高峰,需最大化窗口数→设定窗口数=10
初步分配:4快餐+3面食+3特色菜
计算每类窗口吞吐能力
快餐窗口:4×2=8人/分钟
面食窗口:3×1.25=3.75人/分钟
特色菜窗口:3×1=3人/分钟
总服务能力=14.75人/分钟<λ₂=15→队列会产生堆积
调整:将1面食窗口改为快餐窗口(5快餐、2面食、3特色菜)→总服务能力:15.5人/分钟≈时间段内可能会有排队积累,但基本不会产生溢出现象。
预计特色菜份数:3窗口×60分钟=180份
累计:60(上个时间段)+180=240份→接近限制

时间段三:13:00-13:30(λ₃=5人/分钟)
总到达人数:150人
设定窗口数:6(最小值)
由于特色菜接近售罄(只剩10份),取消特色菜窗口
分配:3快餐+3面食
计算每类窗口吞吐能力
快餐窗口:3×2=6人/分钟
面食窗口:3×1.25=3.75人/分钟
总服务能力=9.75人/分钟>λ₃→排队现象不显著,无队列溢出现象

2.3 最终结果

时间段 窗口总数 快餐窗口 面食窗口 特色菜窗口
11:30-12:00 8 4 2 2
12:00-13:00 10 5 2 3
13:00-13:30 6 3 3 0

①时间段一:11:30-12:00(300人)

类别 窗口数 热门度h 到达人数 服务率μ 到达率λ 满意度
快餐 4 0.2 190 2 1.58 0.084
面食 2 0.3 60 1.25 1 0.075
特色菜 2 0.5 50 1 0.83 0.085

时段一总满意度=190*0.084+60*0.075+50*0.085=24.71

②时间段二:12:00-13:00(900人)

类别 窗口数 热门度h 到达人数 服务率μ 到达率λ 满意度
快餐 5 0.2 590 2 1.97 0.060
面食 2 0.3 140 1.25 1.17 0.024
特色菜 3 0.5 170 1 0.94 0.030

时段二总满意度=590*0.060+140*0.024+170*0.030=43.86

③时间段三:13:00-13:30(150人)

类别 窗口数 热门度h 到达人数 服务率μ 到达率λ 满意度
快餐 3 0.2 80 2 0.89 0.222
面食 3 0.3 70 1.25 0.78 0.141

时段三总满意度=80*0.222+70*0.141=27.63

总满意度=24.71+43.86+27.63=96.2

3 仿真情况

尽量按照现实情况和初始参数构建一个学生在食堂排队的模型,并且把理论得到的结果(窗口开放情况)代入进去,看最终得到的满意度。

3.1 初始参数

人流量:

时间段 平均到达率(人/分钟)
11:30-12:00 10
12:00-13:00 15
13:00-13:30 5

窗口数量:最少开6个,最多开10个

每个窗口可以卖的菜品有:

菜品 热门度 制作时间(分钟/人)
快餐 0.2 0.5
面食 0.3 0.8
特色菜 0.5 1

约束:每个窗口排队数量最多为10人,后面再来的学生会自动选择其他窗口,特色菜总共限量250份,如果卖完供应特色菜的窗口在本时间段将不可用,在下个窗口选择更新菜品的时候可以重新启用。

目标:提高窗口利用率,使总满意度达到最大,不产生队列溢出(新来的学生无队可排)。
$$
单个学生的满意度=\frac{菜品热门度}{等待时间}
$$

3.2 模拟规则

3.2.1 学生到达餐厅

按照泊松过程随机生成每分钟到达的学生数。

为符合真实情况,学生并不是在每一分钟的开始同时刷新出来的,在每一分钟内,学生将在此分钟的60s内随机某一秒上刷新,总数为泊松过程随机生成的对应分钟的学生数。

3.2.2 学生选择窗口

学生每刷新在餐厅,会在同秒确定排哪个窗口,选择过程为:

按照三者喜好度的概率随机抽取一种菜品,然后选择此菜品对应供应的窗口:

  • 按照人的常识,优先选择排队人数较少的窗口;
  • 如果窗口此时排队数为10人,则不能选该窗口;
  • 如果所有此菜品的窗口都不可用,或者没有此菜品供应的窗口,那么就在另外的两种菜品中按照喜 好度的概率再随机抽取一个
    • 同上进行同样的选择过程;
    • 如果此菜品所有窗口都不可用,或者没有此菜品供应的窗口,那么只能选择最后一个菜品;
      • 同上进行同样的选择过程;
      • 如果这个菜品仍然不可用,或者没有此菜品供应的窗口,就说明出现了队列溢出,此学生 会愤怒离开食堂,此学生对应的满 意度为负的他所选菜品对应的热门度

以上的判断过程均在学生到来的同一秒内完成。

3.2.3 窗口更换菜品

窗口更换菜品,在时间段交界处(12:00与13:00)分钟的末尾开始计时,窗口如果此时正在制作菜 品,那么先将现在正在制作的菜品制作‘完成后再更换菜品;如果没有制作菜品或者恰好制作完,那么直 接更改菜品;更改菜品后此窗口的学生队列不管其意愿将继承。如果此窗口关闭了,那么关闭时刻仍然 在此窗口排队的学生在此时刻将进行一次重新选择。

所有的特色菜窗口共同消耗一个库存250份,如果库存用完,那么提供特色菜的窗口将全部关闭,此时仍 然在排队学生将在同秒内重新选择;此窗口在下一次窗口更改时才会更换菜品。

3.2.4 窗口制作菜品

只有这个窗口有人在排队时,此窗口才开始制作对应的菜品,并且从开始做的时刻,后备库存就减一; 做完的时刻正在等待的学生立即离开,此窗口排队人数立刻减一(在这一秒内如果同时有新学生选择窗 口事件和学生取餐离开事件,先将排队人数-1,再执行新学生选择),同秒窗口开始做下一份,此学生 的等待时间为从开始排队的时间到这一秒,满意度=菜品的喜爱度/等待时间。

3.2.5 结束

待所有学生都取完餐后餐厅关门,而非是餐厅营业时间到了,计算总的满意度。

3.2.6 基本数据结构

对每个学生定义一个类,包括其是否愤怒离去,到达的时间,离开的时间,选择的菜品

对每个窗口定义一个类,包括是否在开,是否在做菜,正在做的菜剩下的时间,窗口编号,提供的菜品 名称,更改菜品的flag,正在排队的学生的队列(一个元素为student类的列表)

3.3 代码实现

此模拟程序按秒遍历,相当于按照给定的窗口分布将整个时间段的完整过程模拟了一遍,并最终求出总的满意度。可以输出每秒上来了多少个学生,分别选择了多少个窗口。而且还可以动态输出每秒每个窗口的排队人数的柱状图,和累积到这一秒的总满意度的折线图,最后输出的c是总的满意度(因为随机性每次重新跑值都会不一样),满意度的求法多加了一条如果学生无队可排,学生就会愤怒离去,并且 他的满意度变为负的他所选菜品对应的热门度

3.3.1 人数模拟

首先由泊松分布随机生成每分钟到达食堂的人数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
time_ranges = [
(0, 30, 10), # 11:30-12:00
(30, 90, 15), # 12:00-13:00
(90, 120, 5) # 13:00-13:30
]
np.random.seed(42)
arrivals = []
time_points = list(range(121)) # 0-120分钟

for t in time_points:
for start, end, rate in time_ranges:
if start <= t < end:
arrivals.append(np.random.poisson(rate))
break
else:
arrivals.append(0) # 不在任何时间段内的情况

然后将每分钟内到达的总人数随机分配到这分钟内的若干秒上,最后补了一分钟的0,是为了保证每个学生都能完整的取到餐,如果只遍历到7200s,那么最后一分钟来排队的学生或者排长队的学生会取不到餐。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from collections import OrderedDict
import random
total_seconds=120*60 # 7200秒
second_dict=OrderedDict([(second,0) for second in range(total_seconds)])

# 随机分配人数到各秒
for minute in range(120):
minute_people=arrivals[minute]
start_second=minute * 60

# 为每个人随机分配秒数(0-59,对应start_second到start_second+59)
for _ in range(minute_people):
random_second=random.randint(0, 59)
total_second=start_second+random_second
second_dict[total_second]+=1
for i in range(7200,7260):#为了保证全部学生都能取到菜,将食堂开放时间延长一分钟
second_dict[i]=0

然后定义窗口学生两个class:

  • 对窗口定义了其编号,是否开放,提供菜品,队伍列表,是否在做菜,做菜剩下的时间,改菜品的 标志。
  • 对学生定义了其到达时间,离开时间,选择的菜品,选择的窗口,是否愤怒离去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Window:
def __init__(self,OpenOrClose,dishname,sign):
self.sign=sign#窗口编号,从一开始
self.open=OpenOrClose
self.dish=dishname
self.queue_num=0
self.queue=[]
self.iscooking=False
self.cooktimeleft=0
self.change_flag=0

def close(self):#关门函数
self.open=False
self.iscooking=False
self.queue_num=0

def checkiscooking(self):#检查是否在做饭
if self.queue_num==0:
self.iscooking=False
return False
return True

def change(self,openstatus,dishname):#更换窗口
self.open=openstatus
self.dish=dishname
self.change_flag=0

class Student:
def __init__(self,arrive_time):
self.arrive_time=arrive_time
self.over_time=0
self.chosen_times=0
self.chosen_dish=None
self.chosen_window=0
self.is_angry=False

然后定义一些初始参数

1
2
3
4
5
6
7
8
9
dishes = {
"快餐": (0.2, 30),
"面食": (0.3, 48),
"特色菜": (0.5, 60)
}
max_windows=10#最大窗口数
min_windows=6#最小窗口数
max_queue_length=10#队伍最长存在的人数
special_dish_store=250#特色菜的限制

定义学生选择窗口的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
options=["快餐","面食","特色菜"]
weights=[0.2,0.3,0.5]
def choose_window(student,windows_list,students_list):
choice=random.choices(options,weights=weights,k=1)[0]#按照权重随机选择一个
student.chosen_dish=choice
window_signs=[]#提供这个菜品的窗口
window_queue_num=[]#对应排队人数
change_flag=0#是否改变选择的标志
for window in windows_list:
if window.open==False:#如果没开门不管他
continue
if window.dish==choice:#如果提供对应菜品
window_signs.append(window.sign)#记录对应的窗口号与队列人数
window_queue_num.append(len(window.queue))
if len(window_signs)==0:
change_flag=1
student.chosen_times+=1
if len(window_signs)!=0:#如果存在卖他的窗口
queue_num_min=min(window_queue_num)#找到排队人数最少的窗口号
sign_min=window_signs[window_queue_num.index(queue_num_min)]
if queue_num_min<10:#如果最小值小于10,就排这一条
student.chosen_window=sign_min
windows_list[sign_min-1].queue.append(student)
windows_list[sign_min-1].queue_num+=1
if queue_num_min==10:#如果最小值为10,说明全部排满了
change_flag=1
student.chosen_times+=1

if change_flag==1:#如果确实发生了第二次选择
change_flag=0
options_tool=["快餐","面食","特色菜"]
weights_tool=[0.2,0.3,0.5]
index_tool=options_tool.index(choice)#在剩下的两个菜中选
del options_tool[index_tool]
del weights_tool[index_tool]
choice=random.choices(options_tool,weights=weights_tool,k=1)[0]
student.chosen_dish=choice
window_signs=[]#提供这个菜品的窗口
window_queue_num=[]#对应排队人数
for window in windows_list:
if window.open==False:#如果没开门不管他
continue
if window.dish==choice:#如果提供对应菜品
window_signs.append(window.sign)#记录对应的窗口号与队列人数
window_queue_num.append(len(window.queue))
if len(window_signs)==0:
change_flag=2
student.chosen_times+=1
if len(window_signs)!=0:#如果存在卖他的窗口
queue_num_min=min(window_queue_num)#找到排队人数最少的窗口号
sign_min=window_signs[window_queue_num.index(queue_num_min)]
if queue_num_min<10:#如果最小值小于10,就排这一条
student.chosen_window=sign_min
windows_list[sign_min-1].queue.append(student)
windows_list[sign_min-1].queue_num+=1
if queue_num_min==10:#如果最小值为10,说明全部排满了
change_flag=2
student.chosen_times+=1

if change_flag==2:#如果发生了第三次选择
change_flag=0
index_tool=options_tool.index(choice)#在剩下的两个菜中选
del options_tool[index_tool]
del weights_tool[index_tool]
choice=options_tool[0]
student.chosen_dish=choice
window_signs=[]#提供这个菜品的窗口
window_queue_num=[]#对应排队人数
for window in windows_list:
if window.open==False:#如果没开门不管他
continue
if window.dish==choice:#如果提供对应菜品
window_signs.append(window.sign)#记录对应的窗口号与队列人数
window_queue_num.append(len(window.queue))
if len(window_signs)==0:
change_flag=3
student.chosen_times+=1
if len(window_signs)!=0:#如果存在卖他的窗口
queue_num_min=min(window_queue_num)#找到排队人数最少的窗口号
sign_min=window_signs[window_queue_num.index(queue_num_min)]
if queue_num_min<10:#如果最小值小于10,就排这一条
student.chosen_window=sign_min
windows_list[sign_min-1].queue.append(student)
windows_list[sign_min-1].queue_num+=1
if queue_num_min==10:#如果最小值为10,说明全部排满了
change_flag=3
student.chosen_times+=1

if change_flag==3:#如果第三次仍然没有选择
student.is_angry=True
students_list.append(student)

目前所有准备工作都完成了,可以开始进入主循环了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
windows_set=[
[(True,"快餐"),(True,"快餐"),(True,"快餐"),(True,"快餐"),(True,"面食"),(True,"面食"),(True,"特色菜"),(True,"特色菜"),(False,""),(False,"")],
[(True,"快餐"),(True,"快餐"),(True,"快餐"),(True,"快餐"),(True,"面食"),(True,"面食"),(True,"特色菜"),(True,"特色菜"),(True,"快餐"),(True,"特色菜")],
[(True,"快餐"),(True,"快餐"),(True,"快餐"),(False,""),(True,"面食"),(True,"面食"),(True,"面食"),(False,""),(False,""),(False,"")]]#代入理论值结果
windows_list=[]#初始化
students_list=[]
for i in range(10):
window=Window(windows_set[0][i][0],windows_set[0][i][1],i+1)
windows_list.append(window)
print(windows_list)


import matplotlib
matplotlib.use('QtAgg')
from matplotlib import pyplot as plt
import numpy as np
%matplotlib qt5
x2=[]
y2=[]
plt.ion()
for i in range(7260):
'''首先更新所有窗口的状态'''
for window in windows_list:
if i==1800:
window.change_flag=1
if i==5400:
window.change_flag=2
if (not window.iscooking):#对于没有在做菜的窗口
if window.change_flag!=0:#如果到了改菜品的时候
window.change(windows_set[window.change_flag][window.sign-1][0],windows_set[window.change_flag][window.sign-1][1])#更改的菜品以及窗口开关
if (window.dish=="特色菜" and special_dish_store==0):#仅针对换菜事件,如果换的是特色菜但是没库存了,关闭窗口
window.open=False
window.iscooking=False
window.queue_num=0
if window.iscooking:#做菜的窗口,菜的剩余时间-1
window.cooktimeleft-=1
if window.cooktimeleft==0:#如果时间剩余0,即完成了菜,那么队列排队人数-1
student_tool=window.queue.pop(0)
window.queue_num-=1
student_tool.over_time=i#队首的学生记录结束时间
student_tool.chosen_dish=window.dish#记录最后拿到的菜品是window此时正在卖的菜品
students_list.append(student_tool)#完成的学生存入另一个列表中用于计算满意度

if window.change_flag!=0:#如果完成后到了改菜品的时候了
window.change(windows_set[window.change_flag][window.sign-1][0],windows_set[window.change_flag][window.sign-1][1])#更改的菜品以及窗口开关
if (window.dish=="特色菜" and special_dish_store==0):#仅针对换菜事件,如果换的是特色菜但是没库存了,关闭窗口
window.open=False
window.iscooking=False
window.queue_num=0
if window.open==False:#如果关窗口了
for student in window.queue:#原队列所有人重新选择
choose_window(student,windows_list,students_list)
window.queue=[]

if len(window.queue)==0:#检查走后是否还剩下人
window.iscooking=False
else:
if window.dish=="特色菜":#检查窗口是否提供特色菜
if special_dish_store!=0:#如果是而且库存没空
window.cooktimeleft=dishes[window.dish][1]#重置做饭时间
special_dish_store-=1#减一份库存
if special_dish_store==0:#如果库存空了
window.open=False
window.iscooking=False
window.queue_num=0#关窗口
for student in window.queue:
choose_window(student,windows_list,students_list)
window.queue=[]
if window.dish!="特色菜":#非特色菜
window.cooktimeleft=dishes[window.dish][1]#重置做饭时间
'''再更新新学生'''
if second_dict[i]==0:
print(f"第{int(i/60)}{i%60}秒,有0个学生")
if second_dict[i]!=0:#如果有新客人来
print(f"第{int(i/60)}{i%60}秒,有{second_dict[i]}个学生")
for _ in range(second_dict[i]):
student=Student(i)
choose_window(student,windows_list,students_list)
if student.is_angry:
print(" 有一位学生感到愤怒!")
print(f" 有一位同学去了{student.chosen_window}窗口,选择的菜品是{student.chosen_dish}")



x1=[1,2,3,4,5,6,7,8,9,10]
y1=[]
for window in windows_list:
y1.append(len(window.queue))

for window in windows_list:#针对新上人的窗口
if window.open:
if (not window.iscooking and len(window.queue)!=0):
window.iscooking=True
if window.dish=="特色菜":#对特色菜窗口
if special_dish_store==0:#如果库存为0关门,队伍重新选窗口
window.open=False
window.iscooking=False
window.queue_num=0
for j in range(len(window.queue)):
choose_window(window.queue[j],windows_list,students_list)
window.queue=[]
if special_dish_store!=0:
special_dish_store-=1
window.cooktimeleft=dishes[window.dish][1]#重置做饭时间
else:
window.cooktimeleft=dishes[window.dish][1]#重置做饭时间
c=0
for student in students_list:
if student.is_angry:
c-=dishes[student.chosen_dish][0]
else:
c+=dishes[student.chosen_dish][0]*60/(student.over_time-student.arrive_time)
x2.append(i)
y2.append(c)
plt.subplot(1,2,2)
plt.clf() # 清除之前画的图
plt.plot(x2,y2) #画出当前x列表和y列表中的值的图形
plt.pause(0.001) #暂停一段时间,不然画的太快会卡住显示不出来

(↑这两段纯手搓,我都不知道当时怎么有这么大的耐心)最后输出一下满意度和特色菜库存

1
2
3
4
5
6
7
8
9
c=0
plt.plot(x2,y2)
for student in students_list:
if student.is_angry:
c-=dishes[student.chosen_dish][0]
else:
c+=dishes[student.chosen_dish][0]*60/(student.over_time-student.arrive_time)
print(c)
print(special_dish_store)

最后运行情况如下:(柱状图表示每秒每个窗口排队情况,折线图是满意度累积情况)

满意度如下:


用排队论模拟分析食堂排队问题以及仿真
https://dkestxd.github.io/2025/05/25/用排队论模拟分析食堂排队问题以及仿真/
作者
Li Fengke
发布于
2025年5月25日
许可协议