### 9.1.2 隨機問題的建模與模擬
現實中有許多不確定的事件,稱為隨機事件。例如拋一枚硬幣,結果是正面朝上還是反
面朝上,這是不確定的。研究隨機事件的數學方法是統計,例如經過大量統計試驗可以得出 結論:拋硬幣時正面朝上和反面朝上的可能性是相等的,各占 50%。注意,說硬幣正面朝 上和反面朝上的可能性各占 50%,并不意味著拋硬幣試驗將得到“正,反,正,反,…” 的結果,完全有可能出現一連串的正面或一連串的反面。
既然現實問題中存在隨機事件,用計算機解決這類問題時就需要為隨機事件建模,即程 序能夠模擬隨機事件的發生。例如,假設程序 P 能夠模擬拋硬幣并顯示每次拋硬幣的結果(“正”或“反”),則 P 應該具有這樣的特性:每一次顯示的結果是不可預測的,但多次運 行 P 之后“正”、“反”出現的次數應該是差不多的。可以設想 P 中有這樣的語句:
```
if 模擬拋硬幣的結果是正面:
print "正"
else:
print "反"
```
這里 if 后面的條件必須有時為真有時為假,但無法預測每一次運行時的真假;而多次運行 后,條件為真為假的次數應該基本相等。
我們知道,計算機是確定性的機器,它總是按照預定的指令行事。對于一個程序,只要 程序的初始輸入是一樣的,那么程序的運行結果就是確定的、可預測的。就拿程序 9.1 來說,盡管它產生了看上去不可預測的數值序列,但那并非真正的隨機,因為按照程序 9.1 中的數 學式去計算,從相同的 x 開始必能得到完全一樣的數值序列。所以,程序 9.1 所用的數學式 不能用于模擬隨機事件,我們需要更好的能產生隨機數的方法。
隨機數生成函數
計算機科學家對隨機數生成問題有很成熟的研究,他們設計了一些數學公式,通過計算
這些公式就能得到隨機數。不過,既然是用確定的數學公式計算出來的數值,那就不可能是 數學意義上的隨機,因此準確的名稱實際上是“偽隨機數”。偽隨機數在實際應用中完全可 以當作真隨機數來使用,因為它具有真隨機數的統計分布特性,簡單地說就是“看上去”是 隨機的。
Python 語言提供了一個標準庫模塊 random,該模塊中定義了若干種偽隨機數生成函數。 這些偽隨機數生成函數的計算與模塊導入的時間(精度非常高)有關,因此每次運行函數所 產生的數值是不可預測的。random 模塊中用得最多的隨機數生成函數是 randrange 和 random。
randrange 函數生成一個給定范圍內的整型偽隨機數,范圍由 randrange 的參數指定,具 體格式和 range 函數一樣。例如,randrange(1,3)隨機地產生 1 或 2,randrange(1,7)返回范圍 [1,2,3,4,5,6]中的某個數值,而 randrange(2,100,2)返回小于 100 的某個偶數。例如:
```
>>> from random import randrange
>>> for i in range(20):
print randrange(1,3),
1 2 2 2 1 1 2 2 2 1 1 2 1 2 2 1 1 1 1 1
>>> for i in range(20):
print randrange(1,7),
1 5 1 5 2 3 2 2 3 2 4 2 6 3 1 2 1 1 1 5
```
注意,由于試驗次數較少(20 次),randrange 所生成的數值并未如我們期望的那樣均勻分布, 但隨著試驗次數的增加,會發現 randrange 產生的值都具有差不多的出現次數。
random 函數可用來生成浮點型偽隨機數,確切地說,該函數生成[0,1)區間中的浮點數。
random 不需要提供參數。例如:
```
>>> from random import random
>>> for i in range(5):
print random()
0.35382204835
0.997559174002
0.672268961152
0.889307826404
0.246100521527
```
注意,random 模塊名與 random 函數名恰巧相同,不要因此而誤用。
模擬
有了隨機數生成函數,我們就可以來模擬隨機事件了。以拋硬幣問題為例,前面我們給 出了如下形式的代碼:
```
if 模擬拋硬幣的結果是正面:
print "正"
else:
print "反"
```
并且指出 if 后面的條件的真假應該是不可預測、均勻分布的。
考慮 randrange(1,3):該函數產生隨機數 1 或 2,每一次調用到底生成什么值是不可預測 的,并且大量調用后兩個數值出現的機會是一樣的。據此,randrange(1,3) == 1 正是我們所 需要的條件,此條件每一次計算時的真假是隨機的,但長遠來看真假情形各占 50%。將這 個條件代入上面的條件語句,即得
```
if randrange(1,3) == 1:
print "正"
else:
print "反"
```
這樣,我們就通過調用合適的隨機數生成函數的方式模擬了隨機事件,這種模擬方法稱為蒙特卡洛方法。 類似地,擲骰子也是現實中常見的隨機問題,如果希望在程序中模擬擲骰子,可以這樣做:
```
value = randrange(1,7) print "你擲出的點數是:",value
```
再看個例子,兩個運動員打乒乓球,誰能贏呢?勝負自然取決于球員的技術水平,但又 并非水平高的人必然贏,畢竟體育比賽和天時地利人和等各種因素有關。既然比賽結果有隨 機性,我們就可以利用蒙特卡洛方法來模擬比賽。假設 A、B 兩個球員相互之間的勝率大致 是 55%對 45%,那么他們打一次比賽(比賽的單位可以是 1 分、1 局或 1 盤,在此并不重要) 的結果可以用如下代碼模擬:
```
if random() < 0.55:
print "A wins."
else:
print "B wins."
```
這里,由于 random()生成的隨機數均勻地分布在[0,1)區間內,所以有 55%的值落在 0.55 的 左邊,即 random() < 0.55 為真的可能性為 55%,為假(即 random() >= 0.55)的可能性為 45%, 這就恰當地模擬了 A 和 B 的勝率。注意,random() = 0.55 時應該算作 B 贏,因為 random() 生成的隨機數包含 0 但不包含 1。
- 前言
- 第 1 章 計算與計算思維
- 1.1 什么是計算?
- 1.1.1 計算機與計算
- 1.1.2 計算機語言
- 1.1.3 算法
- 1.1.4 實現
- 1.2 什么是計算思維?
- 1.2.1 計算思維的基本原則
- 1.2.2 計算思維的具體例子
- 1.2.3 日常生活中的計算思維
- 1.2.4 計算思維對其他學科的影響
- 1.3 初識 Python
- 1.3.1 Python 簡介
- 1.3.2 第一個程序
- 1.3.3 程序的執行方式
- 1.3.4 Python 語言的基本成分
- 1.4 程序排錯
- 1.5 練習
- 第 2 章 用數據表示現實世界
- 2.1 數據和數據類型
- 2.1.1 數據是對現實的抽象
- 2.1.1 常量與變量
- 2.1.2 數據類型
- 2.1.3 Python 的動態類型*
- 2.2 數值類型
- 2.2.1 整數類型 int
- 2.2.2 長整數類型 long
- 2.2.3 浮點數類型 float
- 2.2.4 數學庫模塊 math
- 2.2.5 復數類型 complex*
- 2.3 字符串類型 str
- 2.3.1 字符串類型的字面值形式
- 2.3.2 字符串類型的操作
- 2.3.3 字符的機內表示
- 2.3.4 字符串類型與其他類型的轉換
- 2.3.5 字符串庫 string
- 2.4 布爾類型 bool
- 2.4.1 關系運算
- 2.4.2 邏輯運算
- 2.4.3 布爾代數運算定律*
- 2.4.4 Python 中真假的表示與計算*
- 2.5 列表和元組類型
- 2.5.1 列表類型 list
- 2.5.2 元組類型 tuple
- 2.6 數據的輸入和輸出
- 2.6.1 數據的輸入
- 2.6.2 數據的輸出
- 2.6.3 格式化輸出
- 2.7 編程案例:查找問題
- 2.8 練習
- 第 3 章 數據處理的流程控制
- 3.1 順序控制結構
- 3.2 分支控制結構
- 3.2.1 單分支結構
- 3.2.2 兩路分支結構
- 3.2.3 多路分支結構
- 3.3 異常處理
- 3.3.1 傳統的錯誤檢測方法
- 3.3.2 傳統錯誤檢測方法的缺點
- 3.3.3 異常處理機制
- 3.4 循環控制結構
- 3.4.1 for 循環
- 3.4.2 while 循環
- 3.4.3 循環的非正常中斷
- 3.4.4 嵌套循環
- 3.5 結構化程序設計
- 3.5.1 程序開發過程
- 3.5.2 結構化程序設計的基本內容
- 3.6 編程案例:如何求 n 個數據的最大值?
- 3.6.1 幾種解題策略
- 3.6.2 經驗總結
- 3.7 Python 布爾表達式用作控制結構*
- 3.8 練習
- 第 4 章 模塊化編程
- 4.1 模塊化編程基本概念
- 4.1.1 模塊化設計概述
- 4.1.2 模塊化編程
- 4.1.3 編程語言對模塊化編程的支持
- 4.2 Python 語言中的函數
- 4.2.1 用函數減少重復代碼 首先看一個簡單的用字符畫一棵樹的程序:
- 4.2.2 用函數改善程序結構
- 4.2.3 用函數增強程序的通用性
- 4.2.4 小結:函數的定義與調用
- 4.2.5 變量的作用域
- 4.2.6 函數的返回值
- 4.3 自頂向下設計
- 4.3.1 頂層設計
- 4.3.2 第二層設計
- 4.3.3 第三層設計
- 4.3.4 第四層設計
- 4.3.5 自底向上實現與單元測試
- 4.3.6 開發過程小結
- 4.4 Python 模塊*
- 4.4.1 模塊的創建和使用
- 4.4.2 Python 程序架構
- 4.4.3 標準庫模塊
- 4.4.4 模塊的有條件執行
- 4.5 練習
- 第 5 章 圖形編程
- 5.1 概述
- 5.1.1 計算可視化
- 5.1.2 圖形是復雜數據
- 5.1.3 用對象表示復雜數據
- 5.2 Tkinter 圖形編程
- 5.2.1 導入模塊及創建根窗口
- 5.2.2 創建畫布
- 5.2.3 在畫布上繪圖
- 5.2.4 圖形的事件處理
- 5.3 編程案例
- 5.3.1 統計圖表
- 5.3.2 計算機動畫
- 5.4 軟件的層次化設計:一個案例
- 5.4.1 層次化體系結構
- 5.4.2 案例:圖形庫 graphics
- 5.4.3 graphics 與面向對象
- 5.5 練習
- 第 6 章 大量數據的表示和處理
- 6.1 概述
- 6.2 有序的數據集合體
- 6.2.1 字符串
- 6.2.2 列表
- 6.2.3 元組
- 6.3 無序的數據集合體
- 6.3.1 集合
- 6.3.2 字典
- 6.4 文件
- 6.4.1 文件的基本概念
- 6.4.2 文件操作
- 6.4.3 編程案例:文本文件分析
- 6.4.4 緩沖
- 6.4.5 二進制文件與隨機存取*
- 6.5 幾種高級數據結構*
- 6.5.1 鏈表
- 6.5.2 堆棧
- 6.5.3 隊列
- 6.6 練習
- 第 7 章 面向對象思想與編程
- 7.1 數據與操作:兩種觀點
- 7.1.1 面向過程觀點
- 7.1.2 面向對象觀點
- 7.1.3 類是類型概念的發展
- 7.2 面向對象編程
- 7.2.1 類的定義
- 7.2.2 對象的創建
- 7.2.3 對象方法的調用
- 7.2.4 編程實例:模擬炮彈飛行
- 7.2.5 類與模塊化
- 7.2.6 對象的集合體
- 7.3 超類與子類*
- 7.3.1 繼承
- 7.3.2 覆寫
- 7.3.3 多態性
- 7.4 面向對象設計*
- 7.5 練習
- 第 8 章 圖形用戶界面
- 8.1 圖形用戶界面概述
- 8.1.1 程序的用戶界面
- 8.1.2 圖形界面的組成
- 8.1.3 事件驅動
- 8.2 GUI 編程
- 8.2.1 UI 編程概述
- 8.2.2 初識 Tkinter
- 8.2.3 常見 GUI 構件的用法
- 8.2.4 布局
- 8.2.5 對話框*
- 8.3 Tkinter 事件驅動編程
- 8.3.1 事件和事件對象
- 8.3.2 事件處理
- 8.4 模型-視圖設計方法
- 8.4.1 將 GUI 應用程序封裝成對象
- 8.4.2 模型與視圖
- 8.4.3 編程案例:匯率換算器
- 8.5 練習
- 第 9 章 模擬與并發
- 9.1 模擬
- 9.1.1 計算機建模
- 9.1.2 隨機問題的建模與模擬
- 9.1.3 編程案例:乒乓球比賽模擬
- 9.2 原型法
- 9.3 并行計算*
- 9.3.1 串行、并發與并行
- 9.3.2 進程與線程
- 9.3.3 多線程編程的應用
- 9.3.4 Python 多線程編程
- 9.3.5 小結
- 9.4 練習
- 第 10 章 算法設計和分析
- 10.1 枚舉法
- 10.2 遞歸
- 10.3 分治法
- 10.4 貪心法
- 10.5 算法分析
- 10.5.1 算法復雜度
- 10.5.2 算法分析實例
- 10.6 不可計算的問題
- 10.7 練習
- 第 11 章 計算+X
- 11.1 計算數學
- 11.2 生物信息學
- 11.3 計算物理學
- 11.4 計算化學
- 11.5 計算經濟學
- 11.6 練習
- 附錄
- 1 Python 異常處理參考
- 2 Tkinter 畫布方法
- 3 Tkinter 編程參考
- 3.1 構件屬性值的設置
- 3.2 構件的標準屬性
- 3.3 各種構件的屬性
- 3.4 對話框
- 3.5 事件
- 參考文獻