存储论——报童问题(单周期)订货模型与仿真精解

报童问题(Newsvendor Problem),最早由哈维·莫德里格利亚尼(Harvey M. Wagner)和托马斯·M·怀特(Thomas M. Whitin)于1958年提出,是运筹学中经典的库存管理问题。其名称源于报童的情境描述,即一个报童每天需要决定订购多少份报纸以最大化利润。报童每天面对报纸需求的不确定性,若订购量太少,可能错失潜在利润;订购量太多,又可能因未售出的报纸带来损失。因此,报童需根据历史销售数据和需求分布,权衡订购量与收益间的关系。报童问题的基本模型非常简单,却能捕捉复杂的库存管理思想。其主要目标是确定最优的订购量,使得期望收益最大化。这个问题是库存控制和供应链管理理论的基础,涉及的不确定性和随机性为许多后续的管理决策问题提供了范式。

一、报童问题概述

报童问题描述:一报童从报刊发行处订报后零售,每卖出一分可获利a元,若订报后卖不出去,则退回发行处,每分将要赔钱b元。问报童如何根据以往的卖报情况(每天卖出k份的概率pk)来推算出每天收益达到最大的订报量Q

1.1 报童问题的发展

报童问题的基本模型非常简单,却能捕捉复杂的库存管理思想。其主要目标是确定最优的订购量,使得期望收益最大化。这个问题是库存控制和供应链管理理论的基础,涉及的不确定性和随机性为许多后续的管理决策问题提供了范式。随着研究的深入,报童问题的模型得到了不断的发展和扩展。从最初的单周期、单商品问题,逐步演化出多周期、多商品以及涉及供应链管理的复杂版本。例如:

多周期报童问题:扩展了报童问题的时间维度,报童可以多次订购商品,并根据之前的销售情况调整后续的订购量。
多商品报童问题:涉及多种商品的订购,每种商品有不同的成本结构和需求分布,报童需要在多商品之间进行综合考虑。
供应链环境中的报童问题:报童问题被应用于供应链管理中,研究如何协调供应商、制造商和零售商的库存管理决策。

1.2 报童问题的应用

报童问题作为经典库存管理问题,广泛应用于实际生活中的多种场景,尤其在零售、制造、物流和供应链管理领域具有重要应用。

零售业:在零售行业中,报童问题被用来帮助零售商决定每个销售周期应订购多少商品。特别是在面临季节性商品、限时促销活动等情况下,商品的需求具有较大的不确定性,零售商需要根据历史销售数据估计需求,并据此确定库存量。
报纸和杂志销售:正如其名字所示,报童问题可以直接应用于报纸和杂志销售中。由于报纸是一种快速贬值的商品,未售出的报纸几乎没有残值,因此报童面临的订购决策至关重要。
餐饮业:餐饮行业中,一些易腐产品(如每日烘焙的面包、蛋糕、寿司等)的生产和销售也可以使用报童模型。餐馆需要决定每天的备货量,过多的备货会导致浪费,而备货不足会导致顾客流失。
医疗资源管理:在医疗行业中,医院或诊所可以利用报童模型来确定库存水平,如疫苗、血液或其他医疗物资的库存。这些物资有一定的保质期,过度备货可能导致浪费,库存不足则可能影响患者的治疗。
电商与物流:电商平台经常需要根据历史销售数据和促销活动等因素来预测商品的需求,并决定仓库中的备货量。物流企业也可利用报童问题优化快递包裹、库存和运输资源的调配。

1.3 报童问题的分析意义

报童问题的核心在于处理需求不确定性带来的风险管理。通过合理的模型和分析,企业能够在不确定的需求环境中做出更加科学的库存决策。它具有以下几方面的重要意义:

帮助决策者平衡成本与收益:报童问题本质上是一个风险管理问题,要求决策者在不确定需求下,在短缺成本(订购不足带来的损失)和过量成本(订购过多带来的损失)之间做出权衡。这种方法为现实中的库存管理提供了强有力的理论支持。
降低库存管理成本:在库存管理中,过多或过少的库存都会带来额外的成本。通过合理使用报童问题的分析结果,企业可以在需求不确定的情况下,制定更为合理的库存策略,从而有效降低库存管理成本。
提高供应链效率:在供应链管理中,报童问题帮助企业提高整个供应链的协调性,减少供应链中各环节的浪费和延误,提高整体运营效率。例如,供应链上的零售商可以利用报童问题的决策方法来确定最优订货量,进而与上游供应商协同工作,实现供应链中的有效库存管理。
促进管理科学的发展:报童问题是运筹学和管理科学的一个经典问题,它为后续的很多研究问题奠定了理论基础。通过扩展和优化,报童问题衍生出了众多复杂的供应链管理模型和算法。今天的企业可以借助这些更为复杂的模型来应对日益复杂的市场环境和供应链挑战。

