蒙特卡罗 Monte Carlo 模拟

来源:数学建模清风学习内容整理



引例:布丰投针实验

利用蒙特卡罗模拟来对投针问题处理

  • π 的估计

在这里插入图片描述
在这里插入图片描述

%%  蒙特卡罗用于布丰投针实验

%% (1)预备知识
%  rand(m,n)函数产生由在[0,1]之间均匀分布的随机数组成的m行n列的矩阵(或称为数组)。
rand(5,4)
%     0.8300    0.1048    0.2396    0.4398
%     0.5663    0.1196    0.8559    0.5817
%     0.9281    0.2574    0.3013    0.9355
%     0.3910    0.3173    0.2108    0.1676
%     0.3645    0.4372    0.8819    0.9232
rand(3) % 若只给一个输入,则会生成一个方阵
%     0.1709    0.4951    0.0541
%     0.9374    0.8500    0.6155
%     0.2400    0.3156    0.5741
% a + rand(m,n)*(b-a) 可以输出在[a,b]之间均匀分布的随机数组成的m行n列的矩阵。
-2 + rand(3,2) * (2 - (-2))  % 输出在[-2,2]之间均匀分布的随机数组成的3行2列的矩阵。
%    -1.2743    0.6013
%    -1.3084    0.0766
%     1.5075    0.7563
 unifrnd(-2,2,3,2)

%% (2)代码部分
l =  0.520;     % 针的长度(任意给的)
a = 1.314;    % 平行线的宽度(大于针的长度l即可)
n = 1000000;    % 做n次投针试验,n越大求出来的pi越准确
m = 0;    % 记录针与平行线相交的次数
x = rand(1, n) * a / 2 ;   % 在[0, a/2]内服从均匀分布随机产生n个数, x中每一个元素表示针的中点和最近的一条平行线的距离
phi = rand(1, n) * pi;    % 在[0, pi]内服从均匀分布随机产生n个数,phi中的每一个元素表示针和最近的一条平行线的夹角
% axis([0,pi, 0,a/2]);   box on;  % 画一个坐标轴的框架,x轴位于0-pi,y轴位于0-a/2, 并打开图形的边框
for i=1:n  % 开始循环,依次看每根针是否和直线相交
    if x(i) <= l / 2 * sin(phi (i))     % 如果针和平行线相交
        m = m + 1;    % 那么m就要加1
%         plot(phi(i), x(i), 'r.')   % 模仿书上的那个图,横坐标为phi,纵坐标为x , 用红色的小点进行标记
%         hold on  % 在原来的图形上继续绘制
    end
end
p = m / n;    % 针和平行线相交出现的频率
mypi = (2 * l) / (a * p);  % 我们根据公式计算得到的pi
disp(['蒙特卡罗方法得到pi为:', num2str(mypi)])


%% (3) 由于一次模拟的结果具有偶然性,因此我们可以重复100次后再来求一个平均的pi
result = zeros(100,1);  % 初始化保存100次结果的矩阵(生成100行1列的零矩阵 )
l =  0.520;     a = 1.314;
n = 1000000;    
for num = 1:100
    m = 0;  
    x = rand(1, n) * a / 2 ;
    phi = rand(1, n) * pi;
    for i=1:n
        if x(i) <= l / 2 * sin(phi (i))
            m = m + 1;
        end
    end
    p = m / n;
    mypi = (2 * l) / (a * p);
    result(num) = mypi;  % 把求出来的myphi保存到结果矩阵中
end
mymeanpi = mean(result);  % 计算result矩阵中保存的100次结果的均值
disp(['蒙特卡罗方法得到pi为:', num2str(mymeanpi)])




% % 注意:代码文件仅供参考,一定不要直接用于自己的数模论文中
% % 国赛对于论文的查重要求非常严格,代码雷同也算作抄袭

蒙特卡罗⽅法于20世纪40年代美国在第⼆次世界⼤战中研制原⼦弹的“曼哈顿计划”计划的成员S.M.乌拉姆和J.
冯·诺伊曼⾸先提出。数学家冯·诺伊曼⽤驰名世界的赌城—摩纳哥的Monte Carlo—来命名这种⽅法,为它蒙上了⼀
层神秘⾊彩。在这之前,蒙特卡罗⽅法就已经存在。1777年,法国Buffon提出⽤投针实验的⽅法求圆周率,这被认
为是蒙特卡罗⽅法的起源


蒙特卡罗模拟概述


  • 定义

蒙特卡罗⽅法⼜称统计模拟法,是⼀种随机模拟⽅法,以概率和统计理论⽅法为基础的⼀种计算⽅法,是使⽤随机数(或更常⻅的伪随机数)来解决很多计算问题的⽅法。将所求解的问题同⼀定的概率模型相联系,⽤电⼦计算机实现统计模拟或抽样,以获得问题的近似解。为象征性地表明这⼀⽅法的概率统计特征,故借⽤赌城蒙特卡罗Monte Carlo命名。

  • 原理

由⼤数定理可知,当样本容量⾜够⼤时,事件的发⽣频率即为其概率


蒙特卡罗模拟的应用


01 三门问题

在这里插入图片描述

%%  蒙特卡罗用于模拟三门问题

clear;clc
%% (1)预备知识
% randi([a,b],m,n)函数可在指定区间[a,b]内随机取出大小为m*n的整数矩阵
randi([1,5],5,8) %在区间[1,5]内随机取出大小为5*8的整数矩阵
%      2     5     4     5     3     1     4     2
%      3     3     1     5     4     2     1     2
%      4     1     3     3     2     2     5     1
%      5     3     3     4     4     5     4     4
%      4     2     3     4     2     4     2     4
randi([1,5])   %在区间[1,5]内随机取出1个整数
%     3

