用excel VBA模擬醉漢走路過(guò)程

李永樂(lè)老師曾經(jīng)講過(guò)一個(gè)“醉漢走路”問(wèn)題。這個(gè)問(wèn)題常被用來(lái)解釋布朗運(yùn)動(dòng)※。說(shuō)一個(gè)醉漢,已經(jīng)意識(shí)模糊,但任然可以走路。只不過(guò),他走路的方向是完全隨機(jī)的。那么經(jīng)過(guò)一定的步數(shù)以后,醉漢是傾向于原地打轉(zhuǎn),還是離開(kāi)起點(diǎn)一定距離呢?
※布朗運(yùn)動(dòng)指的是極小的微粒(比如一?;ǚ郏?,受到周圍眾多處于無(wú)序熱運(yùn)動(dòng)中的粒子(比如分子)擾動(dòng),“被迫”進(jìn)行無(wú)序的運(yùn)動(dòng)
?
答案是,醉漢傾向于離開(kāi)起點(diǎn)一定距離,而不是原地打轉(zhuǎn)。具體的請(qǐng)大家移步李永樂(lè)老師的頻道。在這里,我只想介紹一種用excel及其附帶的VBA工具,來(lái)模擬“醉漢走路”過(guò)程的方法。
首先,舞臺(tái)在excel的表格上。也就是說(shuō),最小的單元就是一個(gè)單元格。每一次移動(dòng)就是隨機(jī)地移動(dòng)到相鄰的8個(gè)單元格中的其中1個(gè)(移動(dòng)到斜對(duì)角也算1格)。限定的步數(shù)為100步。所以,為了有足夠大的空間,把起始位置設(shè)定到坐標(biāo)為(101,101)※的單元格上,把它用黃色邊框標(biāo)記。這樣即使出現(xiàn)始終往一個(gè)方向運(yùn)動(dòng)的情況也不會(huì)超出范圍。
※VBA里描述單元格坐標(biāo)的格式為(行序號(hào),列序號(hào))。如第3行,B列就是(3,2)
然后,每到達(dá)一個(gè)單元格,就讓其中顯示數(shù)字,記錄到達(dá)過(guò)的次數(shù)※。同時(shí)也為這些單元格上色,到達(dá)次數(shù)越多顏色就越深。最后給終點(diǎn)標(biāo)記上紅色。
※起點(diǎn)在一開(kāi)始時(shí)不會(huì)被計(jì)數(shù),但之后會(huì)正常計(jì)數(shù)。所以起點(diǎn)格中的數(shù)字代表返回起點(diǎn)的次數(shù)

上圖顯示了1次運(yùn)行的結(jié)果??梢钥闯?,這次運(yùn)動(dòng)沒(méi)有返回過(guò)起點(diǎn),在上方的一片區(qū)域(數(shù)字為4,5,6)盤旋了多次,最后位置在右下方的一個(gè)曾經(jīng)到達(dá)過(guò)的位置。因?yàn)槭请S機(jī)※的運(yùn)動(dòng),每一次運(yùn)行的結(jié)果會(huì)不一樣。當(dāng)然,步數(shù)也可以被設(shè)置為更大的值,只不過(guò)要將起點(diǎn)設(shè)置得遠(yuǎn)一些以獲得更大的舞臺(tái)。下面是代碼。
※像VBA這樣弱小的編程工具里是不存在真隨機(jī)的,這里只是簡(jiǎn)單模擬
Sub walk2()
?Dim x As Integer
?Dim y As Integer
?Dim i As Integer
?Dim temp As Double
?
?Range(Cells(1, 1), Cells(200, 200)).Interior.Pattern = xlNone
?Range(Cells(1, 1), Cells(200, 200)).ClearContents
?‘清除范圍內(nèi)的底色和數(shù)值
?x = 101
?y = 101
‘設(shè)置起點(diǎn)坐標(biāo)。這里y代表行,x代表列
?For i = 1 To 100
? temp = 8 * Rnd
? If temp < 1 Then
?? y = y - 1
?? ElseIf temp >= 1 And temp < 2 Then
?? x = x + 1
?? y = y - 1
?? ElseIf temp >= 2 And temp < 3 Then
?? x = x + 1
?? ElseIf temp >= 3 And temp < 4 Then
?? x = x + 1
?? y = y + 1
?? ElseIf temp >= 4 And temp < 5 Then
?? y = y + 1
?? ElseIf temp >= 5 And temp < 6 Then
?? x = x - 1
?? y = y + 1
?? ElseIf temp >= 6 And temp < 7 Then
?? x = x - 1
?? ElseIf temp >= 7 And temp <= 8 Then
?? x = x - 1
?? y = y - 1
? End If
?‘獲取1個(gè)0~8的隨機(jī)數(shù),根據(jù)數(shù)值決定下一格的位置。重復(fù)100次
? Cells(y, x).Value = Cells(y, x).Value + 1
?‘給到達(dá)過(guò)的格子計(jì)數(shù)
? Cells(y, x).Interior.Color = RGB(255 - 17 * Cells(y, x).Value, 255 - 17 * Cells(y, x).Value, 255 - 17 * Cells(y, x).Value)
‘給到達(dá)過(guò)的格子上色
?Next i
?Cells(y, x).Interior.Color = RGB(255, 0, 0)
‘給終點(diǎn)格上紅色
?
End Sub
?
當(dāng)然,這個(gè)運(yùn)行過(guò)程相當(dāng)快。如果想要看到過(guò)程的話,需要在每一步之間加入延時(shí)。VBA并沒(méi)有自帶的延時(shí),需要自己寫(xiě)一個(gè)。方法有很多,我提供一個(gè)以獲取系統(tǒng)時(shí)間的方式實(shí)現(xiàn)的代碼。可以實(shí)現(xiàn)1秒的延時(shí)。
Sub delay1000()
?Dim t1 As Single
?
?t1 = Timer
?Do
? DoEvents
?Loop While Timer - t1 < 1
?
End Sub
?
用“call delay1000”這個(gè)語(yǔ)句調(diào)用延時(shí),加在之前的“Next i”的上一句就行了。
然后,為了可視化效果更好一點(diǎn),在加數(shù)字和上色的同時(shí),還可以令單元格被選中。用“cells(y,x).select”這個(gè)語(yǔ)句。
運(yùn)行的效果我已經(jīng)錄成視頻了。

