第 14 章 排列組合與機率¶

本章重點:

  • 用 Python / SymPy 協助處理「有幾種可能?」這類問題。
  • 認識階乘、排列、組合,並用 SymPy 計算。
  • 建立基本機率模型:樣本空間、事件、等可能模型。
  • 練習計算基本機率、條件機率與獨立性。
  • 用程式模擬(實驗)來驗證機率計算結果,培養直覺。
  • 初步接觸二項分配與 SymPy 的機率工具。

本章適合作為「離散數學+基礎機率」的溫和入門, 同時示範如何用電腦做大量計算與模擬,幫助你檢查自己的推理。

14.1 動機:從日常情境到計數問題¶

日常生活中充滿「有幾種可能?」的問題:

  • 手機解鎖圖形有幾種?
  • 從 10 個候選人中選出 3 位委員,有幾種選法?
  • 抽卡遊戲中,抽到至少一張稀有卡的機率是多少?

這些問題背後的核心其實是兩個:

  1. 怎麼數(計數)? —— 排列、組合、階乘⋯⋯
  2. 怎麼算機率? —— 有利情況 / 所有可能情況。

本章要做的事:

  • 用 SymPy 幫忙計算各種排列組合數量。
  • 用 Python 列出所有可能情況,從「列舉」連結到「公式」。
  • 用程式模擬隨機實驗,檢查理論機率是否合理。

14.2 SymPy 的基本組合函數¶

SymPy 中與排列組合相關的基本函數包括:

  • factorial(n):$n!$(階乘)。
  • binomial(n, k):$\binom{n}{k}$(組合數)。

先來試試看:

In [1]:
import sympy as sp
sp.init_printing()

n, k = sp.symbols('n k', integer=True, nonnegative=True)

fact_5 = sp.factorial(5)
C_5_2 = sp.binomial(5, 2)
fact_5, C_5_2
Out[1]:
$\displaystyle \left( 120, \ 10\right)$

結果:

  • $5! = 5\times 4\times 3\times 2\times 1 = 120$。
  • $\binom{5}{2} = 10$,代表從 5 個人中選 2 個的選法數量。

14.3 排列:有順序的安排¶

若有 $n$ 個不同元素,排成一排,每一種不同的排序稱為一種排列。

  • 全部排出來的排列數是 $n!$。
  • 若只選出其中 $r$ 個排成一排,排列數記為 $P(n,r)$,公式為

$$P(n,r) = n (n-1) (n-2) \cdots (n-r+1) = \frac{n!}{(n-r)!}.$$

在 SymPy 中沒有特別叫 permutation(n, r) 的內建函數, 但可以用 factorial 或 binomial 自己寫:

In [2]:
def P(n, r):
    return sp.factorial(n) / sp.factorial(n-r)

P_5_3 = P(5, 3)
P_5_3
Out[2]:
$\displaystyle 60$

這代表從 5 個不同的人中選 3 個排成一排,有 60 種不同的順序。

啟發:用 Python 真的排一排看看¶

我們可以用 itertools.permutations 實際列出所有排列, 確認數量是否為 60(或從小例子開始)。

In [3]:
import itertools

items = ['A', 'B', 'C']
perms = list(itertools.permutations(items))
len(perms), perms
Out[3]:
(6,
 [('A', 'B', 'C'),
  ('A', 'C', 'B'),
  ('B', 'A', 'C'),
  ('B', 'C', 'A'),
  ('C', 'A', 'B'),
  ('C', 'B', 'A')])

可以看到 3 個不同物件的全排列有 $3! = 6$ 種,與理論一致。

14.4 組合:不在乎順序的選法¶

若只關心「選出哪幾個」而不在乎排序,就得到組合。

從 $n$ 個不同物件中選出 $k$ 個的組合數為:

$$\binom{n}{k} = \frac{n!}{k!(n-k)!}.$$

在 SymPy 中用 sp.binomial(n, k):

In [4]:
sp.binomial(5, 3), sp.binomial(10, 2), sp.binomial(52, 5)
Out[4]:
$\displaystyle \left( 10, \ 45, \ 2598960\right)$

最後一個 $\binom{52}{5}$ 是「從一副 52 張牌中選出 5 張牌」的可能數, 也就是一般撲克牌手牌的總數。

啟發:排列 vs 組合的差別¶

以 {A, B, C} 選 2 個為例:

  • 排列 $P(3,2)$:AB, BA, AC, CA, BC, CB(6 種)。
  • 組合 $\binom{3}{2}$:{A,B}, {A,C}, {B,C}(3 種)。

