3.4 模拟算法策略
3.4 模拟算法策略
有些问题很难建立枚举、递归等算法,甚至建立不了数学模型,但可以根据问题的描述,用程序模拟某种现象或规律,从而跟踪出结果。
根据模拟对象的不同特点,可以把计算机模拟分为决定性模拟和随机性模拟两大类。决定性模拟是对决定性现象进行的模拟,其所模拟的事件按照固有的规律发生发展,并且最终有明确的结果。在这种题目中,由于数学模型中各种参数的变化多半是有规律的,所以算法设计一般不是很困难。随机模拟是模拟随机现象,由于随机现象中至少有一个不确定的因素,因此在模拟中,必须建立一个用随机值来模拟事件的进程,在随机模拟过程中,通过修改问题的各种参数,进而观察变更这些参数所引起的状态变化。一般情况下,题目给出某一概率,设计者利用随机函数区设定在某一范围的随即值,将符合概率的随机值作为参数。然后根据这一模拟模型展开算法设计。随机模拟的关键是在概率已知的条件下,如何确定随机值产生的范围。这个随机值设计得好,模拟效果就好。
用计算机模仿实际事物,产生一些类似真实情况的信号、数据、图像等,使人们从中获得期望的资料,这种工作称作计算机模拟,又称仿真。计算机模拟是计算机的一项重要应用,几乎各种实际情况,如物理实验、交通管理、棋牌游戏、飞行员训练等,都可以用计算机进行模拟。
过程:randomize产生内部随机数时的初始化过程。
函数:random([w])返回随机数,如果w缺省,返回(0,1)之间的随机数,w有值且不为0,返回[0,w]之间的整型随机数。
如:random(6)随机产生0,1,2,3,4,5整数。
调random函数时,Turbo Pascal先从randseed常量中读取随机数发生序号,根据这个序号产生特定的随机数序列,例如:
randseed:=1;
FOR I:=1 to 5 do
Writeln(random:5:2);
它给出的随机数序列为0.03 0.86 0.20 0.67
而对于randseed:=2,对应的序列为0.06 0.69 0.54 0.34 0.07
当randseed未赋值时,randseed:=0;利用randseed这一性质,可对数据和文件加密。当程序需要任意无序随机数时,可用randomize过程来初始化,然后再调用random即可。
例18 有一个圆盘,盘内存36个槽。当一个小球在盘内滚动时,可能落入任一个槽内,这些槽的编号为1~36,其中槽号为奇数时为红色,槽号为偶数时为黄色,编一个程序模拟球滚动100次,计算落入每个槽中的次数及落入红槽,黄槽各有多少次。
算法分析
设定一维数组,用来存放落入每个槽中的次数。
定义形式TYPE类型标识符为Array[下标类型]Of元素类型。
数组的类型定义和变量定义可以合在一起。
例如:Var a:array[1..36]of integer
设定A[1],…,A[36]为36个单元来装入每个槽中的次数;
X——存放随机落入的槽号;
T——存放落入黄槽的次数;
S——存放落入红槽的次数;
X的取值范围:[1,36]的整数。
可用语句X:=random(36+1);实现。
参考程序
uses crt;
var a:array[1..36]of integer;
x,t,s,i:integer;
f:boolean;
begin
clrscr;
randomize;
t:=0;
s:=0;
for i:=1to 36 do a[i]:=0;
for i:=1to 100 do
begin
x:=random(36)+1;
f:=odd(x); {F奇数函数,X为奇数,F为真。}
if f then s:=s+1 else t:=t+1;
a[x]:=a[x]+1;
end;
for i:=1to 36 do writeln(i:5,a[i]:5);
writeln('hong',s,'huang',t:5);
end.
例19 抛一均匀正方体木块,该木块有三面是红色、二面是绿色、一面是蓝色,仅计木块落地后向上一面的颜色。试编一个程序模拟这个试验过程,并打印出每次记录的结果和经过1000次试验后向上一面出现的各种颜色的次数。
算法分析
设X为存放随机产生落地时向上一面的颜色,X的取值范围:[0,5]的整数。
可用语句X:=Random(6)来实现。
若X为0,1,2时,则记录红色,计数器R:=r+1;
若X为3,4时,则记录绿色,计数器g:=g+1;
若X为5时,则记录蓝色,计数器b:=b+1;
参考程序
uses crt;
var x,i,r,g,b:integer;
begin
clrscr;
randomize;
for i:=1to 1000 do
begin
x:=random(6);
case x of
0,1,2:begin r:=r+1;write('r') end;
3,4:begin g:=g+1;write('g')end;
5:begin b:=b+1;write('b')end;
end;
end;
writeln;
writeln('r=',r,'g=',g,'b=',b);
end.
例20.阅览室问题
一个阅览室每天都要接待大批读者。阅览室开门的时间是0,关门的时间是T。美味读者的到达时间都不一样,并且想要阅读的刊物不超过五本。没为读者心里对自己想看的刊物都有一个排位,到达之后他会先去找自己最想看的刊物,如果找不到则去找次想看的刊物。如果找不到任何他想看的刊物,他会开始等待,直到有一本以上他想的刊物被人放回原处。当然,他会先去拿其中自己最想看的刊物。当他看完某一本刊物后,就把他放回原处 ,接着去找自己没看过的最想看的刊物。如此下去,知道看完那其所有的刊物为止。矛盾出现在两个人同时想要拿同一本刊物时。阅览室为了避免读者之间出现争执,做了一个决定,读者每次开始等待时先去服务台做一次登记。如果两个人都同时想要一本刊物那么先登记的读者将得到这本刊物如果两个人同时登记,那么先到达的读者将得到刊物,没得到的人就只能去找其他的刊物,阅览室关门时所用读者都被强迫离开阅览室,不再允许继续阅读。
现在阅览室想做一个调查,你被要求写一个程序来模拟这个过程,计算出所有刊物被阅读的总次数。当某个读者开始阅读某本勘误时,该刊物的阅读次数就加一,无论这本刊物有没有被看完。
输入包括了多个测试数据。每个测试数据开头是两个整数T和n(1<=n<=100),分别表示图书馆关门时间和读者总数。接下来按读者的到达时间先后依次给出了每位读者的具体描述。每个读者描述开头是一个整数k(1<=k<=5),表示该读者想要看的刊物数目。之后跟着2k个整数按照读者想要阅读的刊物的顺序以次给出了刊物的描述。其中第2*I-1个整数表示刊物的编号s(0=<s<=1000),第2*I个整数表示该读者读完这本书所需的时间。
对于每个测试数据,在单独一行里输出所有刊物被阅读的总次数。
输入样例:
10 4
1
2 1 4 2 5
3
1 2 4
7
3 2 21 3 3 2
9
1 4 2
输出样例:
5
参考程序:
program aa; //事件:一个人到达或看完书
const maxn=100;
type integer=longint;
tdata=record //人
c:integer;
b,t:array[1..5] of integer;
end;
tevent=record //事件
time,b,v:integer;
end;
var
timelimit,nowtime:integer; //关门和当前时间
answer:integer; //借书次数
book:array[0..1000] of boolean; //book[I]表示是否被借走
n:integer; //人数
data:array[1..maxn] of tdata; //每个人的信息
wait_size:integer; //等待队列大小
wait:array[1..maxn] of integer; //等待队列
event_size:integer; //事件大小
event:array[1..maxn] of tevent; //事件队列
procedure add_event(time,v,b:integer); //加入一个事件
var //time=时间,v=人的编号,b借的书的编号
i:integer; temp:tevent;
begin
if (time>=timelimit) then //超过关门时间不考虑
exit;
event_size:=event_size+1;
event[event_size].time:=time;
event[event_size].v:=v;
event[event_size].b:=b;
for i:=event_size downto 2 do //调整合适位置
if (event[i].time<event[i-1].time)or (event[i].time=event[i-1].time)
and(event[i].v<event[i-1].v)then
begin
temp:=event[i-1];
event[i-1]:=event[i];
event[i]:=temp;
end;
end;
procedure add_wait(v:integer); //插入一个等待人
begin
wait_size:=wait_size+1; //插在队尾
wait[wait_size]:=v;
end;
function borrow(v:integer):boolean; //借书
var i,j:integer;
begin
for i:=1 to data[v].c do
if (not book[data[v].b[i]]) then
begin
inc(answer);
book[data[v].b[i]]:=true;
add_event(nowtime+data[v].t[i],v,data[v].b[i]);
data[v].c:=data[v].c-1;
for j:=i to data[v].c do
begin
data[v].b[j]:=data[v].b[j+1];
data[v].t[j]:=data[v].t[j+1];
end;
borrow:=true;
exit;
end;
borrow:=false;
end;
procedure init; //读入数据,初始化
var
i,j,reach_time:integer;
begin
answer:=0;
event_size:=0;
wait_size:=0;
fillchar(book,sizeof(book),false);
fillchar(wait,sizeof(wait),false);
read(timelimit,n);
for i:=1 to n do
begin
read(reach_time,data[i].c);
for j:=1 to data[i].c do
read(data[i].b[j],data[i].t[j]);
add_event(reach_time,i,1000);
end;
end;
procedure solve; //模拟过程
var
i,temp:integer;
begin
while event_size>0 do
begin nowtime:=event[1].time;
while (event_size>0)and(event[1].time=nowtime) do //处理这一时刻所有事件
begin
book[event[1].b]:=false;
if (data[event[1].v].c>0) then
add_wait(event[1].v); //可以先把他加入等待队列,然后和所有等待的人一起处理
event_size:=event_size-1;
for i:=1 to event_size do
event[i]:=event[i+1];
end;
temp:=0;
for i:=1 to wait_size do //处理等待队列
if (not borrow(wait[i])) then
begin
temp:=temp+1;
wait[temp]:=wait[i];
end;
wait_size:=temp;
end;
end;
begin
init;
solve;
writeln(answer);
end.