% 字符串的连接方式:(1)['字符串1','字符串2'] (2)strcat('字符串1','字符串2') 
['数学建模','学习交流']
strcat('数学建模','学习交流')
% 输出都是 数学建模学习交流

% num2str函数:将数值转换为字符串 (第一期视频第一讲)
mystr = num2str(1234)  % 注意观察工作区的mystr这个变量的值
disp([num2str(1234),'祝大家平安夜平平安安'])  % disp函数是输出函数


%% (2)代码部分(在成功的条件下的概率)

n = 100000;  % n代表蒙特卡罗模拟重复次数
a = 0;  % a表示不改变主意时能赢得汽车的次数
b = 0;  % b表示改变主意时能赢得汽车的次数
for i= 1 : n  % 开始模拟n次
    x = randi([1,3]);  % 随机生成一个1-3之间的整数x表示汽车出现在第x扇门后
    y = randi([1,3]);  % 随机生成一个1-3之间的整数y表示自己选的门
    % 下面分为两种情况讨论:x=y和x~=y
    if x == y   % 如果x和y相同,那么我们只有不改变主意时才能赢
        a = a + 1;     b = b + 0;
    else  % x ~= y ,如果x和y不同,那么我们只有改变主意时才能赢
        a = a + 0;     b = b +1;
    end
end
disp(['蒙特卡罗方法得到的不改变主意时的获奖概率为:', num2str(a/n)]);
disp(['蒙特卡罗方法得到的改变主意时的获奖概率为:', num2str(b/n)]);


%% (3)考虑失败情况的代码(无条件概率)

n = 100000;  % n代表蒙特卡罗模拟重复次数
a = 0;  % a表示不改变主意时能赢得汽车的次数
b = 0;  % b表示改变主意时能赢得汽车的次数
c = 0;  % c表示没有获奖的次数
for i= 1 : n  % 开始模拟n次
    x = randi([1,3]);  % 随机生成一个1-3之间的整数x表示汽车出现在第x扇门后
    y = randi([1,3]);  % 随机生成一个1-3之间的整数y表示自己选的门
    change = randi([0, 1]); % change =0  不改变主意,change = 1 改变主意
    % 下面分为两种情况讨论:x=y和x~=y
    if x == y   % 如果x和y相同,那么我们只有不改变主意时才能赢
        if change == 0  % 不改变主意
        	a = a + 1; 
        else  % 改变了主意
            c= c+1;
        end
    else  % x ~= y ,如果x和y不同,那么我们只有改变主意时才能赢
         if change == 0  % 不改变主意
        	c = c + 1; 
        else  % 改变了主意
            b= b + 1;
         end
    end
end
disp(['蒙特卡罗方法得到的不改变主意时的获奖概率为:', num2str(a/n)]);
disp(['蒙特卡罗方法得到的改变主意时的获奖概率为:', num2str(b/n)]);
disp(['蒙特卡罗方法得到的没有获奖的概率为:', num2str(c/n)]);

在这里插入图片描述
在这里插入图片描述


02 模拟排队论问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

%%  蒙特卡罗模拟排队问题

%% (1)预备知识
% normrnd(MU,SIGMA):生成一个服从正态分布(MU参数代表均值,SIGMA参数代表标准差,方差开根号是标准差)的随机数
normrnd(10,2)  % 均值为10 标准差为2(方差为4)的正态分布随机数
% exprnd(M)表示生成一个均值为M的指数分布随机数(其对应的参数为1/M)
exprnd(5)  % 均值为5的指数分布随机数(对应的参数为0.2)
% mean函数是用来求解均值的函数(第一期视频第五讲)
mean([1,2,3])

% tic函数和toc函数可以用来返回代码运行的时间,例如我们要计算一段代码的运行时间,就可以在这段代码前加上tic,在这段代码后加上toc
tic
a = 2^100
toc

%% (2)模型中出现的变量的说明
% x(i)表示第i-1个客户和第i个客户到达的间隔时间,服从参数为0.1的指数分布
% y(i)表示第i个客户的服务持续时间,服从均值为10方差为4(标准差为2)的正态分布 (若小于1则按1计算)
% c(i)表示第i个客户的到达时间,那么c(i) = c(i-1) + x(i),初始值c0=0
% b(i)表示第i个客户开始服务的时间
% e(i)表示第i个客户结束服务的时间,初始值e0=0
% 第i个客户结束服务的时间 = 第i个客户开始服务的时间 + 第i个客户的服务持续时间
% 即:e(i) = b(i) + y(i)
% 第i个客户开始服务的时间取决于该客户的到达时间和上一个客户结束服务的时间
% 即:b(i) = max(c(i),e(i-1)),初始值b1=c1;
% 第i个客户等待的时间 = 第i个客户开始服务的时间 - 第i个客户到达银行的时间
% 即:wait(i) = b(i) - c(i)
% w表示所有客户等待时间的总和
% 假设一天内银行最终服务了n个顾客,那么客户的平均等待时间t = w/n