二、报童问题最优订货量

报童问题的核心在于如何确定最优订报量 Q 使得预期收益最大化。问题可以描述如下:

  • a 是每卖出一份报纸的利润,b 是未卖出的报纸带来的损失;
  • D 为每天的需求量(服从某一概率分布),报童订购Q 份报纸,则:
    • 如果 DQ,那么报童卖出 D 份报纸,收益为 D×a,没有卖出去的部分 QD 带来损失 (QD)×b
    • 如果 D>Q,报童卖出 Q 份报纸,收益为 Q×a,无损失。

报童问题的核心目标是选择订购量Q 以使得期望收益E[R(Q)] 最大化。

期望收益法

E[R(Q)]=k=0Qpk(ka(Qk)b)+k=Q+1pk(Qa)

其中,pk 是每天卖出 k 份报纸的概率。

边际分析法
为了使收益最大化,可以用边际分析法。根据条件:

  • F(Q)=P(DQ),即需求不超过 Q 的概率累积分布函数;
  • 最优订购量 Q 满足: F(Q)=aa+b,该公式称为 报童公式

基本符号与定义

  • Q:报童的订购量。
  • D:随机需求量,假设D是一个随机变量,有概率分布F(D)和概率密度函数f(D)
  • a:每售出一份报纸的利润。
  • b:每未售出一份报纸的损失(相当于报纸的成本)。
  • F(Q)=P(DQ):需求量D不超过订购量Q的概率累积分布函数。

收益的计算
根据报童的订购量Q和需求D的不同情况,报童的总收益可以分为以下两种情况:
当需求DQ:报童能够满足全部需求,售出D份报纸,利润为a×D,剩余的QD份报纸没有卖出,会产生损失b×(QD)。因此,当DQ时,总收益为:

R(Q,D)=a×Db×(QD)=(a+b)×Db×Q

当需求D>Q:报童订购的报纸不足以满足需求,全部Q份报纸都卖出,总利润为a×Q。因此,当D>Q时,总收益为:

R(Q,D)=a×Q

期望收益
根据上述两种情况,报童的期望收益可以表示为:

E[R(Q)]=0QR(Q,D)f(D)dD+QR(Q,D)f(D)dD

代入收益函数R(Q,D)的具体形式,可以分两部分求期望:
DQ时,收益为:

E[R(Q)]=0Q[(a+b)×Db×Q]f(D)dD

D>Q时,收益为:

E[R(Q)]=Qa×Qf(D)dD

报童公式的推导
对于连续型分布,要使报童的期望收益最大化,可以对Q求导,得到最优订购量Q的条件。期望收益对Q的导数为:

dE[R(Q)]dQ=(a+b)F(Q)b

要使期望收益最大化,设置导数等于0,即:

(a+b)F(Q)b=0

解得:F(Q)=ba+b
此时,F(Q)=P(DQ) 表示需求小于或等于 Q 的概率,这就给出了最优订购量Q所满足的条件。

对于离散型分布可用边际分析法,报童每多订购一份报纸,就会面临两个边际效益的权衡:
边际收益:如果多订购的报纸可以被需求满足,报童将获得每份报纸a元的边际利润。
边际损失:如果多订购的报纸没有被需求满足,报童将承担每份报纸b元的边际损失。
因此,在最优订购量 Q 处,边际收益与边际损失的预期是相等的,即:

P(DQ)a=P(D>Q)b

将其整理为累积分布函数形式,即:
F(Q)=aa+b

三、报童问题的仿真分析

3.1 连续型需求分布函数仿真

假设参数与分布

  • 每天需求D服从离散型分布,如泊松分布:

    P(D=k)=λkeλk!,k=0,1,2,

    其中λ是需求的期望值。
  • 假设 a=5 元,b=2元,需求的期望λ=10
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import poisson

# 参数设置
a = 5  # 每卖出一份报纸的利润
b = 2  # 每未卖出一份报纸的损失
lambda_ = 10  # 泊松分布的需求均值
max_Q = 30  # 最大订购量

# 泊松分布概率质量函数
def demand_probability(k, lambda_):
    return poisson.pmf(k, lambda_)

