二分图: 一个图中的所有顶点可划分为两个不相交的集合 U 和 V ,使得每一条边都分别连接U、V中的顶点。如果存在这样的划分,则此图为一个二分图。 匹配: 一个匹配就是一个边的集合,这个边集合中的任意两条边没有公共的顶点。 最大匹配: 一个图的所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。 最大匹配数: 最大匹配的匹配边的数目。 完美匹配: 如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。显然,完美匹配一定是最大匹配(完美匹配的任何一个点都已经匹配,添加一条新的匹配边一定会与已有的匹配边冲突)。但并非每个图都存在完美匹配。 最小顶点覆盖数: 选取最少的点,使二分图中任意一条边至少有一个端点被选择。最小覆盖要求用最少的点(U集合或V集合的都行)让每条边都至少和其中一个点关联。可以证明:最少的点(即覆盖数)=最大匹配数 最大独立数: 选取最多的点,使任意所选两点均不相连。 最小路径覆盖数: 对于一个 DAG(有向无环图),选取最少条数的路径,使得每个顶点属于且仅属于一条路径,路径长可以为 0(即单个点)。也就是说用尽量少的不相交简单路径覆盖DAG的所有结点。 一些性质: 定理1:最大匹配数=最小点覆盖数(Konig 定理) 定理2:最大匹配数=最大独立数 定理3:最小路径覆盖数=顶点数-最大匹配数
交替路: 从一个未匹配的点出发,依次经过非匹配边、匹配边、非匹配边······这样形成的路径叫交替路。 增广路: 从一个未匹配的点出发,走交替路,如果路过一个未匹配的点(第一个点除外),这条交替路叫增广路。 增广路有一个重要特点:非匹配边比匹配边多一条。因此,研究增广路的意义是改进匹配。只要把增广路中的匹配边和非匹配边的身份交换即可。由于中间的匹配节点不存在其他相连的匹配边,所以这样做不会破坏匹配的性质。交换后,图中的匹配边数目比原来多了 1 条。 我们可以通过不停地找增广路来增加匹配中的匹配边和匹配点。找不到增广路时,达到最大匹配(这是增广路定理)。匈牙利算法正是这么做的,这也是算法的核心。
论文:The Hungarian Method for the Assignment Problem 论文地址:https://web.eecs.umich.edu/~pettie/matching/Kuhn-hungarian-assignment.pdf 。 重要定理: 如果从一个点A出发,没有找到增广路径,那么无论再从别的点出发找到多少增广路径来改变现在的匹配,从点A出发都永远找不到增广路径。 匈牙利算法的核心思想: 初始时最大匹配为控,然后不断寻找增广路,并扩展增广路。不断重复这一过程直到找不到增广路为止。 如果二分图左半边U集合中共有n个点,那么最多找n条增广路径。如果图中共有m条边,那么每找一条增广路径(DFS或BFS)时最多把所有边遍历一遍,所花时间也就是m。所以总时间大概就是O(nm)。 使用DFS查找的匈牙利匹配算法流程: 从左边U集合找出第一个未匹配点(假设为点1)进行BFS边搜索,寻找一条未匹配边。如果找到一条未匹配边(假设为边1),这个边的另一头是右边V集合中的一个未匹配点(假设为点A),说明寻找成功。更新路径信息,匹配边数+1; 再找到左边U集合的第二个未匹配点(假设为点2),进行BFS边搜索。假如搜索到一条边(假设为边2),该边连接的另一个点V集合中前一轮已经匹配的点(点A),那么我们将边2作为点2和点A的未匹配边,然后对放弃的未匹配边(边1)的另一个点(点1)重新进行BFS边搜索(这次搜索跳过已经选择过的边1),寻找另一条未匹配边。假设我们找到了新的一条为匹配边(边3),该边连接点1和点C。 对于后面的每一轮,我们都从左边U集合中找出一个未匹配点x,然后进行BFS边搜索,如果搜到一条边,其另一点是V集合中前几轮中已经匹配的点y’,那么我们找到y’以前的未匹配边t,找到该边另一头属于左边U集合的点x’,对x’进行BFS边搜索(跳过前面已经选择的边),继续寻找未匹配边。如果x’BFS搜索到下一条边的另一点也是V集合中前几轮中已经匹配的点y’’,那么继续迭代执行上述过程。直到最后迭代找到的点的所有边都搜索过,且找不到为匹配边为止,此时我们就认为本轮最初选择的未匹配点x和点y’无法配对,我们不再从这个点x开始搜索。
原始二分图中,集合U有点1、2、3、4,集合V中有点5、6、7、8,边有:1-5,1-6,2-5,3-6,3-8,4-7。
1---5 \ / / \ 2 6 / / 3 7 \ / / \ 4 8第一轮: 选取集合U中未匹配点1,进行BFS边搜索,找到第一条边1-5,5是集合V中未匹配点,这样我们找到了一条边,将集合U的未匹配点1和集合V的未匹配点5配对,我们用link[5] = 1来表示。 第二轮: 接着选取集合U中未匹配的点2,进行BFS边搜索,找到第一条边2-5,但关于5这个点前面已经标记了一个配对边1-5,那么我们再对原来的点1继续进行BFS边搜索,发现另一条边1-6,6是集合V中未匹配点,于是将集合U的未匹配点1和集合V的未匹配点6配对,用link[6] = 1表示,此时link[5] = 1不变,但是要注意它此时的含义是点2和点5配对。 第三轮: 接着选取集合U中未匹配的点3,进行BFS边搜索,找到第一条边3-6,由于link[6] = 1,于是向前找到原来与6配对的点1,对点1再做BFS边搜索,发现边1-5,但点5已经和点2配对了。那么再对点2做BFS边搜索,发现2只有边2-5,搜索到头了只能停止。因此点3不能和点6配对。 再找点3的第二条边,找到边3-8,8是集合V中未匹配点,这样我们将集合U的未匹配点3和集合V的未匹配点8配对,我们用link[8] = 1来表示。 第四轮: 接着选取集合U中未匹配点4,进行BFS边搜索,找到第一条边4-7,7是集合V中未匹配点,这样我们将集合U的未匹配点4和集合V的未匹配点7配对,我们用link[7] = 1来表示。
这样我们就找到了这个二分图的最大匹配。最大匹配中包含的边是1-6,2-5,3-8,4-7。
运行结果如下:
开始匹配U集合中第1个点: 1 -> 5 开始匹配U集合中第2个点: 1 -> 7 2 -> 5 开始匹配U集合中第3个点: 3 -> 6 开始匹配U集合中第4个点: 4 -> 8 最大匹配边数:4 match: [4, -1, -1, -1, 0, -1, -1, -1] prev: [-1, 0, 0, 0, 0, 0, 0, 0] deque deque([]) match: [6, 4, -1, -1, 1, -1, 0, -1] prev: [1, -1, 0, 0, 0, 0, 0, 0] deque deque([]) match: [6, 4, 5, -1, 1, 2, 0, -1] prev: [1, 2, -1, 0, 0, 0, 0, 0] deque deque([1]) match: [6, 4, 5, 7, 1, 2, 0, 3] prev: [3, 2, -1, -1, 0, 0, 0, 0] deque deque([0]) 最大匹配边数:4