小A要筹办一场“红蓝派对”:所有嘉宾互相之间可能认识,他必须给每个人发一顶红帽或蓝帽,规则是互相认识的两个人不能戴同色帽子。发着发着他发现,有些朋友关系很容易分色,而有些圈子(比如三个彼此都认识的人)怎么分都会撞色。派对下半场,小A又想组织“两人搭档游戏”,每位嘉宾最多参加一次,他希望凑出尽可能多的搭档组合。这两个任务,正是图论中二部图判定和最大匹配问题的原型。本文会用“发帽子”的故事讲清楚二部图、BFS/DFS染色判定、奇环等价、匈牙利算法,并附上可运行代码和实战场景。
掌握二部图判定与匈牙利算法,是从事图算法、调度优化、推荐匹配类AI工程的必修基本功,能帮你在建模时快速识别“两类对象一对一匹配”问题并给出高效解法。
可视化演示
二部图的顶点可分为两个集合且边只跨集合连接,等价于不含奇环。本文详解二部图的BFS与DFS染色判定方法、与无奇... 切换模式查看不同维度。
本节配有交互动效(进入视口后自动加载,约 3KB)。
一、二部图的:核心原理 动态展示关键流程与数据流动 交互控件调节参数实时渲染 f(x) = transform(x) 核心概念的可视化呈现
二部图定义
小A第一次拿到宾客名单时发现,只要把所有人分成“红帽组”和“蓝帽组”,并且保证每对互相认识的人都站在不同组,派对就能顺利开场。这个朴素想法正是二部图的核心:顶点可以被分成两个集合,所有边都只连接两个集合之间。
在数学上,一张无向图 \(G\) 是二部图,当且仅当它的顶点集 \(V\) 能被拆成两个互不相交的集合 \(X\) 和 \(Y\),并且图中任意一条边的一个端点在 \(X\),另一个端点在 \(Y\)。集合内部不允许出现边,这就是“组内无朋友、组间才有朋友”。
如果 \(X\) 中每个顶点都与 \(Y\) 中每个顶点相连,就得到完全二部图 \(K_{m,n}\),它像两个阵营之间“全员加好友”的极端情况。理解定义后,下一步的关键问题就是:给定一张图,怎么快速判断它是不是二部图?
- 顶点划分\(X\cup Y=V\) 表示所有顶点恰好落入两个集合,且同一顶点只能属于一边。
- 边集限制\(\{\{u,v\}:u\in X, v\in Y\}\) 表示只允许连接 \(X\) 与 \(Y\) 的无向边存在。
- 整体表达把 \(G\) 写成 \((X\cup Y,E)\),就是用集合语言给“红蓝两组”下了严格定义。
染色判定法
小A拿起帽子盒,从第一位嘉宾开始发帽子:他戴红帽,那么所有朋友都必须戴蓝帽,朋友的朋友再戴红帽……这个“波浪式发帽子”的过程,在图论里叫作染色。
BFS染色法用队列一层层传播颜色,DFS染色法则用递归沿着一条路走到黑再回头。两者本质相同:访问到一个未染色顶点时,给它染上“与邻居相反”的颜色;如果发现一条边两端已染色且颜色相同,就立刻判定该图不是二部图。因为此时这条边上的两个朋友被迫戴了同色帽,违反了规则。
无论从哪个顶点开始,只要全图能被两种颜色完成染色且无同色边,它就是二部图。BFS和DFS的复杂度都是 \(O(V+E)\),相当于小A顺着朋友关系把所有帽子发一遍,每条朋友关系只检查一次,非常高效。
- 全称约束\(\forall (u,v)\in E\) 表示对图中每一条无向边都要求成立。
- 异色条件\(c(u)+c(v)=1\) 规定两个端点颜色值互异,比如 0 配 1。
- 冲突反例若某条边两端已染色且 \(c(u)=c(v)\),则 \(c(u)+c(v)\neq 1\),说明染色方案无法继续。
无奇环等价
小A很快发现一个规律:如果某个朋友圈恰好围成一个“三人铁三角”,红蓝帽子必定撞车;但四人围成的圈可以红蓝交替圆满解决。原因很简单——奇数长度的环无法完成二染色。
更一般地,一个图是二部图,当且仅当它不含奇数长度的环(简称奇环)。必要性很直观:奇环上从某个顶点出发走回自己,需要经过奇数条边,交替染色会让起点同时被要求戴两种颜色,矛盾。充分性则需要借助BFS分层:从起点出发按深度染色,令颜色等于深度对 2 取余;如果某条边连接两个同深度顶点,就说明存在奇环,否则染色成功。
所以小A不必真的把每条朋友关系都检查一遍,只要找到任何一处“奇数长度朋友圈”,就能断定这组嘉宾没法分成红蓝两队。这就是判定二部图的“捷径”定理。
- 深度取模\(d(v)\) 是顶点 \(v\) 在BFS树中的深度,用 2 取模即可得到 0 或 1 的颜色。
- 同层边矛盾若边 \((u,v)\) 满足 \(d(u)=d(v)\),则 \(c(u)=c(v)\),二染色失败。
- 奇环来源同层边加上BFS树中的两条路径会拼成一个奇数长度环,于是等价关系成立。
匹配与增广
分出红蓝两组后,小A开始安排搭档游戏:每位嘉宾最多参加一次,搭档两人必须互相认识,他希望让尽可能多的人都玩上游戏。这个“一人最多一搭档”的边集合,就叫匹配。
匹配是图中一个边集,其中任意两条边不共享顶点。边数最多的匹配叫作最大匹配。如果当前匹配已经无法通过简单添加一条边来变大,还有办法吗?有——增广路径:一条从某个未匹配顶点出发,交替经过非匹配边、匹配边、非匹配边……最后停在另一个未匹配顶点上的路径。把这条路径上的匹配边和非匹配边互换,匹配数就会增加 1。
增广路径就像多米诺骨牌:一个人让位给新人,自己再去找下一位,一路传递下去,最终多凑出一组搭档。匈牙利算法正是利用这个思路,反复寻找增广路径直到找不到为止。
- 边子集匹配 \(M\) 是原图边集 \(E\) 的一个子集,不是所有边都能入选。
- 不相交条件任意两条匹配边 \(e_i,e_j\) 满足 \(e_i\cap e_j=\varnothing\),即公共顶点为空。
- 最大匹配判定Berge定理指出,\(M\) 是最大匹配当且仅当图中不存在相对 \(M\) 的增广路径。
匈牙利算法
匈牙利算法是小A的“换搭档方案”:从每个还没有搭档的嘉宾出发,沿着好感关系尝试寻找增广路径;能找到就翻转路径让匹配数加 1,找不到就说明当前配对已经是最优。这个算法由匈牙利数学家 Edmonds 提出,因此得名。
算法用 DFS 实现最简洁。设左侧顶点集合为 \(X\),右侧为 \(Y\),用邻接表记录每个左侧顶点可选的右侧顶点。对每个左侧未匹配顶点 \(u\),递归尝试其所有邻接顶点 \(v\):若 \(v\) 未被访问且 \(v\) 未匹配,或能让 \(v\) 当前匹配的那个左侧顶点重新找到新搭档,就把 \(v\) 匹配给 \(u\)。整个过程本质上是在寻找一条增广路径,找到就沿路径取反。
找到增广路径后,把匹配边和非匹配边交换,记作 \(M \leftarrow M \oplus P\),其中 \(\oplus\) 表示对称差。小A每次引入一个新嘉宾都想方设法“挤”进一组配对,最终所有人都无法再挤入时,就得到了最大匹配。
- 存在性\(\exists v\in N(u)\) 表示尝试所有与 \(u\) 相邻的右侧顶点,只要有一个成功即可。
- 直接匹配\(\operatorname{unmatched}(v)\) 表示 \(v\) 空闲,可以立刻把 \(v\) 分给 \(u\)。
- 递归让位\(\text{DFS}(\operatorname{match}[v])\) 表示 \(v\) 已匹配,则让当前匹配对象 \(match[v]\) 另寻出路。
复杂度模板
小A发现,派对规模一大,“全场花名册”(邻接矩阵)会占很多内存,而“每人一张朋友名单”(邻接表)既省空间又方便遍历。匈牙利算法高效的关键,正是邻接表存储和递归 DFS 模板。
设左侧顶点数为 \(|X|\),右侧顶点数为 \(|Y|\),边数为 \(|E|\)。每个左侧顶点最多执行一次 DFS,每次递归会扫描与之相邻的边,因此时间复杂度是 \(O(|X|\cdot|E|)\),通常简写为 \(O(VE)\)。空间上只存储邻接表和 match 数组,复杂度为 \(O(V+E)\)。在竞赛中,当点数在数百、边数在数万规模时,这个复杂度完全可行。
模板化的代码让小A可以快速套用:建图、跑匈牙利、输出匹配数。这也是算法竞赛和工程中最常见的“三板斧”。
- 时间上界最坏情况下每个左侧点都要走遍所有边,因此乘起来是 \(O(VE)\)。
- 空间上界邻接表存储所有边为 \(O(E)\),match 数组保存所有顶点为 \(O(V)\)。
- 模板要点每次 DFS 前重置 visited 数组,match[v] 表示右侧顶点 \(v\) 匹配的左侧顶点。
实战应用
小A从派对中提炼出的方法,其实能解决大量现实问题:只要有“两类对象之间的偏好关系”,做一对一的资源分配,就可以建模成二部图最大匹配。先判断是否为二部图,再调用匈牙利算法求最大匹配,是一套通用解题框架。
在AI与工程场景中,二部图建模无处不在:任务分配把员工和任务当作两组顶点,偏好关系作为边;推荐系统把用户和候选物当作两组顶点;网络调度把请求和资源当作两组顶点。更妙的是,二部图还有一个重要性质——最小点覆盖大小等于最大匹配大小(Kőnig定理),这让我们能用匹配算法同时解决覆盖类问题。
小A的派对最终圆满成功:先分好红蓝两队,再配出最多的搭档组合。这套从“发帽子”到“牵线搭桥”的思路,正是二部图判定与匈牙利算法在真实世界中的价值所在。
- 最小点覆盖\(\tau(G)\) 表示用最少的顶点覆盖所有边,每条边至少有一个端点被选中。
- 最大匹配数\(\nu(G)\) 表示匹配边数的最大值。
- 等式意义在二部图中这两个最优化数值相等,因此求解匹配可以反过来解决覆盖问题。
AI 应用场景
外卖平台骑手订单分配
阶段:数据建模与组合优化
解决的问题:传统人工派单经常出现骑手与订单冲突,导致部分订单无人接。把骑手和订单作为二部图的两类顶点,骑手与可送达订单之间连边,求最大匹配即可得到“最多有多少订单能被覆盖”。
具体案例:某外卖平台在午高峰时段,将 50 名骑手与 80 个订单建模为二部图,骑手与可配送订单之间连边,用匈牙利算法求出最大匹配。平台优先保证匹配成功的订单履约,剩余订单再转入下一轮调度。
社交平台活动组队
阶段:召回与匹配
解决的问题:线下社交活动希望把参与者两两组队,每个人只能加入一队,且双方需要互相接受。这个“一对一偏好匹配”问题正是二部图最大匹配的经典场景。
具体案例:某相亲活动组织者收集男女嘉宾的互选意向,建立二部图,边表示互相喜欢。用匈牙利算法求得最大互选配对组合,让更多嘉宾在首轮就能配对成功,提升活动满意度。
云平台任务调度
阶段:资源分配优化
解决的问题:云计算平台要把一批计算任务分配给物理服务器,每个任务只能运行在一台服务器上,且服务器有容量限制。将任务与服务器视为二部图两侧,能容纳的关系连边,最大匹配帮助平台在有限资源下完成最多任务。
具体案例:某云厂商在资源紧张时段,将 20 个待迁移容器与 15 台宿主机建模成二部图,容器与可承载的宿主机连边。通过匈牙利算法一次求出最大匹配方案,成功让尽可能多的容器在截止时间前完成迁移。
Python 演示
# 匈牙利算法求二部图最大匹配def hungarian(match, visited, adj, u): # 尝试为左侧顶点u寻找匹配,成功返回True for v in adj[u]: if visited[v]: continue visited[v] = True if match[v] == -1 or hungarian(match, visited, adj, match[v]): match[v] = u return True return False def max_matching(n_left, n_right, edges): # 构建邻接表 adj = [[] for _ in range(n_left)] for u, v in edges: adj[u].append(v) # match[v]记录右侧顶点v匹配的左侧顶点 match = [-1] * n_right result = 0 for u in range(n_left): visited = [False] * n_right if hungarian(match, visited, adj, u): result += 1 return result, match if __name__ == '__main__': # 左侧3个顶点,右侧3个顶点,边表示愿意配对 edges = [(0, 0), (0, 1), (1, 1), (2, 0), (2, 2)] ans, match = max_matching(3, 3, edges) print("最大匹配数:", ans) print("右侧匹配结果:", match)
def hungarian(match, visited, adj, u):定义递归函数,为左侧顶点 u 寻找增广路径。for v in adj[u]:遍历与 u 有边相连的右侧顶点,相当于小A翻看嘉宾的好感名单。if match[v] == -1 or hungarian(match, visited, adj, match[v]):若 v 空闲,或能让 v 当前搭档另寻出路,则尝试匹配。match[v] = u记录右侧顶点 v 与左侧顶点 u 配对成功。visited[v] = True每次递归都标记 v 已访问,避免在同一轮增广中反复占用同一右侧顶点。for u in range(n_left):依次为每个左侧未匹配顶点尝试寻找增广路径。visited = [False] * n_right每处理一个新的左侧顶点前,重置访问标记数组。adj = [[] for _ in range(n_left)]用邻接表保存二部图,比邻接矩阵更省空间。
常见误区
误区我检查一张图时发现它有环,所以直接认定它不是二部图。
正解二部图并不是不能有环,而是不能有奇数长度的环。四环、六环等偶环完全可以通过红蓝交替染色成功。判定的金标准是“无奇环”而非“无环”,这一点尤其容易记混。
为何常见:初学者常把“环”和“奇环”混为一谈,是因为做判断题时总拿三角形当反例,形成了“有环即错”的直觉。
误区先用 BFS/DFS 把整张图都染上颜色,最后再统一检查有没有同色边。
正解正确的做法是边染色边检查。一旦发现某条边两端已染色且颜色相同,就应立即返回 False,不需要继续遍历。提前终止不仅能节省时间,也避免在不存在合法染色方案的图上做无用功。
为何常见:人们习惯“先处理完再验收”,但图染色是动态约束,冲突在过程中已经暴露,延迟检查反而增加实现复杂度。
误区只要找到一条还没进入匹配的边,把这条边直接加进匹配,匹配数就会增加。
正解直接加一条边可能会与已匹配边共享顶点,破坏匹配定义。增广路径必须是交替路径:起点和终点都是未匹配顶点,路径上的边交替出现非匹配边和匹配边。只有沿整条路径取反,才能安全地让匹配数 +1。
为何常见:这个概念依赖“交替”和“端点未匹配”两个条件,初学者只记住了“能变大”,忽略了路径结构要求。
误区左侧有 3 个点、右侧有 4 个点,那最大匹配一定是 3。
正解最大匹配受边是否存在限制。如果左侧某个顶点与右侧任何顶点都没有边,它就永远无法匹配。最大匹配最多不超过 \(\min(|X|,|Y|)\),但能否取到上限取决于图的结构,不能想当然。
为何常见:因为完全二部图 \(K_{m,n}\) 中匹配数一定等于 \(\min(m,n)\),看多了这种特例就容易把它当成所有二部图的结论。
误区匈牙利算法要求二部图左右顶点数一样多,否则不能求最大匹配。
正解完全不需要。匈牙利算法天然支持 \(|X|\neq|Y|\),只要邻接表、match 数组按各自顶点数初始化即可。即使某侧顶点很多,也只会遍历实际存在的边,不会因为顶点数不平衡而出错。
为何常见:很多教程例子都用 3×3 或 4×4 的“完美匹配”演示,让人误以为两侧必须等大,其实最大匹配和完美匹配是两个不同概念。
面试题
Q1. 二部图的核心定义是什么?
- A.图中所有顶点度数都为 2
- B.顶点能分成两组,所有边只连接两组之间← 正确答案
- C.图中没有边
- D.图中只包含偶数个顶点
答案:B。二部图要求顶点集可划分为两个集合,且每条边都跨集合连接,组内无连边。A 描述的是环,C 是空图,D 只要求偶数顶点但未规定边结构,均不准确。
Q2. 一个三角形(3 个顶点两两相连)是二部图吗?
- A.是,因为可以分成 1 和 2 两组
- B.是,因为三个顶点都是朋友
- C.不是,因为三角形是奇环无法二染色← 正确答案
- D.不是,因为三角形顶点数不是偶数
答案:C。三角形是长度为 3 的奇环。二染色时第一个顶点染蓝,第二个染红,第三个又必须与第一个异色而染红,但它和第二个之间也有边,同色冲突,因此不是二部图。C 正确,D 理由错误。
Q3. BFS 染色判定中,起点染成蓝色,那么它的未访问邻居应染成什么颜色?
- A.蓝色
- B.红色← 正确答案
- C.随机颜色
- D.不染色
答案:B。染色规则是相邻顶点颜色不同,所以与蓝色顶点相邻的未访问顶点必须染成相反颜色,即红色。若邻居已染成蓝色则产生冲突。
Q4. 下列关于“二部图等价于无奇环”的说法,正确的是?
- A.二部图可以包含奇环
- B.无偶环的图一定是二部图
- C.图是二部图当且仅当图中没有奇数长度的环← 正确答案
- D.只要没有环就是二部图
答案:C。二部图等价于无奇环。偶环被允许,所以 A 错;无偶环不等于无奇环,B 错;D 只说了无环这一种特殊情况,树无环当然是二部图,但其他无奇环有偶环的图也是二部图,D 表述不全面。
Q5. 用染色法判定二部图时,发现一条边两端颜色相同,这说明?
- A.图一定没有环
- B.图含偶数环
- C.图含有奇环,不是二部图← 正确答案
- D.染色顺序错误,换起点一定成功
答案:C。相邻同色意味着二染色失败,等价于图中存在奇环,因此图不是二部图。换起点也无法消除奇环,D 错。
Q6. 在匈牙利算法中,增广路径的作用是什么?
- A.增加一个顶点
- B.使匹配数加 1← 正确答案
- C.使匹配数减 1
- D.删除一条匹配边
答案:B。增广路径是一条交替路径,起点和终点都未匹配。沿路径翻转匹配边与非匹配边后,路径上的非匹配边变为匹配边,匹配数恰好 +1。因此只要存在增广路径,匹配数就能增加。
Q7. Berge 定理指出,匹配 \(M\) 是最大匹配的充要条件是?
- A.\(M\) 包含图中所有边
- B.\(M\) 中不含增广路径← 正确答案
- C.\(M\) 中每条边都连接左右两组
- D.\(M\) 的大小等于顶点数
答案:B。Berge 定理说,一个匹配是最大匹配,当且仅当图中不存在相对于它的增广路径。如果还有增广路径,就能让匹配继续变大;反之没有增广路径,则已无法再提升匹配数。
Q8. 以下哪些图一定是二部图?
- A.长度为 4 的环← 正确答案
- B.一棵树← 正确答案
- C.三角形
- D.完全二部图 \(K_{2,3}\)← 正确答案
答案:ABD。偶环可以红蓝交替染色,是二部图;树没有环,可由根节点按深度奇偶染色,是二部图;\(K_{2,3}\) 按定义就是二部图。三角形是奇环,不是二部图。
Q9. 关于匈牙利算法的描述,正确的有?
- A.时间复杂度通常是 \(O(VE)\)← 正确答案
- B.核心是不断寻找增广路径← 正确答案
- C.只能处理完全二部图
- D.找不到增广路径时得到最大匹配← 正确答案
答案:ABD。匈牙利算法对每个左侧点尝试一次 DFS,每次最多遍历所有边,所以时间 \(O(VE)\),A 正确。它反复寻找增广路径并翻转,B 正确。它适用于一般二部图而非仅完全二部图,C 错。由 Berge 定理,无增广路径即为最大匹配,D 正确。
Q10. 关于二部图匹配理论,下列说法正确的有?
- A.二部图的最小点覆盖大小等于最大匹配大小← 正确答案
- B.增广路径的起点和终点都必须是未匹配顶点← 正确答案
- C.二部图中存在奇环则不能成功二染色← 正确答案
- D.二部图最大匹配边数一定不超过两侧顶点数较小值← 正确答案
答案:ABCD。A 是 Kőnig 定理,正确。B 是增广路径的定义,正确。C 是奇环与染色的关系,正确。D 因为每条匹配边占用左右各一个顶点,总数自然不超过 \(\min(|X|,|Y|)\),正确。四者都属于二部图匹配理论的基本结论。