组合数学与应用:计算机专业的离散思维必修课

发布时间:2026/9/13 10:56:28
组合数学与应用:计算机专业的离散思维必修课 1. 在成电《组合数学与应用》到底在讲什么如果你在电子科技大学读的是计算机类、软件类或者网络空间安全类的专业课表里大概率会出现《组合数学与应用》这门课。第一次看到课名时很多人会下意识以为这是高中排列组合的“威力加强版”多背几个公式就能应付。但真正坐下来学完一学期我才明白这门课的核心并不在“算总数”而在“建模型、理结构、写证明”。它是衔接数学基础与算法设计的一座桥桥的这一头是离散数学思维那一头是数据结构、算法分析、密码学、网络编码等一堆硬核专业课。我当年选课之前特意问过直系学长他说了一句我至今印象深刻的话“如果你只会套公式算排列组合你会觉得它很难如果你能把题目当成‘给程序写状态转移’你会觉得越学越顺手。”整门课就在反复训练这种能力给定一组规则算出合法对象有多少个并搞清楚这些对象长什么样。无论是用容斥原理去重、用生成函数打包整个数列还是用 Hall 定理判断匹配是否存在本质上都是在回答同一个问题——规则约束下的集合究竟有多大、结构如何。1.1 课程定位计算机专业课表里的“连接器”《组合数学与应用》在成电的培养方案里通常排在大二正好卡在高等数学、线性代数之后又在算法设计、数据结构进阶之前。这个时间点很有讲究。高数讲连续、讲极限线性代数讲向量空间这些工具当然重要但它们没有直接告诉你“一个递归程序会递归多少层”“一张图上有多少种匹配”这类离散世界的问题该怎么想。组合数学补的正是这个缺口。它把“连续数学的直觉”切换成“离散对象的定量分析”给你一套处理枚举、存在性、最优性和构造问题的数学框架。后面学算法的时候最常用到的一个动作就是“数复杂度”一段代码在最坏情况下要执行多少次基本操作如果写成递推式它是线性、是多项式还是指数增长这套语言你可以在算法课上学但如果没有组合数学打底你会觉得那些递推式只是黑魔法有了递推关系与生成函数的知识你就能自己推导出来。1.2 从知识脉络看课程内容虽然不同老师的教学安排会有差异但成电这门课的知识主线相对稳定可以分成五块第一块是计数基础加法原理、乘法原理、排列、组合、二项式系数与常用恒等式。第二块是计数技巧鸽巢原理、容斥原理、错排问题。第三块是递推与生成函数从 Fibonacci 数列到线性常系数递推再到用生成函数统一处理数列。第四块是图论初步图的基本概念、树、欧拉图与哈密顿图、二分图匹配。第五块是组合设计拉丁方、平衡不完全区组设计有时还会引入 Ramsey 理论作为鸽巢原理的深化。这五块看起来风格各异实际上有一条主线贯穿给定规则数出可能结果。容斥原理是在“去重”生成函数是在“打包”递推关系是在“利用局部关系推导全局规律”图论匹配是在“判断可行与最优”。每学一块你都会离“用数学描述问题”更近一步。1.3 别把它当成数学系课程的“降级版”有同学会问数学系不也开组合数学吗成电这门课是不是简配版我个人的体会是它并不是降低难度而是换了目标。数学系的组合课更强调“恒等式证明”和“存在性论证”经常把一个二项式恒等式用五六种方法证来证去训练的是数学直觉和推导能力。而《组合数学与应用》更强调“构造性”和“算法化”。比如同样讲二分图匹配数学系可能更关心 Hall 定理的证明本身成电这门课会要求你能把场景抽象成二分图判断条件是否成立并且会带着你去看怎么把匹配找出来。这种“能不能落到算法上”的导向决定了它的题目风格很多题光有答案不够你必须展示建模过程否则会被扣掉大半分。2. 计数的第一课排列组合和“不漏不重”计数是整门课的地基。高中阶段学的排列组合通常停留在“C 何时用、P 何时用、要不要除以重复”这类机械判断上但大学的组合数学要求你把计数当成一个“集合分解”的过程来做。掌握了这个底层视角再去看容斥、递推和生成函数会觉得它们都是同一种思想的不同形态。2.1 加法原理和乘法原理简单但容易被反杀加法原理和乘法原理听起来像废话要么这样、要么那样互斥就相加先做这个、再做那个独立就相乘。可一到复杂题目很多人就是在这两个最基础的原理上翻车。我自己踩过最典型的坑是“分步乘法”和“分类加法”的混淆。比如说统计不含重复数字的四位偶数个数正确的思路是按个位是否为零来分类个位是 0那么千位有 9 种选法、百位和十位依次减少个位是 2、4、6、8 中的某一个那千位不能为 0 也不能和个位重复需要先确定千位再确定中间两位。这两种情况不能笼统地乘起来因为“首位不能为零”在不同类别里面的影响不一样。怎么避免这种错误我的建议是每道题都先在草稿纸上写清楚两个集合全集是什么约束条件限制的是什么。如果能把问题写成“先划分成若干互斥子集每个子集内部再用乘法原理计数”结构就清晰了。加法原理管的是一层分类的并集乘法原理管的是步骤之间状态的笛卡尔积。你一旦把“分类”和“分步”对应到这个层面就不会再被表面文字带偏。2.2 鸽巢原理抽屉才是主角鸽巢原理本身一句话就能说完把 n1 个物品放进 n 个抽屉总有一个抽屉至少有两个物品。它简单到几乎是废话但应用起来却能产生相当惊艳的结论也是这门课第一次让很多同学感受到“存在性证明”的乐趣。老师在课上举过一个例子我记到现在在边长为 1 的等边三角形里任取 5 个点证明至少有两个点之间的距离不超过 1/2。直接看 5 个点的分布毫无头绪可一旦把大三角形划分成 4 个边长都是 1/2 的小等边三角形5 个点必然有至少 2 个落在同一个小三角形里而小三角形内任意两点距离最大就是 1/2结论立刻成立。这个例子能给你一个很重要的启发鸽巢原理的关键从来不是“原理”本身而是怎么构造抽屉。抽屉的尺寸和形状要依据题目要证的“性质”来设计。你想证明距离不超过某个值就按这个值的尺度去划分空间你想证明有两个人有相同特征就按特征可能的取值去分类。这种“为了结论反推抽屉划分”的思考方式在后面的 Ramsey 理论和算法分析里还会反复出现。2.3 容斥原理把重复计数扣干净容斥原理的公式本身不难三个集合的并等于单个集合大小之和减去两两交集再加上三个集合的交集。真正难的是搞清楚“集合怎么设”。我的经验是不要从“要什么”出发要从“不要什么”出发。想清楚哪些情况是“坏情况”把它们设成 A、B、C然后用容斥原理算出并集大小再从全集里减去。这门课里最经典的例子是错排问题n 个人各交一份作业再随机把这些作业发回去问没有一个人拿到自己作业的方案数。定义事件 A_i 为“第 i 个人拿到了自己的作业”这显然是我们不想要的坏事件。全集是 n!容斥原理会告诉你“至少有一个坏事件发生”的总数是多少最后用 n! 减去它就得到错排数 D_n n! \sum_{k0}^n (-1)^k / k!。我在这里吃过亏教训是容斥原理的符号特别容易搞错。层数越多到底哪一项该加、哪一项该减光靠死记硬背早晚会翻车。正确做法是每次从简单特例出发验证比如先看 n1、n2 的情况把二重交集、三重交集的契约逻辑理清楚再推广到一般 n。这样即使你忘了公式也能现场推出来。2.4 球盒模型题型识别器排列组合的题目多到你不可能全背下来但很多题本质上都能映射成“球放盒子”的问题。球是否相同、盒子是否相同、是否允许空盒这几组条件组合起来就形成了一整套经典结论。课程里常见的是“相同球放不同盒子”的隔板法。举个例子把 10 个完全一样的球放进 3 个不同的盒子允许有空盒问有多少种放法。先给每个盒子预置一块“隔板”等价于在 10 个球之间的 9 个空隙和两端共 12 个位置里选 2 个位置放隔板因此答案是 C(103-1, 3-1) C(12,2)66。这个公式在离散概率、组合优化里出场率极高比如统计非负整数解的个数时就是同一模型。球盒模型的真正价值不是让你背公式而是帮你建立一个“翻译系统”以后遇到“把任务分配给机器”“把资源切给进程”这类问题你能迅速意识到它本质上是在问“某种约束下的分配方案有多少种”。翻译成标准模型之后计数就变成套公式与验证约束的问题难度瞬间下降一大截。3. 递推关系与生成函数从局部关系到整体规律如果说排列组合还停留在“一个个数”的层面递推关系与生成函数就直接把视角拉高了一层。你不再关心前几项具体是多少而是关心“相邻项之间的局部关系”如何决定“整个序列的全局规律”。这个思维转变计算机系同学应该格外亲切——动态规划的核心就是状态转移方程而递推关系就是它的数学化身。3.1 递推关系动态规划的数学表达递推关系的定义很好理解某个数列的第 n 项由前若干项通过固定规则得到。最典型的是 Fibonacci 数列 F_n F_{n-1} F_{n-2}给定 F_0、F_1 之后后面每一项都唯一确定。学到这里建议你不要只把它当作数学公式而是尽量联想程序设计。动态规划做两件事定义状态写出状态转移。状态转移方程本身就是一个递推关系比如“到达第 n 个台阶的走法等于到达第 n-1 个台阶的走法加上到达第 n-2 个台阶的走法”这句话翻译成数学就是 F_n F_{n-1} F_{n-2}。正因如此理解递推关系等于从数学根子上理解动态规划为什么只要局部状态对了全局最优解就能成立因为递推关系就是这样把局部规则传播到整个序列的。3.2 特征方程法从猜解到通项的完整逻辑课程里求解线性常系数齐次递推关系标准方法是特征方程法。以二阶递推 F_n aF_{n-1}bF_{n-2} 为例设 F_n r^n代入得到特征方程 r^2 ar b解出两个根 r_1、r_2 后通项就是 (F_n A r_1^n B r_2^n)再用初值条件解出 A、B。很多同学对这个方法的第一反应是“凭什么能设成 r^n”我当年也有这个困惑。后来找到一个比较顺的理解方式线性常系数递推在结构上类似于把常微分方程离散化。一阶线性齐次微分方程的解是 (e^{rx})离散化之后的基函数就变成了 (r^n)。这里的 r 就像是“离散世界的特征根”它决定的是每一步的缩放比例而不是连续时间内某个时刻的值。想通了这一点特征方程法就不再是魔术而是一套有逻辑的推广。还有两个容易踩的坑值得一提。一个是特征方程有重根时通项里必须补上形如 n 的多项式因子漏写就会和已知数列对不上。另一个是初值条件代错。特征方程解出通项之后还要用前两三个值联立方程求 A 和 B很多同学在这里把 F_0、F_1 代错求出来的通项看起来正常实际上每个数都不对。我的习惯是每解完一道递推题都顺手把通项代回前 4 项验算一遍这个习惯帮我避免了不少低级错误。3.3 生成函数把无限序列装进一个函数里生成函数是整门课里认知跨度最大的概念。它定义很朴素给定一个数列 a_n它的生成函数就是形式幂级数 (G(x) \sum_{n\ge0} a_n x^n)。表面上看这不过是用幂级数的系数来存数列可一旦接受这个视角很多计数问题就像换了一副眼镜。举个例子想求 Fibonacci 数列的通项公式除了用特征方程也可以直接用生成函数。把你关心的整个数列写成一个幂级数然后利用递推关系对幂级数做代数运算最后得到形如 (G(x)x/(1-x-x^2)) 的封闭表示。再把它展开成幂级数每一项的系数就是 Fibonacci 数列的第 n 项。整个过程相当于“把无限多个数打包成一个对象”再集中计算最后解包。在这个过程里我用到了生成函数的关键思想如果两个生成函数相等那么它们对应项的系数一定相等。这叫“系数比较法”它在很多组合恒等式里都有奇效。所以当你面对一个看似无从下手的数列求和或恒等式证明时尝试把它转成生成函数很多时候只需要做基础的分式分解。这个方法直到我参加工作后在分析某个随机算法的期望复杂度时还用到过可以说是投资回报率很高的一节内容。3.4 从递推到算法复杂度一门课前后联动的典型片段成电版组合数学的“应用”标签最直接的体现就是把递推关系接到算法复杂度上。比如归并排序的复杂度满足 (T(n) 2T(n/2)n)用递推展开或者代入法可以解得 (T(n)\Theta(n\log n))。这道题在算法课里是必学内容但在组合数学课上换了个讲法把它当成一个递推求解问题来分析。这种讲法传递的信息是更通用的任何递归程序它的运行时间本质上是关于输入规模的递推关系递归树的分支数、每层的合并成本都会被写进递推式里。会用特征方程是一回事能把程序的运行时间准确写成递推式并判断复杂度是另一回事。后者才是组合数学想让你带走的能力。4. 图论基础与组合设计从关系网到结构设计进入图论部分之后课程重心从“计数”扩展到“结构分析”。很多概念你在离散数学课上可能已经见过但这里的侧重点不同不只要认识图还要理解图的性质如何影响匹配、遍历和设计方案的可行性。4.1 图的本质是二元关系图是什么把一组对象看作顶点对象之间的某种关联看作边。你可以在图上表示社交网络里的好友关系也可以表示电路中的连接关系还可以表示任务之间的依赖关系。组合数学里的图论部分本质上是研究“这种二元关系在不同约束下能呈现哪些结构”。课程通常会从基础概念开始顶点、边、度数、路径、回路、连通性、树。然后过渡到欧拉图和哈密顿图。欧拉图关心的是“是否存在一条路径每条边恰好走一次”哈密顿图关心的是“是否存在一条路径每个顶点恰好访问一次”。这两个问题看起来只差一个字难度却天差地别欧拉回路有简单的充要条件每个顶点度数都是偶数而哈密顿回路至今没有简单的判定条件。这种对比本身就是组合数学里很迷人的现象规则差的是一点点结构的复杂程度却可能完全不在一个量级。4.2 二分图匹配与 Hall 定理二分图匹配是图论部分的重点也是被认为“最有用”的内容之一。二分图的顶点分成左右两组边只连接左右两侧的顶点。典型的应用是任务分配左边是若干任务或人员右边是若干资源或岗位边表示“可以做/愿意做”匹配问题就是“最多能安排多少对”。课程里最核心的结论是 Hall 定理左边顶点集合 X 能被完全匹配当且仅当对于 X 的任意子集 S它的邻居集合 N(S) 的规模都不小于 S 的规模。这个看起来很抽象的“对于任意子集”的条件翻译成人话就是左边无论你挑一撮人多重的需求右边都得有足够多的人愿意接。我当时学这个定理的时候靠画图才真正理解。把左边的几个人用一个圈圈起来再看右边有哪些顶点和它们相连如果右边被连到的数量比左边圈里的人还少那一定没法给这一撮人每个人都安排到不同对象。理解了这个直观含义之后再回头去看定理的证明就会顺很多。除了 Hall 定理课程还会介绍用匈牙利算法求最大匹配的思路。这一节虽然不会要求你把算法写出完整代码但理解它的迭代思想对后续学网络流和任务调度都很有帮助。4.3 拉丁方与组合设计构造之美组合设计这部分在不同老师的课程里差别比较大但成电一般会讲到拉丁方和平衡不完全区组设计BIBD。拉丁方就是一个 n×n 方阵用 n 种符号填充每行每列每个符号都恰好出现一次。它看起来像一个“填字游戏”实际上在实验设计、统计抽样、纠错码构造里都有应用。BIBD 是更一般的设计有 v 个对象把它们分到 b 个组里每组包含 k 个对象每个对象出现在 r 个组中任意两个对象恰好在 λ 个组里同时出现。五组参数 (v, b, r, k, \lambda) 之间有自然的恒等关系(vr bk) 以及 (\lambda(v-1) r(k-1))。这些恒等式不复杂但很有用很多时候你只需要知道其中几个参数就能判断一个设计是否存在或者推算出剩余参数。很多同学觉得这章离计算机很远但若干年后你接触分布式系统里的数据分片、纠删码布局或者实验设计中的样本分组就会想起这些“平衡结构”。它们的核心思想一致在一个离散系统里如何通过精巧的排布让任意两个对象之间的关联保持均匀。组合设计教的不是某个具体算法而是一种“结构可控”的思维方式。5. 组合思想在计算机领域里的实际落点可能有人会问学完这门课除了应付考试它到底能用在哪儿我可以明确地说组合数学不是一门“你马上能看到产出”的课但它在计算机领域的渗透程度远超想象。这里挑三个最直接的场景聊一聊。5.1 算法复杂度先数清楚再谈优化算法分析里到处都是计数。排序算法的最坏情况比较次数是多少是计数哈希表的冲突概率是计数随机化算法的期望复杂度也是计数。组合数学提供的不是某个现成公式而是一整套“把复杂过程拆成可计数片段”的思路。最典型的例子是二分搜索和归并排序。归并排序的复杂度递推式 (T(n)2T(n/2)n)从组合数学角度看就是一棵递归树的层数与每层总工作量的乘积。递归树总共约 (\log_2 n) 层每层总工作量是 (O(n))所以整体是 (O(n\log n))。这种分析方法不是死记硬背而是把问题还原成“树的结构与层内成本”这恰恰是组合数学训练带给你的直觉。5.2 信息安全生日攻击与哈希碰撞信息安全领域有一个组合数学的著名结论叫“生日问题”在一个房间里只需要 23 个人就有超过 50% 的概率出现两个人生日相同。大多数人第一次听到都会觉得不可思议因为 23 对 365 实在太小了。但概率算下来确实如此因为两个人生日相同的碰撞次数不是线性增长而是组合数量级 (C(n,2))。这个结论直接对应哈希碰撞问题。如果你用一个 64 位的哈希值很多人会想当然地认为要尝试 (2^{64}) 次才有碰撞但生日攻击告诉我们实际上大约只需要 (2^{32}) 量级的尝试就会以显著概率发生碰撞。对做网络安全、密码存储的同学来说这是一条必须刻在脑子里的安全边界。它背后没有高深的数学就是组合计数但它的影响可能决定一个系统是否会被轻易攻破。5.3 网络结构与资源调度组合数学还大量出现在通信与网络领域。数据中心的网络拓扑经常使用特殊图结构比如超立方体、胖树选择哪种拓扑本质上是在比较图的性质直径多大、容错性如何、并行路径有多少。而分布式系统中如何把数据副本均匀地分散到不同节点才能保证任意两个节点故障时数据仍可恢复这可以直接建模成 BIBD 或拉丁方问题。组合设计的“平衡性”在这里发挥了实实在在的作用让你不需要逐个场景暴力枚举而是从结构上就保证某种均匀性。这些应用课程正文不会展开讲但我建议你在学每一章时都多想一步这个东西将来能在哪个场景变成模型带着问题学你的收获会比单纯为期末考试多出不少。6. 给正在选修或准备学习的你避坑与路线建议最后说说我自己一路学下来的体会和教训包括哪些地方容易卡壳哪些做法让我真正开窍。6.1 先把“递推—递归—动态规划”这条链打通我会把递推关系和生成函数视作这门课最容易引发两极分化的章节。如果前面排列组合学得还好到了这里突然跟不上大概率是因为你还没有建立起“局部关系可以决定全局”的心智模型。建议的做法是在学递推之前先回到程序里看几个最简单的递归函数比如 Fibonacci 的朴素递归和带记忆化的动态规划。你看着它一步一步把较大问题拆成较小问题再看着它一层层返回结果这个过程本身就演示了递推关系的含义。有了这个代码层面的直觉再回到数学课上接受“通项公式”会顺利很多。6.2 刷题不要贪多但每道题都要做完整的“建模三部曲”组合数学的题目类型五花八门但解题路径高度一致。我给自己定了一套流程简单说就是三步先判断题考察的模型类型——是计数、是存在性、还是结构构造再把问题准确翻译成集合、数列或图的语言最后才套用公式或定理。很多人拿到一道题就急着代公式结果往往是在不该用的地方用错公式。比如看到“至少”两个字就条件反射用容斥但有些“至少”其实用补集思想做更简单。真正稳妥的做法是先在小例子上试数用小规模情况验证一下你的公式是否合理再推广。这一步看似多花时间实际上能避免大量无意义的重做。6.3 常见失分点顺序、初值和符号期末卷面上几个高频失分点这里提前给你提个醒。第一排列组合忘判顺序。判断要不要考虑顺序最可操作的标准不是“看语境”而是“交换两个元素后操作结果是否仍算同一个方案”。如果交换后方案不变就是组合如果变了就是排列。这比反复琢磨题目用词要可靠得多。第二递推通项求出后不验算。前面说过特征方程解出通项后必须回代前几项验证。我当年有一次考试题目全部思路都对但初值代错导致最后通项公式错得离谱那道题几乎全扣教训太惨痛了。第三容斥原理的符号方向。处理多集合问题时建议你每次都在草稿上从 n1、n2 如此小的情景开始推算确认哪一项该加、哪一项该减再推广。一个小技巧是每写一层并集公式都默念一遍“单项之和减二交、再加三交”但更重要的是理解“奇负偶正”这一规律这样即使公式记岔了也能现场纠正。6.4 值得搭配的教材和学习资源教材方面Brualdi 的《组合数学》和这门课的匹配度很高从排列组合、容斥原理到递推关系、图论匹配章节组织几乎和课堂大纲同步。中文世界里卢开澄的《组合数学》也适合入门。如果英语阅读没问题还可以把《A Course in Combinatorics》作为进阶读物里面的题目质量很高适合想冲刺更高难度的同学。网课资源可以在慕课平台或 B 站搜索“组合数学”很多高校都放出了完整课程视频。但我的建议是网课视频适合做“定点补课”哪里不会补哪里不要指望刷视频就能替代课堂和刷题。组合数学这门课的特点是“看懂了不一定会做做出来才能说明懂”所以时间分配上至少要给“动手做题”留出一半以上的精力。6.5 最后说点我个人的体会这门课在成电口碑其实挺两极分化。一部分同学觉得它抽象、证明多、和实际业务代码没关系另一部分同学则会在学完算法分析、网络协议甚至做科研的时候反复庆幸自己当初认真学了。我属于后者。毕业之后工作这些年我最大的体会是组合数学不一定改变你写代码的手速但会改变你看问题的视角。你面对一个系统会本能地去想它的状态空间有多大、最坏情况在哪里、有什么重复计算可以避免。这种思维习惯一旦养成带来的收益远不止期末考试那点分数。所以我希望你学这门课时不要只冲着“怎么得分”去而是多问一步“它到底在回答什么本质问题”。只要你能把这一步想清楚分数往往也会跟着来。