%% (3)问题1的代码
clear
tic  %计算tic和toc中间部分的代码的运行时间
i = 1;  % i表示第i个客户,最开始取i=1
w = 0;  % w用来表示所有客户等待的总时间,初始化为0
e0 = 0;  c0 = 0;   % 初始化e0和c0为0
x(1) = exprnd(10);  % 第0个客户(假想的)和第1个客户到达的时间间隔
c(1) = c0 + x(1);  % 第1个客户到达的时间
b(1) = c(1); % 第1个客户的开始服务的时间
while b(i) <= 480  % 开始设置循环,只要第i个顾客开始服务的时间(时刻)小于480,就可以对其服务(银行每天工作8小时,折换为分钟就是480分钟)
    y(i) = normrnd(10,2); % 第i个客户的服务持续时间,服从均值为10方差为4(标准差为2)的正态分布
    if y(i) < 1  % 根据题目的意思:若服务持续时间不足一分钟,则按照一分钟计算
        y(i) = 1;
    end
    e(i) = b(i) + y(i); % 第i个客户结束服务的时间 = 第i个客户开始服务的时间 + 第i个客户的服务持续时间
    wait(i) = b(i) - c(i); % 第i个客户等待的时间 = 第i个客户开始服务的时间 - 第i个客户到达银行的时间
    w = w + wait(i); % 更新所有客户等待的总时间
    i = i + 1; % 增加一名新的客户
    x(i) = exprnd(10); % 这位新客户和上一个客户到达的时间间隔
    c(i) = c(i-1) + x(i); % 这位新客户到达银行的时间 = 上一个客户到达银行的时间 + 这位新客户和上一个客户到达的时间间隔
    b(i) = max(c(i),e(i-1)); % 这个新客户开始服务的时间取决于其到达时间和上一个客户结束服务的时间
end
n = i-1; % n表示银行一天8小时一共服务的客户人数
t = w/n; % 客户的平均等待时间
disp(['银行一天8小时一共服务的客户人数为: ',num2str(n)])
disp(['客户的平均等待时间为: ',num2str(t)])
toc  %计算tic和toc中间部分的代码的运行时间

%% (4)问题2的代码
clear
tic  %计算tic和toc中间部分的代码的运行时间
day = 100;  % 假设模拟100天
n = zeros(day,1); % 初始化用来保存每日接待客户数结果的矩阵
t = zeros(day,1); % 初始化用来保存每日客户平均等待时长的矩阵
for k = 1:day
    i = 1;  % i表示第i个客户,最开始取i=1
    w = 0;  % w用来表示所有客户等待的总时间,初始化为0
    e0 = 0;  c0 = 0;   % 初始化e0和c0为0
    x(1) = exprnd(10);  % 第0个客户(假想的)和第1个客户到达的时间间隔
    c(1) = c0 + x(1);  % 第1个客户到达的时间
    b(1) = c(1); % 第1个客户的开始服务的时间
    while b(i) <= 480  % 开始设置循环,只要第i个顾客开始服务的时间(时刻)小于480,就可以对其服务(银行每天工作8小时,折换为分钟就是480分钟)
        y(i) = normrnd(10,2); % 第i个客户的服务持续时间,服从均值为10方差为4(标准差为2)的正态分布
        if y(i) < 1  % 根据题目的意思:若服务持续时间不足一分钟,则按照一分钟计算
            y(i) = 1;
        end
        e(i) = b(i) + y(i); % 第i个客户结束服务的时间 = 第i个客户开始服务的时间 + 第i个客户的服务持续时间
        wait(i) = b(i) - c(i); % 第i个客户等待的时间 = 第i个客户开始服务的时间 - 第i个客户到达银行的时间
        w = w + wait(i); % 更新所有客户等待的总时间
        i = i + 1; % 增加一名新的客户
        x(i) = exprnd(10); % 这位新客户和上一个客户到达的时间间隔
        c(i) = c(i-1) + x(i); % 这位新客户到达银行的时间 = 上一个客户到达银行的时间 + 这位新客户和上一个客户到达的时间间隔
        b(i) = max(c(i),e(i-1)); % 这个新客户开始服务的时间取决于其到达时间和上一个客户结束服务的时间
    end
    n(k) = i-1; % n(k)表示银行第k天服务的客户人数
    t(k) = w/n(k); % t(k)表示该银行第k天客户的平均等待时间
end
disp([num2str(day),'个工作日中,银行每日平均服务的客户人数为: ',num2str(mean(n))])
disp([num2str(day),'个工作日中,银行每日客户的平均等待时间为: ',num2str(mean(t))])
toc  %计算tic和toc中间部分的代码的运行时间

03 有约束的非线性规划问题

在这里插入图片描述

  • 例题
    在这里插入图片描述
  • 可以先进行放缩简化计算过程
    在这里插入图片描述
  • matlab代码部分:可以先进行一次蒙特卡罗模拟,然后缩小范围后再进行一次蒙特卡洛模拟得到更精确的结果
%%  蒙特卡罗求解有约束的非线性规划问题
% max f(x) = x1*x2*x3
% s.t.
% (1) -x1+2*x2+2*x3>=0
% (2) x1+2*x2+2*x3<=72
% (3) x2<=20 & x2>=10
% (4) x1-x2 == 10

%% (1)预备知识
%  (1) format long g  可以将Matlab的计算结果显示为一般的长数字格式(默认会保留四位小数,或使用科学计数法)
5/7
5895*514100
format long g
5/7
5895*514100
%  (2)unifrnd(a,b,m,n)可以输出在[a,b]之间均匀分布的随机数组成的m行n列的矩阵。(等价于 a + rand(m,n)*(b-a))
unifrnd(0,5,4,3)
%           4.07361843196589          3.16179623112705          4.78753417717149
%            4.5289596853781         0.487702024997048          4.82444267599638
%           0.63493408146753          1.39249109433524         0.788065408387741
%            4.5668792806951          2.73440759602492          4.85296390880308