# 计算期望收益
def expected_profit(Q, a, b, lambda_):
    expected_profit = 0
    for k in range(Q + 1):
        p_k = demand_probability(k, lambda_)
        profit_if_k_sold = k * a - (Q - k) * b
        expected_profit += p_k * profit_if_k_sold
    for k in range(Q + 1, max_Q + 1):
        p_k = demand_probability(k, lambda_)
        profit_if_all_sold = Q * a
        expected_profit += p_k * profit_if_all_sold
    return expected_profit

# 模拟不同订购量的期望收益
Qs = np.arange(0, max_Q + 1)
profits = [expected_profit(Q, a, b, lambda_) for Q in Qs]

# 找到期望收益最大的订购量
optimal_Q = Qs[np.argmax(profits)]
print(f"最优订购量 Q*: {optimal_Q}")

# 绘制期望收益曲线
plt.plot(Qs, profits, marker='o')
plt.title('期望收益随订购量的变化')
plt.xlabel('订购量 Q')
plt.ylabel('期望收益')
plt.grid(True)
plt.show()
仿真图1 仿真图2
import simpy
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import poisson

# 参数设置
a = 5  # 每卖出一份报纸的利润
b = 2  # 每未卖出一份报纸的损失
lambda_ = 10  # 泊松分布的需求均值
num_days = 1000  # 仿真的天数
max_Q = 30  # 最大订购量

# 需求分布(泊松分布)
def demand(lambda_):
    return poisson.rvs(lambda_)

# 报童过程
def newspaper_selling(env, Q, a, b, lambda_, total_profits):
    while True:
        D = demand(lambda_)  # 每天的需求
        if D <= Q:
            profit = D * a - (Q - D) * b  # 卖出的量少于或等于订购量
        else:
            profit = Q * a  # 卖出的量多于订购量
        total_profits.append(profit)
        yield env.timeout(1)  # 每天的时间间隔为1天

# 仿真不同订购量下的收益
def simulate(Q, a, b, lambda_, num_days):
    env = simpy.Environment()
    total_profits = []
    env.process(newspaper_selling(env, Q, a, b, lambda_, total_profits))
    env.run(until=num_days)
    return np.mean(total_profits)  # 返回平均收益

# 模拟不同订购量的期望收益
Qs = np.arange(0, max_Q + 1)
profits = [simulate(Q, a, b, lambda_, num_days) for Q in Qs]

# 找到期望收益最大的订购量
optimal_Q = Qs[np.argmax(profits)]
print(f"最优订购量 Q*: {optimal_Q}")

# 绘制期望收益曲线
plt.plot(Qs, profits, marker='o')
plt.title('期望收益随订购量的变化 (SimPy仿真)')
plt.xlabel('订购量 Q')
plt.ylabel('平均收益')
plt.grid(True)
plt.show()

3.2 离散型需求分布函数仿真1

航空公司超量售票问题:某航空公司在A市到B市的航线上用波音737客机执行飞行任务。已知该机有效载客量为138人。按民用航空有关条例,旅客因有事或误机,机票可免费改签一次,此外也有在飞机起飞前退票的。为避免由此发生的空座损失,该航空公司决定每个航班超量售票(即每班售出票数为138+S张)。但由此会发生持票登机旅客多于座位数量的情况,这种情况下,航空公司规定,对超员旅客愿改乘本公司后续航班的,机票免费(即退回原机票款);若换乘其他航空公司航班的,按机票价的150%退款。据统计,前一类旅客占超员中的80%,后一类占20%。又据该公司长期统计,每个航班旅客退票和改签发生的人数i的概率p(i)如表所示。试确定该航空公司从A市到B市的航班每班应多售出的机票张数S, 使预期的获利最大。

i (退票和改签人数) 0 1 2 3 4 5
p(i) (概率) 0.18 0.25 0.25 0.16 0.12 0.04
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams

# 添加中文显示支持
rcParams['font.sans-serif'] = ['SimHei']  # 设置默认字体为SimHei
rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

# 参数设置
seat_capacity = 138  # 飞机座位数
ticket_price = 1000  # 每张票的收益
loss_per_overbooked = 1100  # 每名超售乘客的赔偿损失1000×0.8+1500×0.2=1100
S_max = 5  # 超售最大数目

# 新的需求概率分布,表示退票和改签人数
demand_probs = np.array([0.18, 0.25, 0.25, 0.16, 0.12, 0.04])
demand_values = np.arange(len(demand_probs))  # 对应的需求量为 [0, 1, 2, 3, 4, 5]

