目录
-
- 前言
- 1. 引言
- 2. 二项式系数
- 3. 定义多项分布
- 4. 公式推导
- 5. 结语
- 结语
- 参考
前言
学习 Steven Brunton 讲授的概率与统计入门概述视频,本篇文章记录第七讲:二项分布与多项分布,记录下个人学习笔记,和大家一起分享交流😄
video:https://www.youtube.com/playlist?list=PLMrJAkhIeNNR3sNYvfgiKgcStwuPSts9V
1. 引言
我们一直在研究如何计算概率问题,比如:一副扑克牌中能组成同花顺的手牌有多少种可能,或者出现满堂红的手牌组合有多少种,归根结底,这需要计算特定事件在所有可能情况中出现的概率。
在所有扑克牌组合中,需要统计出同花、满堂红等特定牌型的出现次数,这本质上就是统计事件数量的问题 — 比如计算所有可能的扑克手牌总数,或者统计包含单张 K 的手牌组合数量等。
我们总结出了一些非常实用的计算公式,现在我要写下一个重要公式,这个公式与二项式系数密切相关,之后我们会将其推广到多项式分布,这个公式在计算可能出现的扑克牌组合数量时非常实用,当我们从一副 52 张扑克牌中发出 5 张手牌时,其分布符合二项式分布。
如果我发两副扑克手牌,假设有两个玩家在玩,那么发两副或三副手牌分别由多少种发牌方式?这属于多项式分布的范畴,这套公式极其实用,是计算各类复杂概率问题的必备工具。
因此,我们能收集到的无序样本数量就是无序样本的总数,从总数为
n
n
n 的物件中抽取样本,那么
n
n
n 就是 52 张牌,我抽到的样本就是一手 5 张牌,无序样本的数量计算方式为:从总量
n
n
n 中无放回抽取,即当我发出第一张牌后,牌堆只剩
n
−
1
n-1
n−1 张牌,发出第二张牌后,牌堆仅剩 50 张牌,因此,无放回的无序样本数量等于组合数
(
n
r
)
\\binom{n}{r}
(rn) ,记住
n
C
r
nC_r
nCr 是其简写形式,我们有时称之为 “从
n
n
n 中选取
r
r
r 个样本”,这个公式的字面含义就是 “
n
n
n 选
r
r
r ”,记住,这个公式的计算方式是:
(
n
r
)
=
n
!
(
n
−
r
)
!
r
!
\\binom{n}{r} = \\frac{n!}{(n-r)! \\, r!}
(rn)=(n−r)!r!n!
这就是计算公式,这就是我们计算扑克牌可能发牌组合数的方法,从一副 52 张的扑克牌中,不考虑发牌顺序且无放回抽取,总共可以发出多少种不同的 5 张牌组合?无论是先拿到 7 还是先拿到 9,最终组合结果都是相同的,这就是从
n
n
n 个元素中无序抽取
r
r
r 的样本的组合数,记作
(
n
r
)
\\binom{n}{r}
(rn) ,这个数值与二项式系数密切相关。
2. 二项式系数
我想展示这个是因为它非常有趣 — 你可能在帕斯卡三角形和多项式展开系数的计算中见过它,若取
(
x
+
y
)
2
(x+y)^2
(x+y)2 或者更一般地考虑
(
x
+
y
)
n
(x+y)^n
(x+y)n ,展开后将得到:
(
x
+
y
)
n
=
∑
k
=
0
n
(
n
k
)
(
x
k
y
n
−
k
)
(x+y)^n = \\sum_{k=0}^{n} \\binom{n}{k} (x^ky^{n-k})
(x+y)n=k=0∑n(kn)(xkyn−k)
若干形如
x
k
y
n
−
k
x^ky^{n-k}
xkyn−k 的项之和,求和符号从
k
=
0
k=0
k=0 到
n
n
n ,这些单项式的系数将是
(
n
k
)
\\binom{n}{k}
(kn) ,这些就是二项式系数,之所以称为 “二项式”,是因为我们实际上是将两个项相加后求幂,因此这个多项式就是所有这些单项式的总和,其中系数正是二项式系数,要计算这些系数,可以通过帕斯卡三角形来求解,想必大家对此早已熟悉。