%%%%%%%%% (2)代码部分     %%%%%%%%%%%
clc,clear;
tic %计算tic和toc中间部分的代码的运行时间
n=10000000; %生成的随机数组数
x1=unifrnd(20,30,n,1);  % 生成在[20,30]之间均匀分布的随机数组成的n行1列的向量构成x1
x2=x1 - 10;
x3=unifrnd(-10,16,n,1);  % 生成在[-10,16]之间均匀分布的随机数组成的n行1列的向量构成x3
fmax=-inf; % 初始化函数f的最大值为负无穷(后续只要找到一个比它大的我们就对其更新)
for i=1:n
    x = [x1(i), x2(i), x3(i)];  %构造x向量, 这里千万别写成了:x =[x1, x2, x3]
    if (-x(1)+2*x(2)+2*x(3)>=0)  &  (x(1)+2*x(2)+2*x(3)<=72)     % 判断是否满足条件
        result = x(1)*x(2)*x(3);  % 如果满足条件就计算函数值
        if  result  > fmax  % 如果这个函数值大于我们之前计算出来的最大值
            fmax = result;  % 那么就更新这个函数值为新的最大值
            X = x;  % 并且将此时的x1 x2 x3保存到一个变量中
        end
    end
end
disp(strcat('蒙特卡罗模拟得到的最大值为',num2str(fmax)))
disp('最大值处x1 x2 x3的取值为:')
disp(X)
toc %计算tic和toc中间部分的代码的运行时间


%% (3)缩小范围重新模拟得到更加精确的取值
clc,clear;
tic %计算tic和toc中间部分的代码的运行时间
n=10000000; %生成的随机数组数
x1=unifrnd(22,23,n,1);  % 生成在[22,23]之间均匀分布的随机数组成的n行1列的向量构成x1
x2=x1 - 10;
x3=unifrnd(11,13,n,1);  % 生成在[11,13]之间均匀分布的随机数组成的n行1列的向量构成x3
fmax=-inf; % 初始化函数f的最大值为负无穷(后续只要找到一个比它大的我们就对其更新)
for i=1:n
    x = [x1(i), x2(i), x3(i)];  %构造x向量, 这里千万别写成了:x =[x1, x2, x3]
    if (-x(1)+2*x(2)+2*x(3)>=0)  &  (x(1)+2*x(2)+2*x(3)<=72)     % 判断是否满足条件
        result = x(1)*x(2)*x(3);  % 如果满足条件就计算函数值
        if  result  > fmax  % 如果这个函数值大于我们之前计算出来的最大值
            fmax = result;  % 那么就更新这个函数值为新的最大值
            X = x;  % 并且将此时的x1 x2 x3保存到一个变量中
        end
    end
end
disp(strcat('蒙特卡罗模拟得到的最大值为',num2str(fmax)))
disp('最大值处x1 x2 x3的取值为:')
disp(X)
toc %计算tic和toc中间部分的代码的运行时间

04 书店选择问题(0-1)规划问题

在这里插入图片描述
在这里插入图片描述

%% 书店买书问题的蒙特卡罗的模拟
%% (1)预备知识
% (1)unique函数: 剔除一个矩阵或者向量的重复值,并将结果按照从小到大的顺序排列
% adj. 唯一的; 独一无二的 [ju’ni:k]
unique([1 2 5; 6 8 9;2 4 6])
unique([5 6 8 8 4 1 6 2 2 4 8 4 5 6])

% (2)randi([a,b],m,n)函数可在指定区间[a,b]内随机取出大小为m*n的整数矩阵
randi([-5,5],2,6)

%% (2)代码求解
min_money = +Inf; % 初始化最小的花费为无穷大,后续只要找到比它小的就更新
min_result = randi([1, 6],1,5); % 初始化五本书都在哪一家书店购买,后续我们不断对其更新
%若min_result = [5 3 6 2 3],则解释为:第1本书在第5家店买,第2本书在第3家店买,第3本书在第6家店买,第4本书在第2家店买,第5本书在第3家店买
n = 100000; % 蒙特卡罗模拟的次数
M = [18 39 29 48 59
24 45 23 54 44
22 45 23 53 53
28 47 17 57 47
24 42 24 47 59
27 48 20 55 53]; % m_ij 第j本书在第i家店的售价
freight = [10 15 15 10 10 15]; % 第i家店的运费
for k = 1:n % 开始循环
result = randi([1, 6],1,5); % 在1-6这些整数中随机抽取一个1*5的向量,表示这五本书分别在哪家书店购买
index = unique(result); % 在哪些商店购买了商品,因为我们等下要计算运费
money = sum(freight(index)); % 计算买书花费的运费
% 计算总花费:刚刚计算出来的运费 + 五本书的售价
for i = 1:5
money = money + M(result(i),i);
end
if money < min_money % 判断刚刚随机生成的这组数据的花费是否小于最小花费,如果小于的话
min_money = money % 我们更新最小的花费
min_result = result % 用这组数据更新最小花费的结果
end
end
min_money % 18+39+48+17+47+20
min_result


05 导弹追踪问题(物理问题的模拟)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