# 计算期望收益
def expected_profit(S, seat_capacity, ticket_price, loss_per_overbooked):
    expected_profit = 0
    for i in range(len(demand_probs)):
        demand = demand_values[i]  # 实际需求为退票和改签人数
        passengers = seat_capacity + S  # 实际售出的票数
        actual_passengers = passengers - demand  # 实际登机的乘客数

        if actual_passengers <= seat_capacity:  # 没有超售
            profit = actual_passengers * ticket_price
        else:  # 发生超售
            num_overbooked = actual_passengers - seat_capacity
            profit = seat_capacity * ticket_price - num_overbooked * loss_per_overbooked
        
        expected_profit += demand_probs[i] * profit
    return expected_profit

# 模拟不同超售量的期望收益
S_values = np.arange(0, S_max + 1)
profits = [expected_profit(S, seat_capacity, ticket_price, loss_per_overbooked) for S in S_values]

# 找到期望收益最大的超售量
optimal_S = S_values[np.argmax(profits)]
print(f"最优超售量 S*: {optimal_S}")

# 绘制期望收益曲线
plt.plot(S_values, profits, marker='o')
plt.title('期望收益随超售量的变化')
plt.xlabel('超售票数 S')
plt.ylabel('期望收益')
plt.grid(True)
plt.show()

3.3 离散型需求分布函数仿真2

若每一份报纸的批发价为l,零售价为m,退回价为n,并且m>l>n,因此小强每售出一份报纸赚(ml),退回一份赔(ln)。小强从报站批发报纸过多卖不完时,必然会赔钱,每张报纸赔(ln)元;然后多卖一张报纸却可以多赚(ml)元。设m=3,l=2,n=1,小强每天卖出报纸数量基于市场经验可知满足如下分布:

数量 100 200 300 400 500 600 700
概率 0.05 0.1 0.2 0.3 0.2 0.1 0.05
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams

# 设置中文字体以防止图形不显示中文
rcParams['font.sans-serif'] = ['SimHei']  # 设置字体为黑体
rcParams['axes.unicode_minus'] = False  # 正常显示负号

# 参数设定
a = 3  # 零售价
b = 2  # 批发价
c = 1  # 退货价

# 每天的销售数量和对应的概率
quantities = np.array([100, 200, 300, 400, 500, 600, 700])
probabilities = np.array([0.05, 0.1, 0.2, 0.2, 0.2, 0.15, 0.1])

# 计算给定订货量和销售量下的收益
def calculate_profit_for_demand(q, d):
    if d <= q:
        # 正收益 = 销售产生的收益 (a-b)
        positive_profit = (a - b) * d
        # 负收益 = 退货造成的损失 (b-c)
        negative_profit = (b - c) * (q - d)
        # 总收益 = 正收益 - 负收益
        profit = positive_profit - negative_profit
    else:
        # 如果销售量大于订货量,只计算订货量部分的正收益
        profit = (a - b) * q
    
    # 输出每个订货量和销售量下的收益
    print(f"订货量: {q}, 销售量: {d}, 收益: {profit:.2f}")
    return profit

# 计算不同订货量下的期望收益
def calculate_expected_profit(q):
    total_profit = 0
    for d, p in zip(quantities, probabilities):
        profit = calculate_profit_for_demand(q, d)  # 计算给定销售量下的收益
        total_profit += p * profit  # 按需求概率加权
    return total_profit

# 计算不同订货量下的期望收益
order_quantities = np.arange(100, 801, 100)
expected_profits = [calculate_expected_profit(q) for q in order_quantities]

# 绘制收益曲线图
plt.figure(figsize=(10, 6))
plt.plot(order_quantities, expected_profits, marker='o')
plt.title('报童问题收益曲线')
plt.xlabel('订货量')
plt.ylabel('期望收益')
plt.grid(True)

# 标出最优订货量
optimal_order = order_quantities[np.argmax(expected_profits)]
optimal_profit = max(expected_profits)
plt.axvline(optimal_order, color='r', linestyle='--', label=f'最优订货量: {optimal_order}')
plt.legend()

plt.show()

print(f"最优订货量为: {optimal_order}, 对应的期望收益为: {optimal_profit:.2f} 元")

总结

报童问题从其简单的模型出发,逐渐发展成为库存管理、供应链管理领域中的基础工具。它不仅帮助企业解决了实际生产生活中的订购和库存管理问题,还在管理科学领域产生了广泛而深远的影响。通过解决需求不确定性带来的挑战,报童问题的应用提高了供应链效率,降低了成本,并为企业的运营决策提供了强有力的理论支持。在现代商业社会中,它仍然具有广泛的应用前景。

参考文献

  1. 随机规划 | 报童问题(The Newsvendor Problem,附MATLAB代码)
  2. 【数学建模】基于matlab模拟报童问题仿真
posted @   郝hai  阅读(1356)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
点击右上角即可分享
微信分享提示