你可以用 Python 實際列出:

In [5]:
items = ['A', 'B', 'C']
perms_2 = list(itertools.permutations(items, 2))
from itertools import combinations
combs_2 = list(combinations(items, 2))
len(perms_2), perms_2, len(combs_2), combs_2
Out[5]:
(6,
 [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')],
 3,
 [('A', 'B'), ('A', 'C'), ('B', 'C')])

14.5 啟發性例子一:樂透抽球¶

假設有 42 個號碼球,從中不放回地抽出 6 顆,且順序不重要(就是一般樂透)。

  1. 總共有多少種可能開獎結果?
  2. 如果你買 1 組號碼,中獎機率是多少?

用 SymPy:

In [6]:
total_outcomes = sp.binomial(42, 6)
prob_win = 1 / total_outcomes
total_outcomes, sp.nsimplify(prob_win), float(prob_win)
Out[6]:
$\displaystyle \left( 5245786, \ \frac{1}{5245786}, \ 1.90629202182476 \cdot 10^{-7}\right)$

可以看到總組合數極大,中獎機率極低。

這個例子說明:

  • 組合自然用來描述「從 n 個中選 k 個」的情境。
  • 機率可以用「有利情況 / 所有情況」來定義。

14.6 基本機率:樣本空間與事件¶

在最簡單「等可能」模型中:

  • 樣本空間 $\Omega$:所有可能結果的集合。
  • 事件 $A$:$\Omega$ 的一個子集合。
  • 機率:

$$P(A) = \frac{|A|}{|\Omega|}.$$

例:擲一顆公平骰子一次。

  • $\Omega = \{1,2,3,4,5,6\}$。
  • $A = \{2,4,6\}$ =「點數為偶數」。
  • $P(A) = 3/6 = 1/2$。

用 Python 列出樣本空間:

In [7]:
Omega = list(range(1, 7))
A = [x for x in Omega if x % 2 == 0]
Omega, A, len(A)/len(Omega)
Out[7]:
$\displaystyle \left( \left[ 1, \ 2, \ 3, \ 4, \ 5, \ 6\right], \ \left[ 2, \ 4, \ 6\right], \ 0.5\right)$

14.7 兩次擲骰子的樣本空間與事件¶

擲兩顆(或一顆骰子連擲兩次),樣本空間可以表示為有序對 $(i,j)$:

$\Omega = \{(i,j) : i = 1..6, j = 1..6\}$,共有 $6\times 6 = 36$ 種結果。

例:事件 $A$ =「點數和為 7」。

先用 Python 列出所有 $(i,j)$,再篩選和為 7 的情況:

In [8]:
Omega = [(i, j) for i in range(1, 7) for j in range(1, 7)]
A = [(i, j) for (i, j) in Omega if i + j == 7]
len(Omega), A, len(A)/len(Omega)
Out[8]:
$\displaystyle \left( 36, \ \left[ \left( 1, \ 6\right), \ \left( 2, \ 5\right), \ \left( 3, \ 4\right), \ \left( 4, \ 3\right), \ \left( 5, \ 2\right), \ \left( 6, \ 1\right)\right], \ 0.166666666666667\right)$

可以看到:和為 7 的組合有 6 種,因此機率為 $6/36 = 1/6$。

14.8 補事件、聯集、交集與容斥原理(簡單版)¶

在等可能模型中:

  • 補事件:$A^c$ 代表「不是 A 的所有結果」,$P(A^c) = 1 - P(A)$。
  • 聯集:$A \cup B$ 代表「屬於 A 或 B(或兩者)」。
  • 交集:$A \cap B$ 代表「同時屬於 A 和 B」。

基本關係:

$$P(A \cup B) = P(A) + P(B) - P(A \cap B).$$

例:擲一顆骰子一次。

  • $A$ =「點數是偶數」。
  • $B$ =「點數大於等於 4」。

用 Python 實作:

In [9]:
Omega = set(range(1, 7))
A = {x for x in Omega if x % 2 == 0}
B = {x for x in Omega if x >= 4}

A_union_B = A | B
A_inter_B = A & B
A_comp = Omega - A

Omega, A, B, A_union_B, A_inter_B, A_comp, len(A_union_B)/len(Omega)
Out[9]:
$\displaystyle \left( \left\{1, 2, 3, 4, 5, 6\right\}, \ \left\{2, 4, 6\right\}, \ \left\{4, 5, 6\right\}, \ \left\{2, 4, 5, 6\right\}, \ \left\{4, 6\right\}, \ \left\{1, 3, 5\right\}, \ 0.666666666666667\right)$

你可以檢查:

  • $P(A) = 3/6$,$P(B) = 3/6$,$P(A \cap B) = 2/6$。
  • $P(A \cup B) = 3/6 + 3/6 - 2/6 = 4/6$。

14.9 條件機率與獨立性¶

條件機率 $P(A\mid B)$ 代表在「知道 B 發生」的前提下,A 發生的機率:

$$P(A\mid B) = \frac{P(A \cap B)}{P(B)} \quad (P(B) > 0).$$

兩事件 A, B 若滿足:

$$P(A \cap B) = P(A)P(B),$$

則稱 A, B 獨立。

例:擲一顆骰子一次。

  • $A$ =「點數是偶數」。
  • $B$ =「點數大於等於 4」。

我們來計算 $P(A\mid B)$ 並檢查 A, B 是否獨立:

In [10]:
Omega = set(range(1, 7))
A = {x for x in Omega if x % 2 == 0}
B = {x for x in Omega if x >= 4}

PA = len(A)/len(Omega)
PB = len(B)/len(Omega)
PAB = len(A & B)/len(Omega)
PAgivenB = PAB / PB
PA, PB, PAB, PAgivenB, PA*PB
Out[10]:
$\displaystyle \left( 0.5, \ 0.5, \ 0.333333333333333, \ 0.666666666666667, \ 0.25\right)$

結果:

  • $P(A) = 1/2$。
  • $P(B) = 1/2$。
  • $P(A \cap B) = 2/6 = 1/3$。
  • $P(A\mid B) = (1/3)/(1/2) = 2/3$。

因為 $P(A \cap B) \ne P(A)P(B)$,所以 A 與 B 不是獨立事件。

14.10 隨機模擬:用 Python 擲硬幣與擲骰子¶

數學機率給出「理論值」, 但我們也可以用電腦模擬大量實驗,看看頻率是否接近機率。

例:擲硬幣,正反各半。

  • 理論上 $P(正面) = 1/2$。
  • 我們模擬 1000 次,看看正面出現的比例。
In [11]:
import random

def simulate_coin_flips(n_trials=1000):
    heads = 0
    for _ in range(n_trials):
        if random.random() < 0.5:  # 0~1 均勻,在 0.5 之下算正面
            heads += 1
    return heads/n_trials

simulate_coin_flips(1000)
Out[11]:
$\displaystyle 0.499$

你可以多跑幾次,或把 n_trials 改成 10000、100000,觀察比例如何收斂到 0.5。

同樣地,我們也可以模擬兩顆骰子點數和為 7 的機率:

In [12]:
def simulate_two_dice_sum_7(n_trials=10000):
    count = 0
    for _ in range(n_trials):
        d1 = random.randint(1, 6)
        d2 = random.randint(1, 6)
        if d1 + d2 == 7:
            count += 1
    return count/n_trials

simulate_two_dice_sum_7(10000)
Out[12]:
$\displaystyle 0.1709$

理論值是 $1/6 \approx 0.1667$,模擬結果應該接近這個數字。

14.11 SymPy.stats:簡單的機率物件¶

SymPy 提供 sympy.stats 模組,用於處理機率變數。 這裡我們只示範非常簡單的用法。

例:公平骰子一次的點數。

In [13]:
from sympy.stats import Die, P, E, variance

d = Die('d', 6)  # 一顆六面公平骰子
prob_3 = P(d > 3)   # 點數大於 3 的機率
expected = E(d)     # 期望值
var = variance(d)
prob_3, expected, var
Out[13]:
$\displaystyle \left( \frac{1}{2}, \ \frac{7}{2}, \ \frac{35}{12}\right)$

你可以嘗試:

  • P(d == 6):出現 6 的機率。
  • P(d <= 2):小於等於 2 的機率。
  • E(d**2):點數平方的期望值。

雖然本章不深入「期望值」與「變異數」, 但你可以把它們想成「平均」與「變化程度」的機率版本, 會在之後資料分析章節再連結回來。

14.12 二項分配:多次成功/失敗的模型¶

當我們有一個「成功/失敗」的試驗(例如擲硬幣),且:

  • 每次成功的機率為 $p$。
  • 試驗之間互相獨立。

連續進行 $n$ 次,成功的次數 $X$ 會服從二項分配:

$$P(X = k) = \binom{n}{k} p^k (1-p)^{n-k}.$$

例:擲一枚不公平硬幣 5 次,每次正面機率為 $p=0.3$。
求正面出現 2 次的機率。

In [14]:
n = 5
k = 2
p = sp.Rational(3, 10)  # 0.3
prob_k = sp.binomial(n, k) * p**k * (1-p)**(n-k)
sp.simplify(prob_k), float(prob_k)
Out[14]:
$\displaystyle \left( \frac{3087}{10000}, \ 0.3087\right)$

我們也可以用 sympy.stats 來計算:

In [15]:
from sympy.stats import Binomial

X = Binomial('X', n, p)
P_X_eq_2 = P(X == 2)
P_X_eq_2
Out[15]:
$\displaystyle 0$

你可以試著算 $P(X = 0)$、$P(X = 1)$,並觀察所有 $k=0..5$ 的機率總和是否為 1。

14.13 本章小結¶

本章你學到了:

  • 如何用 SymPy 計算階乘、排列數 $P(n,r)$ 與組合數 $\binom{n}{k}$。
  • 在等可能模型中,以「有利情況 / 所有情況」定義機率。
  • 補事件、聯集、交集與條件機率的基本觀念。
  • 用 Python 列舉樣本空間與事件,並透過模擬(隨機實驗)加深對機率的直觀。
  • SymPy.stats 提供了機率變數、期望值與變異數的符號處理。
  • 二項分配作為「多次成功/失敗試驗」的基本模型,與排列組合的關係。

這些工具在後續的資料分析、統計與機器學習中都非常常見, 本章的目標是幫你建立一個整體的輪廓,並熟悉 Python / SymPy 如何輔助計算。

14.14 練習題¶

請在本 Notebook 中新增儲存格,試著完成以下練習:

  1. 基本排列組合計算
    (a) 使用 SymPy 計算 $7!$、$P(7,3)$、$\binom{7}{3}$。
    (b) 解釋 $P(7,3)$ 與 $\binom{7}{3}$ 在意義上的差別。
    (c) 嘗試設計一個「選班代表」的情境,分別適合用排列與組合來建模。

  2. 撲克牌問題
    一副 52 張的撲克牌,假設不考慮花色順序。
    (a) 從中抽出 5 張牌有幾種不同組合?
    (b) 假設有 4 張 A(Ace),抽到「剛好一張 A」的 5 張手牌有幾種可能?
    (c) 計算抽到「剛好一張 A」的機率。
    (提示:用組合數表示「選出 A 的方式 × 選出非 A 的方式」。)

  3. 骰子與機率
    擲兩顆公平骰子,設 $X$ 為兩顆點數的和。
    (a) 用 Python 列出所有 36 種 $(i,j)$,統計各種點數和(2~12)的出現次數。
    (b) 用程式計算 $P(X=7)$、$P(X\ge 10)$。
    (c) 嘗試用理論方式計算這些機率,確認與程式一致。

  4. 條件機率練習
    擲一顆公平骰子一次。
    (a) 令 $A$ =「點數為偶數」,$B$ =「點數大於 3」。
    計算 $P(A)$、$P(B)$、$P(A\cap B)$、$P(A\mid B)$。
    (b) 判斷 A 與 B 是否獨立。
    (c) 嘗試再設計一對事件,使它們是獨立的,並驗證 $P(A \cap B) = P(A)P(B)$。

  5. 隨機模擬 vs 理論機率
    (a) 寫一個函數模擬擲三顆骰子,重複 10000 次,估計點數和為 10 的機率。
    (b) 嘗試用程式枚舉所有點數組合,計算理論機率,並與模擬結果比較。
    (c) 觀察當實驗次數從 1000 增加到 10000、50000 時,估計值如何收斂。

  6. 二項分配練習
    一枚硬幣正面機率為 0.4,連續擲 6 次。
    (a) 使用二項分配公式或 SymPy.stats 計算:正面出現 0,1,2,...,6 次的機率。
    (b) 檢查所有機率加總是否為 1。
    (c) 使用隨機模擬(大量試驗)估計「正面出現恰好 2 次」的機率,並與理論值比較。

  7. 加分題:機率與期望值的連結
    以擲一顆公平骰子為例,令隨機變數 $X$ 為點數。
    (a) 寫出 $P(X=k)$ 對於 $k=1..6$。
    (b) 用定義 $E[X] = \sum_k k P(X=k)$ 計算期望值(不使用 SymPy.stats)。
    (c) 再用 SymPy.stats 的 E(d) 求出期望值,確認兩者一致。
    (d) 思考:如果玩一個「擲骰子,獲得等於點數的獎金」的遊戲, 每玩一次的合理門票價格大約應該是多少?

回首頁