?
最后,我想用這種方式來(lái)計(jì)算運(yùn)動(dòng)100步后,終點(diǎn)與起點(diǎn)距離的數(shù)學(xué)期望的近似值※。只不過(guò),因?yàn)榍懊姘岩苿?dòng)到斜對(duì)角也看作1步了,所以計(jì)算距離的方法需要做些改變,以最小的格子數(shù)來(lái)表示。
※也就是多次運(yùn)行結(jié)果的平均值。運(yùn)行次數(shù)越多就越逼近數(shù)學(xué)期望
如果初始坐標(biāo)是(a,b),終點(diǎn)坐標(biāo)是(x,y)。那么距離就是:
Max(Abs(a-x),Abs(b-y))
比如下面這個(gè)例子,行方向上相距3,列方向上相距7,所以距離就是較大的7 。

因?yàn)椴恍枰@示出來(lái),所以可以略去部分代碼。再加上一層循環(huán),以及計(jì)算最終結(jié)果部分。代碼如下。
Sub walk5()
?Dim x As Integer
?Dim y As Integer
?Dim i As Integer
?Dim j As Integer
?Dim temp As Double
?Dim d As Double
?
?For j = 1 To 10000
? x = 101
? y = 101
? For i = 1 To 100
?? temp = 8 * Rnd
?? If temp < 1 Then
??? y = y - 1
??? ElseIf temp >= 1 And temp < 2 Then
??? x = x + 1
??? y = y - 1
??? ElseIf temp >= 2 And temp < 3 Then
??? x = x + 1
??? ElseIf temp >= 3 And temp < 4 Then
??? x = x + 1
??? y = y + 1
??? ElseIf temp >= 4 And temp < 5 Then
??? y = y + 1
??? ElseIf temp >= 5 And temp < 6 Then
??? x = x - 1
??? y = y + 1
??? ElseIf temp >= 6 And temp < 7 Then
??? x = x - 1
??? ElseIf temp >= 7 And temp <= 8 Then
??? x = x - 1
??? y = y - 1
?? End If
? Next i
? If Abs(x - 101) >= Abs(y - 101) Then
?? d = d + Abs(x - 101) / 10000
?? Else
?? d = d + Abs(y - 101) / 10000
? End If
?Next j
?MsgBox d
?
End Sub
?
我運(yùn)行了幾次,基本上是在9.7~9.8之間。與布朗運(yùn)動(dòng)的規(guī)律是接近的。布朗運(yùn)動(dòng)的期望是步數(shù)的平方根。運(yùn)動(dòng)100步,距離的期望就是10步。運(yùn)動(dòng)10000步,距離的期望就是100步。你也可以copy這個(gè)代碼,將步數(shù)(i的值)修改成10000,看看結(jié)果會(huì)不會(huì)是100附近(運(yùn)行起來(lái)可能有點(diǎn)費(fèi)時(shí))。