CC++ & Algorithm

排列与组合

困难2
语言版本:C++
概述:排列讲顺序,组合不讲顺序,就像排队和选人参加活动。

排列与组合:从排队选座位到分组打扫卫生

排列和组合是数学中帮我们“数一数有多少种方法”的工具。生活中到处需要数方法:比如从班级里选几个人排成一排拍照,或者选几个人去参加活动,方法数不一样。排列讲顺序,顺序不同就算不同;组合不讲顺序,只关心选了哪些人。学好它们,你就能轻松算出抽奖中奖概率、游戏里的搭配方案等。


什么是排列?—— 排队选座位,顺序很重要

定义:从 nn 个不同元素中取出 mm 个(mnm \le n),按一定顺序排成一列,叫做一个排列。顺序不同就是不同的排列。

生活例子:从A、B、C三个同学中选两个排座位(比如第一排和第二排)。

  • 可能的排法:AB(A第一,B第二)、BA(B第一,A第二)、AC、CA、BC、CB,一共6种。
  • 注意:AB和BA虽然选的人一样,但座位不同,所以算两种。

公式

P(n,m)=n!(nm)!P(n, m) = \frac{n!}{(n-m)!}

其中 n!n! 表示从1乘到n的阶乘,例如 3!=3×2×1=63! = 3 \times 2 \times 1 = 6
算出 P(3,2)=3!(32)!=61!=6P(3,2) = \frac{3!}{(3-2)!} = \frac{6}{1!} = 6,和例子吻合。


什么是组合?—— 选人参加活动,只看有谁

定义:从 nn 个不同元素中取出 mm 个(mnm \le n),不计顺序,只考虑选出了哪些人,叫做一个组合。

生活例子:从A、B、C中选两个人去扫地。

  • 可能的组合:AB、AC、BC,只有3种。
  • 为什么?因为AB和BA是同一组人,扫地的活不分谁先谁后,所以只算一次。

公式

C(n,m)=n!m!(nm)!C(n, m) = \frac{n!}{m! \cdot (n-m)!}

算出 C(3,2)=3!2!1!=62×1=3C(3,2) = \frac{3!}{2! \cdot 1!} = \frac{6}{2 \times 1} = 3,和例子吻合。


如何用Python快速计算?

Python的 math 模块已经帮我们准备好了两个函数:perm 计算排列数,comb 计算组合数。你也可以自己写函数实现,方便理解原理。

方法一:直接用 math 模块(推荐)
import math

# 排列:从5个元素中取3个排列
print(math.perm(5, 3))   # 输出60

# 组合:从5个元素中取3个组合
print(math.comb(5, 3))   # 输出10
方法二:自己写阶乘函数
def factorial(n):
    result = 1                      # 初始化结果为1
    for i in range(2, n + 1):      # 从2乘到n
        result *= i
    return result

def perm(n, m):
    return factorial(n) // factorial(n - m)

def comb(n, m):
    return factorial(n) // (factorial(m) * factorial(n - m))

print(perm(5, 3))  # 输出60
print(comb(5, 3))  # 输出10

注意:// 是整数除法,因为阶乘结果一定是整数,所以直接用整除不会出错。


常见错误与注意事项

  1. 混淆排列和组合

    • 题中说“排队”、“照相”、“名次”等有顺序的,用排列。
    • 题中说“选几个人”、“分组”、“参加活动”等不考虑顺序的,用组合。
    • 练习:从5个同学中选3个参加跳绳比赛,是组合;选3个分别担任班长、学习委员、体育委员,是排列。
  2. m=0m = 0

    • 什么都不选,只有一种情况:就是“一个都不选”。所以排列数和组合数都是1。
    • 例如 math.perm(5, 0) 返回 1,math.comb(5, 0) 也返回 1。
  3. m>nm > n

    • 不可能从5个元素中取6个,结果就是0。math.perm(5, 6)math.comb(5, 6) 都返回 0。
  4. 阶乘计算要小心大数

    • Python 的整数可以无限大,所以直接用 math.perm 或自己写阶乘都没问题。但在某些语言里,大数会溢出,Python 不用怕。
  5. 公式中的除法要保证整除

    • 公式看起来有除法,但结果一定是整数。自己写时记得用 // 整除,避免出现浮点数。

完整示例:班级选班长分组程序

下面是一个完整的程序,让用户输入总人数、要选的人数,然后选择计算排列还是组合,输出结果并附带生活解释。

import math

# 获取用户输入
n = int(input("请输入总人数(n): "))   # 总人数
m = int(input("请输入选取人数(m): "))  # 选取人数

if m > n:
    print("错误:选取人数不能大于总人数!")
else:
    # 选择计算类型
    choice = input("计算排列请输入P,计算组合请输入C: ").upper()
    if choice == 'P':
        result = math.perm(n, m)
        print(f"从{n}人中选{m}人排成一排,有{result}种方法。")
        print("解释:排队顺序很重要,比如AB和BA算两种。")
    elif choice == 'C':
        result = math.comb(n, m)
        print(f"从{n}人中选{m}人参加活动,有{result}种方法。")
        print("解释:只关心选哪些人,不关心顺序,比如AB和BA算一种。")
    else:
        print("请输入P或C!")

运行示例:

请输入总人数(n): 5
请输入选取人数(m): 3
计算排列请输入P,计算组合请输入C: P
从5人中选3人排成一排,有60种方法。
解释:排队顺序很重要,比如AB和BA算两种。

相关知识点延伸

排列与组合是概率论的基础:比如从10张奖券中抽一张中奖的概率就是 1/101/10,但如果抽两张且不考虑顺序,中奖组合数就用组合算。
编程竞赛中,很多计数问题(比如用动态规划求方案数)本质上就是排列组合的变形。
如果你对阶乘的快速计算感兴趣,可以学习乘法原理递归;对组合数取模的问题(比如结果很大,只要求输出对某个数取余),则要学会模逆运算卢卡斯定理

下一步建议学习:概率初步二项式定理(a+b)n(a+b)^n 的展开系数就是组合数)、容斥原理(既会排列又会组合才能解决更复杂的问题)。

例题精讲

1单选题

从5本不同的书中选3本送给3位同学,每人一本,有多少种不同的送法?

A10种
B20种
C60种
D125种
2判断题

从6名同学中选出3人参加志愿者活动,不考虑顺序,共有C(6,3)=20种选法。

3填空题
以下函数用于计算组合数C(n,m),请补全代码。
def comb(n, m):
    if m < 0 or m > n:
        return 0
    if m == 0 or m == n:
        return 1
    return comb(n-1, m-1) + ___
4单选题

用数字0,1,2,3组成没有重复数字的三位数,共有多少个?

A18个
B24个
C27个
D48个
5填空题
以下代码使用Python的itertools模块生成所有排列,请补全输出排列个数的语句。
import itertools
items = ['A','B','C']
perms = list(itertools.permutations(items, 2))
print(___)
# 应输出6,但要求输出排列个数