现在观察系数,帕斯卡三角形的顶端数字是 1,因此
(
x
+
y
)
0
(x+y)^0
(x+y)0 的结果就是 1,
(
x
+
y
)
1
(x+y)^1
(x+y)1 展开后,
x
x
x 和
y
y
y 的系数都是 1,帕斯卡三角形中的这些数字就是对应的系数,
(
x
+
y
)
2
(x+y)^2
(x+y)2 展开后的系数是 1、2、1,展开式是
x
2
+
2
x
y
+
y
2
x^2 + 2xy + y^2
x2+2xy+y2 ,
(
x
+
y
)
3
(x+y)^3
(x+y)3 展开后得到
x
3
+
3
x
2
y
+
3
x
y
2
+
y
3
x^3 + 3x^2y + 3xy^2 + y^3
x3+3x2y+3xy2+y3 ,注意这个三角形中,每个新项都是它上方两项之和,
(
x
+
y
)
4
(x+y)^4
(x+y)4 的展开系数为 1、4、6、4、1,
(
x
+
y
)
5
(x+y)^5
(x+y)5 的展开系数为 1、5、10、10、5、1。
因此,我们可以直接从帕斯卡三角形中读出这个多项式展开的系数,构造起来非常简单,这个巧妙思路与概率论中频繁出现的
(
n
k
)
\\binom{n}{k}
(kn) 二项式系数密切相关,你会发现这种对称结构非常优美,蕴含着精妙的数学美感,这确实令人着迷。
但你会发现,当
n
→
∞
n \\rightarrow \\infty
n→∞ 时,比如计算
(
x
+
y
)
100
(x+y)^{100}
(x+y)100 的展开式,这些系数的分布将逐渐收敛于正态分布,这种趋势在此已初现端倪,可以看出这个分布逐渐趋于正态分布。
事实上,你或许会想在计算机上编写代码实现这个现象,尝试取较大的
n
n
n 值构建三角形,逐行绘制每一层的分布,最终会收敛到优美的高斯分布(即正态分布)。
Python 示例代码如下:
"""
帕斯卡三角形(二项式系数)逐渐趋近正态分布的可视化。
数学关系:
C(n, k) / 2^n = P(X = k), X ~ Binomial(n, 1/2)
当 n 足够大时,将横坐标标准化:
z = (k – n/2) / sqrt(n/4)
标准化后的二项分布逐渐趋近标准正态分布 N(0, 1)。
"""
import argparse
import math
from typing import Iterable
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
def pascal_row(n: int) –> list[int]:
"""返回帕斯卡三角形第 n 行,即 (x+y)^n 的全部二项式系数。"""
if n < 0:
raise ValueError("n 必须是非负整数。")
return [math.comb(n, k) for k in range(n + 1)]
def binomial_probability(n: int) –> tuple[np.ndarray, np.ndarray]:
"""
返回 X ~ Binomial(n, 1/2) 的横坐标 k 和概率 P(X=k)。
使用递推方式计算概率,避免直接把超大的组合数转换为浮点数:
p_0 = 2^(-n)
p_(k+1) = p_k * (n-k)/(k+1)
"""
if n < 0:
raise ValueError("n 必须是非负整数。")
k = np.arange(n + 1, dtype=float)
if n == 0:
return k, np.array([1.0])
probabilities = np.empty(n + 1, dtype=float)
probabilities[0] = math.exp(–n * math.log(2.0))
for i in range(n):
probabilities[i + 1] = probabilities[i] * (n – i) / (i + 1)
# 修正可能出现的微小浮点误差。
probabilities /= probabilities.sum()
return k, probabilities
def standard_normal_pdf(z: np.ndarray) –> np.ndarray:
"""标准正态分布 N(0,1) 的概率密度函数。"""
return np.exp(–0.5 * z**2) / math.sqrt(2.0 * math.pi)
def standardized_binomial(n: int) –> tuple[np.ndarray, np.ndarray]:
"""
将 Binomial(n, 1/2) 标准化,并把离散概率转换为近似密度。
z = (k – μ) / σ
density ≈ P(X=k) / Δz = P(X=k) * σ
"""
k, probability = binomial_probability(n)
if n == 0:
return np.array([0.0]), np.array([1.0])
mean = n / 2.0
std = math.sqrt(n) / 2.0
z = (k – mean) / std
density = probability * std
return z, density
def print_pascal_rows(max_n: int) –> None:
"""在终端打印从第 0 行到第 max_n 行的帕斯卡三角形。"""
if max_n < 0:
raise ValueError("max_n 必须是非负整数。")
rows = [" ".join(map(str, pascal_row(n))) for n in range(max_n + 1)]
width = len(rows[–1])
for n, row in enumerate(rows):
print(f"n={n:>2}: {row.center(width)}")
def plot_selected_rows(rows: Iterable[int]) –> None:
"""绘制若干指定行,并与标准正态分布进行比较。"""
rows = sorted(set(rows))
if not rows or rows[0] < 1:
raise ValueError("用于标准化绘图的 n 必须是正整数。")
figure, axis = plt.subplots(figsize=(11, 7))
z_gaussian = np.linspace(–4.5, 4.5, 1000)
axis.plot(
z_gaussian,
standard_normal_pdf(z_gaussian),
linewidth=3,
label="Standard normal distribution",
)
for n in rows:
z, density = standardized_binomial(n)
axis.plot(
z,
density,
marker="o" if n <= 20 else None,
markersize=4,
linewidth=1.8,
label=f"Pascal row n={n}",
)
axis.set_title("Pascal Triangle Coefficients Converging to a Gaussian Distribution")
axis.set_xlabel(r"Standardized position $z=(k-n/2)/\\sqrt{n/4}$")
axis.set_ylabel("Probability density")
axis.set_xlim(–4.5, 4.5)
axis.grid(alpha=0.3)
axis.legend()
figure.tight_layout(rect=[0, 0, 1, 0.94])
plt.show()
def plot_original_distribution(n: int) –> None:
"""以原始横坐标 k 绘制第 n 行系数归一化后的二项分布。"""
k, probability = binomial_probability(n)
figure, axis = plt.subplots(figsize=(11, 6))
axis.bar(k, probability, width=0.85)
mean = n / 2.0
std = math.sqrt(n) / 2.0
x = np.linspace(max(0.0, mean – 4.5 * std), min(float(n), mean + 4.5 * std), 1000)
gaussian_approximation = (
np.exp(–0.5 * ((x – mean) / std) ** 2)
/ (std * math.sqrt(2.0 * math.pi))
)
axis.plot(x, gaussian_approximation, linewidth=3, label="Gaussian approximation")
axis.set_title(
rf"Normalized Coefficients of $(x+y)^{{{n}}}$: "
rf"$P(X=k)=\\binom{{{n}}}{{k}}/2^{{{n}}}$"
)
axis.set_xlabel("k")
axis.set_ylabel("Probability")
axis.grid(axis="y", alpha=0.3)
axis.legend()
figure.tight_layout()
plt.show()
def animate_convergence(
max_n: int = 100,
interval_ms: int = 100,
save_path: str | None = None,
) –> None:
"""逐行绘制 n=1,…,max_n,动态展示其向标准正态分布收敛。"""
if max_n < 1:
raise ValueError("max_n 必须至少为 1。")
figure, axis = plt.subplots(figsize=(11, 7))
z_gaussian = np.linspace(–4.5, 4.5, 1000)
axis.plot(
z_gaussian,
standard_normal_pdf(z_gaussian),
linewidth=3,
label="Standard normal distribution",
)
binomial_line, = axis.plot([], [], linewidth=2, marker="o", markersize=3)
title = axis.set_title(
r"Pascal row $n=1$: "
r"$\\binom{n}{k}/2^n \\longrightarrow N(0,1)$",
pad=12,
)
axis.set_xlabel(r"Standardized position $z=(k-n/2)/\\sqrt{n/4}$")
axis.set_ylabel("Probability density")
axis.set_xlim(–4.5, 4.5)
axis.set_ylim(0.0, 0.45)
axis.grid(alpha=0.3)
axis.legend()
def update(n: int):
z, density = standardized_binomial(n)
binomial_line.set_data(z, density)
binomial_line.set_marker("o" if n <= 30 else "")
title.set_text(
rf"Pascal row $n={n}$: "
rf"$\\binom{{n}}{{k}}/2^n \\longrightarrow N(0,1)$"
)
return binomial_line, title
animation = FuncAnimation(
figure,
update,
frames=range(1, max_n + 1),
interval=interval_ms,
blit=False,
repeat=True,
)
figure.tight_layout()
if save_path:
try:
animation.save(save_path, writer="pillow", fps=max(1, 1000 // interval_ms))
print(f"动画已保存到:{save_path}")
except Exception as exc:
raise RuntimeError(
"保存 GIF 失败,请先安装 Pillow:pip install pillow"
) from exc
else:
plt.show()
def parse_rows(text: str) –> list[int]:
"""把 '2,5,10,30,100' 转换为整数列表。"""
try:
rows = [int(item.strip()) for item in text.split(",") if item.strip()]
except ValueError as exc:
raise argparse.ArgumentTypeError("rows 必须是用逗号分隔的整数。") from exc
if not rows or any(n < 1 for n in rows):
raise argparse.ArgumentTypeError("rows 中的每个 n 都必须是正整数。")
return rows
def main() –> None:
parser = argparse.ArgumentParser(
description="可视化帕斯卡三角形系数向正态分布收敛的现象。"
)
parser.add_argument(
"–mode",
choices=["triangle", "selected", "original", "animate"],
default="selected",
help=(
"triangle: 打印帕斯卡三角形;"
"selected: 比较若干行;"
"original: 绘制某一行的原始分布;"
"animate: 动态逐行绘制。"
),
)
parser.add_argument(
"–n",
type=int,
default=100,
help="用于 triangle、original 或 animate 模式的最大阶数,默认 100。",
)
parser.add_argument(
"–rows",
type=parse_rows,
default=[2, 5, 10, 30, 100],
help="selected 模式中的行号,例如:–rows 2,5,10,30,100",
)
parser.add_argument(
"–interval",
type=int,
default=100,
help="动画每帧间隔,单位毫秒,默认 100。",
)
parser.add_argument(
"–save",
type=str,
default=None,
help="动画保存路径,例如:–save pascal_to_gaussian.gif",
)
args = parser.parse_args()
if args.n < 0:
parser.error("–n 不能为负数。")
if args.interval <= 0:
parser.error("–interval 必须大于 0。")
if args.mode == "triangle":
print_pascal_rows(args.n)
elif args.mode == "selected":
plot_selected_rows(args.rows)
elif args.mode == "original":
if args.n < 1:
parser.error("original 模式要求 –n 至少为 1。")
plot_original_distribution(args.n)
elif args.mode == "animate":
if args.n < 1:
parser.error("animate 模式要求 –n 至少为 1。")
animate_convergence(
max_n=args.n,
interval_ms=args.interval,
save_path=args.save,
)
if __name__ == "__main__":
main()
执行指令如下:
python .\\pascal_gaussian.py –mode animate –n 100 –save pascal_to_gaussian.gif
生成的动态图如下所示:

需要注意:并不是帕斯卡三角形中的原始系数直接收敛到正态分布,而是归一化后的系数:
P
(
X
=
k
)
=
(
n
k
)
2
n
P(X=k) = \\frac{\\binom{n}{k}}{2^n}
P(X=k)=2n(kn)
构成参数为
n
n
n 和
p
=
1
2
p=\\frac{1}{2}
p=21 的二项分布。进一步令
z
=
k
−
n
2
n
4
,
z = \\frac{k-\\frac{n}{2}}{\\sqrt{\\frac{n}{4}}},
z=4n
k−2n,
标准化后的分布会随着
n
n
n 增大趋近标准正态分布
N
(
0
,
1
)
N(0,1)
N(0,1) 。
还记得《价格猜猜猜》节目里的 Plinko 游戏吗?把球从顶部落下,经过格栅弹跳后最终掉进不同的收集槽里,这种装置有时被称为高尔顿板,德语中称作 Galtonbrett,若将大量珠子撒在布满钉子的格板上,经过弹跳后,它们会逐渐下落并最终形成高斯分布。
帕斯卡三角形正是快速计算多项式展开系数的工具,归根结底,这些系数就是无处不在的二项式系数 — 组合数
C
n
k
C_n^k
Cnk ,我们在概率计算中经常用到它们。
所以如果我想计算从一副 52 张牌中能抽出多少种五张扑克组合,直接用组合数
C
52
5
C_{52}^{5}
C525 就行了,对吧?计算起来非常简单。
而多项式分布则是将其推广到更高维分布的方法,适用于更复杂的纸牌游戏场景,假设现在我要发两副扑克牌,我和朋友一起玩,现在有两副五张牌的手牌,这种情况有多少种可能?最终得到的结果就是多项式分布,其计算过程也相当简便。
3. 定义多项分布
下面我们来推导下,那么有
n
n
n 个物体,划分为
r
r
r 个类别,其规模分别为
n
1
,
n
2
,
…
,
n
r
n_1,n_2,\\ldots,n_r
n1,n2,…,nr ,比如我处理两副牌时,可能会各发五张牌,类似这样的情况,而且顺序不重要。将
n
n
n 个对象划分为
r
r
r 个类别,且不考虑顺序,那么样本总数就是下面这种组合数:
(
n
n
1
n
2
…
n
r
)
\\binom{n}{n_1n_2 \\ldots n_r}
(n1n2…nrn)
接下来我将说明这个符号的定义,
n
1
n
2
…
n
r
n_1n_2 \\ldots n_r
n1n2…nr 并非乘积关系,不是
n
1
×
n
2
×
…
×
n
r
n_1 \\times n_2 \\times \\ldots \\times n_r
n1×n2×…×nr 的形式,这并非从
n
n
n 个元素中选取这个大数值的组合,这是一种新的计数记号,明白吗?
其定义方式如下:
(
n
n
1
n
2
…
n
r
)
=
n
!
n
1
!
n
2
!
⋯
n
r
!
\\binom{n}{n_1n_2 \\ldots n_r} = \\frac{n!}{n_1 \\ ! \\ n_2! \\ \\cdots \\ n_r!}
(n1n2…nrn)=n1 ! n2! ⋯ nr!n!
还有一点需要补充说明,这里有个前提条件:这些
n
i
n_i
ni 的总和必须等于
n
n
n ,因此,整副 52 张牌会被分成
r
r
r 手牌,想象一下你在玩黑桃牌戏:整副牌分给四位玩家后,会形成四手牌,所有手牌相加正好是 52 张,也就是说,这里假设
n
1
+
n
2
+
⋯
+
n
r
=
n
n_1 + n_2 + \\cdots + n_r = n
n1+n2+⋯+nr=n ,对吧?
这与计算从 52 张牌中发出 5 张手牌的组合数略有不同,但原理相通,同样的原理也可用于计算从 52 张牌中抽取 5 张手牌的组合数,这里假设将整个集合
n
n
n 划分为这
r
r
r 个组别,其计算公式如上所示。
4. 公式推导
通过基本原理推导这个公式的过程其实很有帮助,因为这能让你更熟练地掌握这类计算,所以这个公式并非凭空而来,完全可以通过列举所有可能性地方式来推导出这个公式,接下来我们来看看这个公式的推导过程。
这个概率的推导依据是:可能发生的事件总数等于从
n
n
n 个元素中选取
n
1
,
n
2
,
…
,
n
r
n_1,n_2,\\ldots, n_r
n1,n2,…,nr 个元素的组合数。首先分配
n
1
n_1
n1 张牌,有多少种方法可以做到这一点?这就是我们前面提到的
(
n
n
1
)
\\binom{n}{n_1}
(n1n) ,现在我要分配第二组,这就是
(
n
−
n
1
n
2
)
\\binom{n-n_1}{n_2}
(n2n−n1) ,现在剩下
n
−
n
1
−
n
2
n-n_1-n_2
n−n1−n2 张牌,我要用这些来分配
n
3
n_3
n3 ,所以是
(
n
−
n
1
−
n
2
n
3
)
\\binom{n-n_1-n_2}{n_3}
(n3n−n1−n2) ,依次类推,直到最后一项,即:
(
n
−
n
1
−
n
2
−
⋯
−
n
r
n
r
)
\\binom{n-n_1-n_2-\\cdots -n_r}{n_r}
(nrn−n1−n2−⋯−nr)
其中每一项的简答排列都可以计算出来,这些本质上都是二项系数:从总数
n
n
n 种抽取
n
1
n_1
n1 张牌的组合数,以此类推,我们可以列出所有这些组合数的计算公式,然后将它们相乘,你会发现这些项会以非常巧妙地方式相互抵消,最终得到下面这个简洁的公式:
(
n
n
1
n
2
…
n
r
)
=
n
!
n
1
!
n
2
!
⋯
n
r
!
\\binom{n}{n_1n_2 \\ldots n_r} = \\frac{n!}{n_1 \\ ! \\ n_2! \\ \\cdots \\ n_r!}
(n1n2…nrn)=n1 ! n2! ⋯ nr!n!
5. 结语
OK,让我们从整体视角来看,这就是所谓的多项系数,利用这个公式可以计算多种概率问题,在工厂生产零件时,若将合格品与不合格品分为两类,分别记为
n
1
n_1
n1 和
n
2
n_2
n2 ,这种分类情况就符合多项式分布。
通过这个多项式分布,可以计算在采样中出现特定数量不合格品的概率,用这个方法可以计算特定扑克牌型的概率,比如同花顺、四条等组合出现的可能性,能计算的情况非常丰富。
假设总人数为偶数,设为
2
n
2n
2n 人,有多少种方法可以将这些人两两配对?这些人能配成多少对组合?这是道课后习题,你需要针对不同的
n
n
n 值进行计算。
那如果是委员会决策呢?你正在组建一个分委员会,现有七位成员,需组建三个分委员会:两人组、三人组和两人组,从七人中选出这些小组委员会共有多少种方式?
这类问题可以通过多项式分布来计算,它是二项式分布的推广形式,这些二项式系数实在精妙绝伦,它是多项式展开的系数,源自帕斯卡三角形,当
n
n
n 足够大时,最终会收敛于正态分布。
OK,以上就是本讲的全部内容。
我们将在后续课程中演示如何从二项分布推导出正态分布,但我想让大家先对这些概念有个直观认识,因为借助基础的
(
n
r
)
\\binom{n}{r}
(rn) 组合数及其多项式推广,我们就能计算各种有趣事件的概率 — 比如纸牌游戏中的各类情形。
结语
本讲从二项式系数
(
n
r
)
\\binom{n}{r}
(rn) 出发,串联起帕斯卡三角形、多项式展开与高尔顿板,直观展示了归一化后的二项式系数随
n
n
n 增大逐渐收敛于高斯分布的过程 — 这为中心极限定理埋下了伏笔。在此基础上,我们将二项式系数推广为多项分布:
(
n
n
1
n
2
…
n
r
)
=
n
!
n
1
!
n
2
!
⋯
n
r
!
\\binom{n}{n_1 n_2 \\ldots n_r} = \\frac{n!}{n_1! \\ n_2! \\ \\cdots \\ n_r!}
(n1n2…nrn)=n1! n2! ⋯ nr!n!
这一公式是解决 “将
n
n
n 个对象划分为
r
r
r 个无序组别” 的通用计数工具,可广泛应用于扑克牌型、委员会分组、质量抽检等场景。掌握从二项到多项的推广逻辑,就掌握了一套处理复杂组合计数问题的核心方法论 🤗。
参考
- https://www.youtube.com/playlist?list=PLMrJAkhIeNNR3sNYvfgiKgcStwuPSts9V