%%  蒙特卡罗用于模拟导弹追击问题
% 注意,模拟导弹追击问题更像是一种仿真模拟的方法。这里本质上没有用到随机数,因此严格意义上不能称为蒙特卡罗。
clear;clc
%% (1)预备知识
% mod(m,n)表示求m/n的余数
mod(8,3)
mod(1000,50)

% 设置横纵坐标的范围并标上字符
x = 1:0.01:3;
y = x .^ 2;
plot(x,y)  % 画出x和y的图形
axis([0 3 0 10])  % 设置横坐标范围为[0, 3] 纵坐标范围为[0, 10]
pause(3)  % 暂停3秒后再继续接下来的命令
text(2,4,'清风')  % 在坐标为(2,4)的点上标上字符串:清风
close % 关闭图形窗口



%% (2) 代码求解
% 1. 不画追击的示意图
clear;clc
v=200; % 任意给定B船的速度(后期我们可以再改的)
dt=0.0000001; % 定义时间间隔
x=[0,20]; % 定义导弹和B船的横坐标分别为x(1)和x(2)
y=[0,0]; % 定义导弹和B船的纵坐标分别为y(1)和y(2)
t=0; % 初始化导弹击落B船的时间
d=0; % 初始化导弹飞行的距离
m=sqrt(2)/2;   % 将sqrt(2)/2定义为一个常量,使后面看起来很简洁
dd=sqrt((x(2)-x(1))^2+(y(2)-y(1))^2); % 导弹与B船的距离
while(dd>=0.001)  % 只要两者的距离足够大,就一直循环下去。(两者距离足够小时表示导弹击中,这里的临界值要结合dt来取,否则可能导致错过交界处的情况)
    t=t+dt; % 更新导弹击落B船的时间
    d=d+3*v*dt; % 更新导弹飞行的距离
    x(2)=20+t*v*m;  y(2)=t*v*m;   % 计算新的B船的位置 (注:m=sqrt(2)/2)
    dd=sqrt((x(2)-x(1))^2+(y(2)-y(1))^2);  % 更新导弹与B船的距离
    tan_alpha=(y(2)-y(1))/(x(2)-x(1));   % 计算斜率,即tan(α)
    cos_alpha=sqrt(1/(1+tan_alpha^2));   % sec(α)^2 = (1+tan(α)^2)
    sin_alpha=sqrt(1-cos_alpha^2);  % sin(α)^2 +cos(α)^2 = 1
    x(1)=x(1)+3*v*dt*cos_alpha;   y(1)=y(1)+3*v*dt*sin_alpha; % 计算新的导弹的位置
    if d>50  % 导弹的有效射程为50个单位
        disp('导弹没有击中B船');
        break;  % 退出循环
    end
    if d<=50 & dd<0.001   % 导弹飞行的距离小于50个单位且导弹和B船的距离小于0.001(表示击中)
        disp(['导弹飞行',num2str(d),'单位后击中B船'])
        disp(['导弹飞行的时间为',num2str(t*60),'分钟'])
    end
end

% 2. 画追击的示意图
clear;clc
v=200; % 任意给定B船的速度(后期我们可以再改的)
dt=0.0000001; % 定义时间间隔
x=[0,20]; % 定义导弹和B船的横坐标分别为x(1)和x(2)
y=[0,0]; % 定义导弹和B船的纵坐标分别为y(1)和y(2)
t=0; % 初始化导弹击落B船的时间
d=0; % 初始化导弹飞行的距离
m=sqrt(2)/2;   % 将sqrt(2)/2定义为一个常量,使后面看起来很简洁
dd=sqrt((x(2)-x(1))^2+(y(2)-y(1))^2); % 导弹与B船的距离
for i=1:2
    plot(x(i),y(i),'.k','MarkerSize',1);  % 画出导弹和B船所在的坐标,点的大小为1,颜色为黑色(k),用小点表示
    grid on;  % 打开网格线
    hold on;  % 不关闭图形,继续画图
end
axis([0 30 0 10])  % 固定x轴的范围为0-30  固定y轴的范围为0-10
k = 0;  % 引入一个变量  为了控制画图的速度(因为Matlab中画图的速度超级慢)
while(dd>=0.001)  % 只要两者的距离足够大,就一直循环下去。(两者距离足够小时表示导弹击中,这里的临界值要结合dt来取,否则可能导致错过交界处的情况)
    t=t+dt; % 更新导弹击落B船的时间
    d=d+3*v*dt; % 更新导弹飞行的距离
    x(2)=20+t*v*m;  y(2)=t*v*m;   % 计算新的B船的位置 (注:m=sqrt(2)/2)
    dd=sqrt((x(2)-x(1))^2+(y(2)-y(1))^2);  % 更新导弹与B船的距离
    tan_alpha=(y(2)-y(1))/(x(2)-x(1));   % 计算斜率,即tan(α)
    cos_alpha=sqrt(1/(1+tan_alpha^2));   % 利用公式:sec(α)^2 = (1+tan(α)^2)  计算出cos(α)
    sin_alpha=sqrt(1-cos_alpha^2);  % 利用公式: sin(α)^2 +cos(α)^2 = 1  计算出sin(α)
    x(1)=x(1)+3*v*dt*cos_alpha;   y(1)=y(1)+3*v*dt*sin_alpha;   % 计算新的导弹的位置
    k = k +1 ;  
    if mod(k,500) == 0   % 每刷新500次时间就画出下一个导弹和B船所在的坐标  mod(m,n)表示求m/n的余数
        for i=1:2
            plot(x(i),y(i),'.k','MarkerSize',1);
            hold on; % 不关闭图形,继续画图
        end
        pause(0.001);  % 暂停0.001s后再继续下面的操作
    end
    if d>50  % 导弹的有效射程为50个单位
        disp('导弹没有击中B船');
        break;  % 退出循环
    end
    if d<=50 & dd<0.001   % 导弹飞行的距离小于50个单位且导弹和B船的距离小于0.001(表示击中)
        disp(['导弹飞行',num2str(d),'个单位后击中B船'])
        disp(['导弹飞行的时间为',num2str(t*60),'分钟'])
    end
end


06 旅行商问题(Traveling Saleman Problem,TSP)

在这里插入图片描述

%% TSP(旅行商问题)
%% (1)预备知识
plot([1,2],[5,10],'-o') % 画出一条线段,x范围是[1, 2] ,y范围是[5,10]
text(1.5,7.5,'清风') % 在坐标(1.5,7.5)处标上文本:清风
close

% randperm函数的用法
randperm(5)  % 生成1-5组成的一个随机序列(类似于洗牌的操作)
%      3     5     1     2     4
%      1     4     5     3     2


%% (2)代码求解
clear;clc
% 只有10个城市的简单情况
 coord =[0.6683 0.6195 0.4    0.2439 0.1707 0.2293 0.5171 0.8732 0.6878 0.8488 ;
               0.2536 0.2634 0.4439 0.1463 0.2293 0.761  0.9414 0.6536 0.5219 0.3609]' ;  % 城市坐标矩阵,n行2列
% 38个城市,TSP数据集网站(http://www.tsp.gatech.edu/world/djtour.html) 上公测的最优结果6656。
 % coord = [11003.611100,42102.500000;11108.611100,42373.888900;11133.333300,42885.833300;11155.833300,42712.500000;11183.333300,42933.333300;11297.500000,42853.333300;11310.277800,42929.444400;11416.666700,42983.333300;11423.888900,43000.277800;11438.333300,42057.222200;11461.111100,43252.777800;11485.555600,43187.222200;11503.055600,42855.277800;11511.388900,42106.388900;11522.222200,42841.944400;11569.444400,43136.666700;11583.333300,43150.000000;11595.000000,43148.055600;11600.000000,43150.000000;11690.555600,42686.666700;11715.833300,41836.111100;11751.111100,42814.444400;11770.277800,42651.944400;11785.277800,42884.444400;11822.777800,42673.611100;11846.944400,42660.555600;11963.055600,43290.555600;11973.055600,43026.111100;12058.333300,42195.555600;12149.444400,42477.500000;12286.944400,43355.555600;12300.000000,42433.333300;12355.833300,43156.388900;12363.333300,43189.166700;12372.777800,42711.388900;12386.666700,43334.722200;12421.666700,42895.555600;12645.000000,42973.333300];

n = size(coord,1);  % 城市的数目

figure(1)  % 新建一个编号为1的图形窗口
plot(coord(:,1),coord(:,2),'o');   % 画出城市的分布散点图
for i = 1:n
    text(coord(i,1)+0.01,coord(i,2)+0.01,num2str(i))   % 在图上标上城市的编号(加上0.01表示把文字的标记往右上方偏移一点)
end
hold on % 等一下要接着在这个图形上画图的


d = zeros(n);   % 初始化两个城市的距离矩阵全为0
for i = 2:n  
    for j = 1:i  
        coord_i = coord(i,:);   x_i = coord_i(1);     y_i = coord_i(2);  % 城市i的横坐标为x_i,纵坐标为y_i
        coord_j = coord(j,:);   x_j = coord_j(1);     y_j = coord_j(2);  % 城市j的横坐标为x_j,纵坐标为y_j
        d(i,j) = sqrt((x_i-x_j)^2 + (y_i-y_j)^2);   % 计算城市i和j的距离
    end
end
d = d+d';   % 生成距离矩阵的对称的一面

min_result = +inf;  % 假设最短的距离为min_result,初始化为无穷大,后面只要找到比它小的就对其更新
min_path = [1:n];   % 初始化最短的路径就是1-2-3-...-n
N = 10000000;  % 蒙特卡罗模拟的次数
for k = 1:N  % 开始循环
    result = 0;  % 初始化走过的路程为0
    path = randperm(n);  % 生成一个1-n的随机打乱的序列
    for i = 1:n-1  
        result = d(path(i),path(i+1)) + result;  % 按照这个序列不断的更新走过的路程这个值
    end
    result = d(path(1),path(n)) + result;  % 别忘了加上从最后一个城市返回到最开始那个城市的距离
    if result < min_result  % 判断这次模拟走过的距离是否小于最短的距离,如果小于就更新最短距离和最短的路径
        min_path = path;
        min_result = result
    end
end
min_path
min_path = [min_path,min_path(1)];   % 在最短路径的最后面加上一个元素,即第一个点(我们要生成一个封闭的图形)
n = n+1;  % 城市的个数加一个(紧随着上一步)
for i = 1:n-1 
     j = i+1;
    coord_i = coord(min_path(i),:);   x_i = coord_i(1);     y_i = coord_i(2); 
    coord_j = coord(min_path(j),:);   x_j = coord_j(1);     y_j = coord_j(2);
    plot([x_i,x_j],[y_i,y_j],'-')    % 每两个点就作出一条线段,直到所有的城市都走完
    pause(0.5)  % 暂停0.5s再画下一条线段
    hold on
end

07 自然常数e的估计

在这里插入图片描述

%%  蒙特卡罗的方法去估计自然常数e
%% (1)预备知识
% (1)randperm函数的用法
randperm(5)  % 生成1-5组成的一个随机序列
%      3     5     1     2     4
%      1     4     5     3     2

% (2)find函数的用法 (第一期视频第一讲)
% 假设a是一个向量,那么find(a)可以用来返回这个向量中非零元素的下标,如果a中所有元素都为0,则返回空值
find([1,5,6,0,8,0,-5])  %      1     2     3     5     7
find([0,0,0,0,0])  %   空的 1×0 double 行矢量

% (3) 矩阵(或向量)和常量的比较运算可返回逻辑矩阵(或向量)(元素全为0和1)
[1,5,6,0,8,0,-5] > 0      %    1   1   1   0   1   0   0
[1,5,6,0,8,0,-5] == 0    %    0   0   0   1   0   1   0

% (4) isempty(A)函数可以用来判断A是否为空, 如果A为空, isempty(A) 返回逻辑值1(true),否则返回逻辑值0(false)。
isempty(find([0,0,0,0,0]))   %    1   返回1为空矩阵
isempty(find([0,1,0,0,0]))   %    0   返回0不为空矩阵
isempty([0,0,0,0,0])  % 注意,别搞错啦,它不是空矩阵(空矩阵是指里面没有元素)


%% (2)代码部分
clear;clc

tic  %计算tic和toc中间部分的代码的运行时间
%
n = 1000000;  % 蒙特卡洛的次数(理论上n取得越大,计算出来的结果越精确)
m = 0;   % 每个人拿到的都不是自己卡片的次数(频数)
people = 100;   % 假设一共有100个人玩这个游戏 (任给的)
for i = 1: n  % 开始循环
    if isempty(find(randperm(people) - [1:people] == 0))  % 如果每个人拿到的都不是自己的卡片
        m = m + 1;  % 那么次数就加1
    end
end
frequency = m / n;  % 每个人拿到的都不是自己卡片的频率(概率)
disp(['自然常数e的蒙特卡罗模拟值为:', num2str(1 / frequency)])  % 注:自然常数真实值约为2.7182
%
toc  %计算tic和toc中间部分的代码的运行时间


08 武器升级花费问题

在这里插入图片描述

%%  蒙特卡罗解决武器升级问题
% 现在有一把神器,初始为1级,可免费领取(即价值为0),可花费金币对其升级,每次10000金币,最多升到5级。
% 给定一个升级的概率表(见讲义),问:5级神器价值多少金币?(即升级到5级神器平均的花费)

%% (1)预备知识
% 以一定的概率产生随机数  randsrc(m,n,[alphabet; prob])
% m和n表示生成的随机数矩阵的行数和列数
% alphabet表示需要产生的随机数的数字,用一个行向量表示
% prob表示这些数字出现的概率大小,用一个行向量表示,向量长度和alphabet向量要完全相同, 且这些概率的和要为1
% 比如:要产生1、4、 6这三个数。它们分别出现的概率为 0.1、0.2、0.7,如何设计程序使得按照这个概率产生10个随机数呢?
alphabet = [1 4 6]; prob = [0.1 0.2 0.7];
randsrc(10,1,[alphabet; prob])

%% (2)参考答案
clear;clc
tic  %计算tic和toc中间部分的代码的运行时间
% 升级的成功率储存在success矩阵中,以第一行和第三行为例,表格的解释:
%  1级武器强化时,有20%概率升到2级,10%概率升到3级,5%概率升到4级,65%概率不变。
%  3级武器强化时,10%概率跌到1级,20%概率跌到2级,20%概率升到4级,10%概率升到5级
success = [0.65 0.2  0.1  0.05  0;
                 0.25 0.4  0.2  0.1    0.05;
                 0.1   0.2  0.4  0.2    0.1;
                 0      0.1  0.3  0.4    0.2] ;
n = 10000;  % 蒙特卡罗模拟的次数
MONEY = zeros(n,1);  % 初始化用来存储每次蒙特卡罗计算出来的表示强化费用的向量
for i = 1:n
    rank = 1; % 武器的初始等级
    money = 0;  %花费的钱数,初始化为0
    alphabet = [1 2 3 4 5];   % 用来表示五个等级
    while rank ~= 5  % 只要等级不是5级, 就一直循环下去
        prob =success(rank,:);    % 令生成随机数的概率为第rank行
        rank = randsrc(1,1,[alphabet; prob]);   % 生成一个在1-5中的随机数,表示强化后的等级
        money = money + 10000;  % 更新强化的费用
    end
    MONEY(i) = money;  % 将这次蒙特卡罗的结果保存到MONEY向量中
end
disp(['将武器升级到5级的平均花费为:',num2str(mean(MONEY))])
toc  %计算tic和toc中间部分的代码的运行时间


09 非线性规划的模拟

在这里插入图片描述

%%  蒙特卡罗求解非线性规划问题
% min f(x) =2*(x1^2)+x2^2-x1*x2-8*x1-3*x2
% s.t.
% (1) 3*x1+x2>9
% (2) x1+2*x2<16
% (3) x1>0 & x2>0

%% (1)初次寻找最小值的代码
clc,clear;
format long g   %可以将Matlab的计算结果显示为一般的长数字格式(默认会保留四位小数,或使用科学计数法)
tic %计算tic和toc中间部分的代码的运行时间
n=10000000; %生成的随机数组数
x1=unifrnd(0,16,n,1);  % 生成在[0,16]之间均匀分布的随机数组成的n行1列的向量构成x1
x2=unifrnd(0,8,n,1);  % 生成在[0,8]之间均匀分布的随机数组成的n行1列的向量构成x2
fmin=+inf; % 初始化函数f的最小值为正无穷(后续只要找到一个比它小的我们就对其更新)
for i=1:n
    x = [x1(i), x2(i)];  %构造x向量, 这里千万别写成了:x =[x1, x2]
    if (3*x(1)+x(2)>9)  &  (x(1)+2*x(2)<16)     % 判断是否满足条件
        result = 2*(x(1)^2)+x(2)^2-x(1)*x(2)-8*x(1)-3*x(2);  % 如果满足条件就计算函数值
        if  result  < fmin  % 如果这个函数值小于我们之前计算出来的最小值
            fmin = result;  % 那么就更新这个函数值为新的最小值
            X = x;  % 并且将此时的x1 x2 保存到相应的变量中
        end
    end
end
disp(strcat('蒙特卡罗模拟得到的最小值为',num2str(fmin)))
disp('最小值处x1 x2的取值为:')
disp(X)
toc %计算tic和toc中间部分的代码的运行时间


%% (2)缩小范围重新模拟得到更加精确的取值
clc,clear;
tic %计算tic和toc中间部分的代码的运行时间
n=10000000; %生成的随机数组数
x1=unifrnd(2,3,n,1);  % 生成在[2,3]之间均匀分布的随机数组成的n行1列的向量构成x1
x2=unifrnd(2,3,n,1);  % 生成在[2,3]之间均匀分布的随机数组成的n行1列的向量构成x2
fmin=+inf; % 初始化函数f的最小值为正无穷(后续只要找到一个比它小的我们就对其更新)
for i=1:n
    x = [x1(i), x2(i)];  %构造x向量, 这里千万别写成了:x =[x1, x2]
    if (3*x(1)+x(2)>9)  &  (x(1)+2*x(2)<16)     % 判断是否满足条件
        result = 2*(x(1)^2)+x(2)^2-x(1)*x(2)-8*x(1)-3*x(2);  % 如果满足条件就计算函数值
        if  result  < fmin  % 如果这个函数值小于我们之前计算出来的最小值
            fmin = result;  % 那么就更新这个函数值为新的最小值
            X = x;  % 并且将此时的x1 x2 保存到相应的变量中
        end
    end
end
disp(strcat('蒙特卡罗模拟得到的最小值为',num2str(fmin)))
disp('最小值处x1 x2的取值为:')
disp(X)
toc %计算tic和toc中间部分的代码的运行时间



10 选择决策方案的模拟

在这里插入图片描述

%% 选择决策方案的模拟
% 某设备上安装有四只型号规格完全相同的电子管,已知电子管寿命为1000--2000小时之间的均匀分布(假定为整数)。
% 当电子管损坏时有两种维修方案,一是每次更换损坏的那一只;二是当其中一只损坏时四只同时更换。
% 已知更换时间为换一只时需1小时,4只同时换为2小时。
% 更换时机器因停止运转每小时的损失为20元,又每只电子管价格10元,
% 试用模拟方法决定哪一个方案经济合理?

%% (1)预备知识
% randi([a,b],m,n)  随机生成m*n的矩阵,矩阵中的每个元素都是[a,b]中的随机整数
randi([1, 5],3,2)
randi([1, 5])  % 不写m*n代表只生成1个随机数

% find函数的用法
% find函数的用法在第一期视频:层次分析法那一节讲过,我们当时找最大特征值的位置
a = [2 3 5 1 7 5];
find(a)  % 找到a中所有非0元素的位置
find(a == 5)  % 找到a中等于5的元素的位置
find(a == 5,1)  % 找到a中第一个等于5的元素的位置
find(a == min(a))   % 找到a中最小元素的位置

%% (2)代码部分
clear;clc
T = 100000000;   % T表示模拟的总时间(单位为小时)
t = 0;   % 初始化当前时刻为0小时
c1 = 0; c2 = 0;  % 初始化两种方案的总花费都为0

%%  方案一
life = randi([1000,2000],1,4);  % 随机生成四个电子管的寿命,假设为整数
while t < T  % 只要现在的时刻没有超过总时刻,就不断循环下去
    result = min(life);  % 找出寿命最短的那一个电子管的寿命
    t = t+result+1;  % 现在的时间更改到有电子管损坏的时刻(加上1表示更换电子管需要花费的时间)
    c1 = c1 + 20 * 1 +10;  % 更新方案一的花费 
    k = find(life == result,1);   % 找到哪一个电子管是坏的
    life = life - result -1; % 更新所有电子管的寿命(这里不减去1也是可以的,减少了1也无所谓,对结果的影响很小)    
    life(k) = randi([1000,2000]);  % 把坏掉的那个电子管的寿命重置
end

%%  方案二
t = 0;   % 初始化当前时刻为0小时
while t < T  % 只要现在的时刻没有超过总时刻,就不断循环下去
    life = randi([1000,2000],1,4); % 随机生成四个电子管的寿命,假设为整数
    result = min(life); % 找出寿命最小的那一个电子管的寿命
    t = t+result+2;  % 现在的时间更改到有电子管损坏的时刻(加上2表示更换所有电子管需要花费的时间)
    c2 =c2 + 20 * 2 +40;  % 更新方案二的花费 
end

%% 两种方案的花费
c1
c2
  • 6
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苗半里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值