二分图匹配(匈牙利算法和KM算法)

合集下载

匈牙利算法解决二分图最大匹配

匈牙利算法解决二分图最大匹配

匈⽛利算法解决⼆分图最⼤匹配预备知识 匈⽛利算法是由匈⽛利数学家Edmonds于1965年提出,因⽽得名。

匈⽛利算法是基于Hall定理中充分性证明的思想,它是⼆分图匹配最常见的算法,该算法的核⼼就是寻找增⼴路径,它是⼀种⽤增⼴路径求⼆分图最⼤匹配的算法。

⼆分图 ⼆分图⼜称作⼆部图,是图论中的⼀种特殊模型。

设G=(V,E)是⼀个⽆向图,如果顶点V可分割为两个互不相交的⼦集(A,B),并且图中的每条边(i,j)所关联的两个顶点 i 和 j 分别属于这两个不同的顶点集(i in A,j in B),则称图G为⼀个⼆分图。

匹配 在图论中,⼀个图是⼀个匹配(或称独⽴边集)是指这个图之中,任意两条边都没有公共的顶点。

这时每个顶点都⾄多连出⼀条边,⽽每⼀条边都将⼀对顶点相匹配。

例如,图3、图4中红⾊的边就是图2的匹配。

图3中1、4、5、7为匹配点,其他顶点为⾮匹配点,1-5、4-7为匹配边,其他边为⾮匹配边。

最⼤匹配 ⼀个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最⼤匹配。

图 4 是⼀个最⼤匹配,它包含 4 条匹配边。

任意图中,极⼤匹配的边数不少于最⼤匹配的边数的⼀半。

完美匹配 如果⼀个图的某个匹配中,所有的顶点都是匹配点,那么它就是⼀个完美匹配。

显然,完美匹配⼀定是最⼤匹配,但并⾮每个图都存在完美匹配。

最⼤匹配数:最⼤匹配的匹配边的数⽬。

最⼩点覆盖数:选取最少的点,使任意⼀条边⾄少有⼀个端点被选择。

最⼤独⽴数:选取最多的点,使任意所选两点均不相连。

最⼩路径覆盖数:对于⼀个DAG(有向⽆环图),选取最少条路径,使得每个顶点属于且仅属于⼀条路径,路径长可以为0(即单个点)定理1:Konig定理——最⼤匹配数 = 最⼩点覆盖数定理2:最⼤匹配数 = 最⼤独⽴数定理3:最⼩路径覆盖数 = 顶点数 - 最⼤匹配数匈⽛利算法例⼦ 为了便于理解,选取了dalao博客⾥找妹⼦的例⼦: 通过数代⼈的努⼒,你终于赶上了剩男剩⼥的⼤潮,假设你是⼀位光荣的新世纪媒⼈,在你的⼿上有N个剩男,M个剩⼥,每个⼈都可能对多名异性有好感(惊讶,-_-||暂时不考虑特殊的性取向) 如果⼀对男⼥互有好感,那么你就可以把这⼀对撮合在⼀起,现在让我们⽆视掉所有的单相思(好忧伤的感觉,快哭了),你拥有的⼤概就是下⾯这样⼀张关系图,每⼀条连线都表⽰互有好感。

二分图的最大匹配、完美匹配和匈牙利算法

二分图的最大匹配、完美匹配和匈牙利算法

二分图的最大匹配、完美匹配和匈牙利算法August 1, 2013 / 算法这篇文章讲无权二分图(unweighted bipartite graph)的最大匹配(maximum matching)和完美匹配(perfect matching),以及用于求解匹配的匈牙利算法(Hungarian Algorithm);不讲带权二分图的最佳匹配。

二分图:简单来说,如果图中点可以被分为两组,并且使得所有边都跨越组的边界,则这就是一个二分图。

准确地说:把一个图的顶点划分为两个不相交集U和V,使得每一条边都分别连接U、V中的顶点。

如果存在这样的划分,则此图为一个二分图。

二分图的一个等价定义是:不含有「含奇数条边的环」的图。

图 1 是一个二分图。

为了清晰,我们以后都把它画成图 2 的形式。

匹配:在图论中,一个「匹配」(matching)是一个边的集合,其中任意两条边都没有公共顶点。

例如,图3、图 4 中红色的边就是图 2 的匹配。

我们定义匹配点、匹配边、未匹配点、非匹配边,它们的含义非常显然。

例如图 3 中 1、4、5、7 为匹配点,其他顶点为未匹配点;1-5、4-7为匹配边,其他边为非匹配边。

最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。

图 4 是一个最大匹配,它包含 4 条匹配边。

完美匹配:如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。

图 4 是一个完美匹配。

显然,完美匹配一定是最大匹配(完美匹配的任何一个点都已经匹配,添加一条新的匹配边一定会与已有的匹配边冲突)。

但并非每个图都存在完美匹配。

举例来说:如下图所示,如果在某一对男孩和女孩之间存在相连的边,就意味着他们彼此喜欢。

是否可能让所有男孩和女孩两两配对,使得每对儿都互相喜欢呢?图论中,这就是完美匹配问题。

如果换一个说法:最多有多少互相喜欢的男孩/女孩可以配对儿?这就是最大匹配问题。

基本概念讲完了。

二分图匹配题目类型总结.

二分图匹配题目类型总结.

二分图匹配题目类型总结二分图最大匹配的匈牙利算法二分图是这样一个图,它的顶点可以分类两个集合X和Y,所有的边关联在两个顶点中,恰好一个属于集合X,另一个属于集合Y。

最大匹配:图中包含边数最多的匹配称为图的最大匹配。

完美匹配:如果所有点都在匹配边上(x=y=m),称这个最大匹配是完美匹配。

最小点覆盖:(二分图)最小覆盖要求用最少的点(X集合或Y集合的都行)让每条边都至少和其中一个点关联。

可以证明:最少的点(即覆盖数)=最大匹配数。

支配集:(二分图)最小点覆盖数+孤立点最小边覆盖:找最大匹配(注意可能是任意图最大匹配)m则有2*m 个点被m 条两两不相交的边覆盖。

对于剩下的n-2*m 个点,每个点用一条边覆盖,总边数为n-m条;最小路径覆盖:用尽量少的不相交简单路径覆盖有向无环图G的所有结点。

解决此类问题可以建立一个二分图模型。

把所有顶点i拆成两个:X结点集中的i和Y结点集中的i',如果有边i->j,则在二分图中引入边i->j',设二分图最大匹配为m,则结果就是n-m。

最大独立集问题:(二分图)n-最小点覆盖;任意图最大匹配:(没有奇环)转换为二分图:把所有顶点i拆成两个:X结点集中的i和Y结点集中的i',如果原图中有边i->j,则在二分图中引入边i-> j',j->i’;设二分图最大匹配为m,则结果就是m/2。

最大完全子图:补图的最大独立集三大博弈问题威佐夫博奕(Wythoff Game):有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

这种情况下是颇为复杂的。

我们用(ak,bk)(ak ≤bk ,k=0,1,2,...,n)表示两堆物品的数量并称其为局势,如果甲面对(0,0),那么甲已经输了,这种局势我们称为奇异局势。

前几个奇异局势是:(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)。

最大权匹配KM算法

最大权匹配KM算法

最大权匹配KM算法
KM算法(Kuhn–Munkres算法,也称为匈牙利算法)是由Kuhn于
1955年和Munkres于1957年分别提出的,用于解决二分图最大匹配问题。

该算法的核心思想是基于匈牙利算法的增广路径,通过构建一个增广路径
来不断更新匹配,直到无法找到增广路径为止。

算法流程如下:
2.从G的每个未匹配顶点开始,通过增广路径将其标记为可增广点;
3.当存在增广路径时,将匹配的边进行反向操作,直到不存在增广路径;
4. 利用增广路径的反向操作可以修改lx和ly的值,使其满足特定
约束条件;
5.通过相等子图的扩展来实现增广路径的;
6.重复步骤3-5,直到不存在更多的增广路径;
7.返回找到的最大匹配。

具体实现时,对于增广路径的可以利用DFS或BFS等方法进行,当找
到一个增广路径时,通过反向操作修改匹配情况,并更新lx和ly的值。

同时,算法还可以使用增广路径来调整优化标号,以减少匹配时间。

KM算法是一种高效的解决最大权匹配问题的方法,其时间复杂度为
O(V^3),其中V为图的顶点数。

算法的核心思想是利用二分图中的相等子
图来查找增广路径,并通过修改顶点的标号来实现最大匹配。

总之,最大权匹配KM算法是一个解决带权无向二分图最大匹配问题
的高效算法,通过不断寻找增广路径并调整顶点的标号来实现最大权匹配。

它的思想简单而有效,可以广泛应用于各种实际问题中。

二分图匹配--匈牙利算法

二分图匹配--匈牙利算法

⼆分图匹配--匈⽛利算法⼆分图匹配--匈⽛利算法⼆分图匹配匈⽛利算法基本定义:⼆分图 —— 对于⽆向图G=(V,E),如果存在⼀个划分使V中的顶点分为两个互不相交的⼦集,且每个⼦集中任意两点间不存在边 ϵ∈E,则称图G为⼀个⼆分图。

⼆分图的充要条件是,G⾄少有两个顶点,且所有回路长度为偶数。

匹配 —— 边的集合,其中任意两条边都不存在公共顶点。

匹配边即是匹配中的元素,匹配点是匹配边的顶点,同样⾮匹配边,⾮匹配点相反定义。

最⼤匹配——在图的所有匹配中,包含最多边的匹配成为最⼤匹配 完美匹配——如果在⼀个匹配中所有的点都是匹配点,那么该匹配称为完美匹配。

附注:所有的完美匹配都是最⼤匹配,最⼤匹配不⼀定是完美匹配。

假设完美匹配不是最⼤匹配,那么最⼤匹配⼀定存在不属于完美匹配中的边,⽽图的所有顶点都在完美匹配中,不可能找到更多的边,所以假设不成⽴,及完美匹配⼀定是最⼤匹配。

交替路——从⼀个未匹配点出发,依次经过⾮匹配边,匹配边,⾮匹配边…形成的路径称为交替路,交替路不会形成环。

增⼴路——起点和终点都是未匹配点的交替路。

因为交替路是⾮匹配边、匹配边交替出现的,⽽增⼴路两端节点都是⾮匹配点,所以增⼴路⼀定有奇数条边。

⽽且增⼴路中的节点(除去两端节点)都是匹配点,所属的匹配边都在增⼴路径上,没有其他相连的匹配边,因此如果把增⼴路径中的匹配边和⾮匹配边的“⾝份”交换,就可以获得⼀个更⼤的匹配(该过程称为改进匹配)。

⽰例图Fig1_09_09.JPG注释:Fig3是⼀个⼆分图G=(V,E),V={1,2,3,4,5,6,7,8},E={(1,7),(1,5),(2,6),(3,5),(3,8),(4,5),(4,6)},该图可以重绘成Fig4,V可分成两个⼦集V={V1,V2},V1={1,2,3,4},V2={5,6,7,8}。

Fig4中的红⾊边集合就是⼀个匹配{(1,5),(4,6),(3,8)}Fig2中是最⼤匹配Fig1中红⾊边集合是完美匹配Fig1中交替路举例(4-6-2-7-1-5)Fig4中增⼴路(2-6-4-5-1-7)匈⽛利树匈⽛利树中从根节点到叶节点的路径均是交替路,且匈⽛利树的叶节点都是匹配点。

二分图的最大匹配—匈牙利算法

二分图的最大匹配—匈牙利算法

⼆分图的最⼤匹配—匈⽛利算法【基本概念】:⼆分图:⼆分图⼆分图⼜称作⼆部图,是图论中的⼀种特殊模型。

设G=(V,E)是⼀个⽆向图,如果顶点V可分割为两个互不相交的⼦集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为⼀个⼆分图。

⽆向图G为⼆分图的充分必要条件是,G⾄少有两个顶点,且其所有回路的长度均为偶数。

最⼤匹配最⼤匹配:给定⼀个⼆分图G,在G的⼀个⼦图M中,M的边集中的任意两条边都不依附于同⼀个顶点,则称M是⼀个匹配. 选择这样的边数最⼤的⼦集称为图的最⼤匹配问题,如果⼀个匹配中,图中的每个顶点都和图中某条边相关联,则称此匹配为完全匹配,也称作完备匹配.最⼩覆盖:最⼩覆盖要求⽤最少的点(X集合或Y集合的都⾏)让每条边都⾄少和其中⼀个点关联。

可以证明:最少的点(即覆盖数)=最⼤匹配数最⼩路径覆盖:⽤尽量少的不相交简单路径覆盖有向⽆环图G的所有结点。

解决此类问题可以建⽴⼀个⼆分图模型。

把所有顶点i拆成两个:X结点集中的i 和Y结点集中的i',如果有边i->j,则在⼆分图中引⼊边i->j',设⼆分图最⼤匹配为m,则结果就是n-m。

增⼴路(增⼴轨):(增⼴轨):增⼴路若P是图G中⼀条连通两个未匹配顶点的路径,并且属于M的边和不属于M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的⼀条增⼴路径(举例来说,有A、B集合,增⼴路由A中⼀个点通向B中⼀个点,再由B中这个点通向A中⼀个点……交替进⾏)。

增⼴路径的性质:1 有奇数条边。

2 起点在⼆分图的左半边,终点在右半边。

3 路径上的点⼀定是⼀个在左半边,⼀个在右半边,交替出现。

(其实⼆分图的性质就决定了这⼀点,因为⼆分图同⼀边的点之间没有边相连,不要忘记哦。

)4 整条路径上没有重复的点。

5 起点和终点都是⽬前还没有配对的点,⽽其它所有点都是已经配好对的。

算法学习:图论之二分图的最优匹配(KM算法)

算法学习:图论之二分图的最优匹配(KM算法)

二分图的最优匹配(KM算法)KM算法用来解决最大权匹配问题:在一个二分图内,左顶点为X,右顶点为Y,现对于每组左右连接XiYj有权wij,求一种匹配使得所有wij的和最大。

基本原理该算法是通过给每个顶点一个标号(叫做顶标)来把求最大权匹配的问题转化为求完备匹配的问题的。

设顶点Xi的顶标为A[ i ],顶点Yj的顶标为B[ j ],顶点Xi与Yj之间的边权为w[i,j]。

在算法执行过程中的任一时刻,对于任一条边(i,j),A[ i ]+B[j]>=w[i,j]始终成立。

KM算法的正确性基于以下定理:若由二分图中所有满足A[ i ]+B[j]=w[i,j]的边(i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配。

首先解释下什么是完备匹配,所谓的完备匹配就是在二部图中,X点集中的所有点都有对应的匹配或者是Y点集中所有的点都有对应的匹配,则称该匹配为完备匹配。

这个定理是显然的。

因为对于二分图的任意一个匹配,如果它包含于相等子图,那么它的边权和等于所有顶点的顶标和;如果它有的边不包含于相等子图,那么它的边权和小于所有顶点的顶标和。

所以相等子图的完备匹配一定是二分图的最大权匹配。

初始时为了使A[ i ]+B[j]>=w[i,j]恒成立,令A[ i ]为所有与顶点Xi关联的边的最大权,B[j]=0。

如果当前的相等子图没有完备匹配,就按下面的方法修改顶标以使扩大相等子图,直到相等子图具有完备匹配为止。

我们求当前相等子图的完备匹配失败了,是因为对于某个X顶点,我们找不到一条从它出发的交错路。

这时我们获得了一棵交错树,它的叶子结点全部是X顶点。

现在我们把交错树中X顶点的顶标全都减小某个值d,Y顶点的顶标全都增加同一个值d,那么我们会发现:1)两端都在交错树中的边(i,j),A[ i ]+B[j]的值没有变化。

也就是说,它原来属于相等子图,现在仍属于相等子图。

2)两端都不在交错树中的边(i,j),A[ i ]和B[j]都没有变化。

最大二分图匹配(匈牙利算法)

最大二分图匹配(匈牙利算法)

最大二分图匹配(匈牙利算法)二分图指的是这样一种图:其所有的顶点分成两个集合M和N,其中M或N中任意两个在同一集合中的点都不相连。

二分图匹配是指求出一组边,其中的顶点分别在两个集合中,并且任意两条边都没有相同的顶点,这组边叫做二分图的匹配,而所能得到的最大的边的个数,叫做最大匹配。

计算二分图的算法有网络流算法和匈牙利算法(目前就知道这两种),其中匈牙利算法是比较巧妙的,具体过程如下(转自组合数学):令g=(x,*,y)是一个二分图,其中x={x1,x2...},y={y1,y2,....}.令m为g中的任意匹配。

1。

将x的所有不与m的边关联的顶点表上¥,并称所有的顶点为未扫描的。

转到2。

2。

如果在上一步没有新的标记加到x的顶点上,则停,否则,转33。

当存在x被标记但未被扫描的顶点时,选择一个被标记但未被扫描的x的顶点,比如xi,用(xi)标记y 的所有顶点,这些顶点被不属于m且尚未标记的边连到xi。

现在顶点xi 是被扫描的。

如果不存在被标记但未被扫描的顶点,转4。

4。

如果在步骤3没有新的标记被标记到y的顶点上,则停,否则转5。

5。

当存在y被标记但未被扫描的顶点时。

选择y的一个被标记但未被扫描的顶点,比如yj,用(yj)标记x的顶点,这些顶点被属于m且尚未标记的边连到yj。

现在,顶点yj是被扫描的。

如果不存在被标记但未被扫描的顶点则转道2。

由于每一个顶点最多被标记一次且由于每一个顶点最多被扫描一次,本匹配算法在有限步内终止。

代码实现:bfs过程:#include<stdio.h>#include<string.h>main(){bool map[100][300];inti,i1,i2,num,num1,que[300],cou,stu,match1[100],match2[300],pqu e,p1,now,prev[300],n;scanf("%d",&n);for(i=0;i<n;i++){scanf("%d%d",&cou,&stu);memset(map,0,sizeof(map));for(i1=0;i1<cou;i1++){scanf("%d",&num);for(i2=0;i2<num;i2++){scanf("%d",&num1);map[i1][num1-1]=true;}}num=0;memset(match1,int(-1),sizeof(match1)); memset(match2,int(-1),sizeof(match2)); for(i1=0;i1<cou;i1++){p1=0;pque=0;for(i2=0;i2<stu;i2++){if(map[i1][i2]){prev[i2]=-1;que[pque++]=i2;}elseprev[i2]=-2;}while(p1<pque){now=que[p1];if(match2[now]==-1)break;p1++;for(i2=0;i2<stu;i2++){if(prev[i2]==-2&&map[match2[now]][i2]){prev[i2]=now;que[pque++]=i2;}}}if(p1==pque)continue;while(prev[now]>=0){match1[match2[prev[now]]]=now; match2[now]=match2[prev[now]]; now=prev[now];}match2[now]=i1;match1[i1]=now;num++;}if(num==cou)printf("YES\n");elseprintf("NO\n");}}dfs实现过程:#include<stdio.h>#include<string.h>#define MAX 100bool map[MAX][MAX],searched[MAX]; int prev[MAX],m,n;bool dfs(int data){int i,temp;for(i=0;i<m;i++){if(map[data][i]&&!searched[i]){searched[i]=true;temp=prev[i];prev[i]=data;if(temp==-1||dfs(temp))return true;prev[i]=temp;}}return false;}main(){int num,i,k,temp1,temp2,job;while(scanf("%d",&n)!=EOF&&n!=0) {scanf("%d%d",&m,&k);memset(map,0,sizeof(map));memset(prev,int(-1),sizeof(prev)); memset(searched,0,sizeof(searched));for(i=0;i<k;i++){scanf("%d%d%d",&job,&temp1,&temp2); if(temp1!=0&&temp2!=0)map[temp1][temp2]=true;}num=0;for(i=0;i<n;i++){memset(searched,0,sizeof(searched)); dfs(i);}for(i=0;i<m;i++){if(prev[i]!=-1)num++;}printf("%d\n",num);}}。

二分图匹配(匈牙利算法)

二分图匹配(匈牙利算法)

KM算法
对于任意的G和M,可行顶标都是存在的: l(x) = maxw(x,y) l(y) = 0 欲求完全二分图的最佳匹配,只要用匈牙利算法求 其相等子图的完备匹配;问题是当标号之后的Gl无 完备匹配时怎么办?1957年(居然比匈牙利算法 早???),Kuhn和Munkras给出了一个解决该问 题的有效算法,用逐次修改可行顶标l(v)的办法使对 应的相等子图之最大匹配逐次增广,最后出现完备 匹配.
例题3 打猎 猎人要在n*n的格子里打鸟,他可以在某一行 中打一枪,这样此行中的所有鸟都被打掉, 也可以在某一列中打,这样此列中的所有鸟 都打掉.问至少打几枪,才能打光所有的鸟? 建图:二分图的X部为每一行,Y部为每一列, 如果(i,j)有一只鸟,那么连接X部的i与Y部的j. 该二分图的最大匹配数则是最少要打的枪数.
1 2 3 4 5
1 2 5 3 4
1
2
3
4
由于每条边表示一个空地,有冲 突的空地之间必有公共顶点,所 以问题转化为二部图的最大匹配 问题.
1 2Leabharlann 34例题1 Place the Robots(ZOJ) 小结
比较前面的两个模型:模型一过于简单,没有给问 题的求解带来任何便利;模型二则充分抓住了问题的内 在联系,巧妙地建立了二部图模型.为什么会产生这种 截然不同的结果呢?其一是由于对问题分析的角度不同: 模型一以空地为点,模型二以空地为边;其二是由于对 原型中要素的选取有差异:模型一对要素的选取不充分, 模型二则保留了原型中"棋盘"这个重要的性质.由此 可见,对要素的选取,是图论建模中至关重要的一步.
例题4 最小路径覆盖 一个不含圈的有向图G中,G的一个路径覆盖 是一个其结点不相交的路径集合P,图中的每 一个结点仅包含于P中的某一条路径.路径可 以从任意结点开始和结束,且长度也为任意 值,包括0.请你求任意一个不含圈的有向图 G的最小路径覆盖数. 理清一个关系:最小路径覆盖数=G的定点 数-最小路径覆盖中的边数

用匈牙利算法求二分图的最大匹配

用匈牙利算法求二分图的最大匹配

用匈牙利算法求二分图的最大匹配二分图的最大匹配有两种求法,第一种是最大流;第二种就是匈牙利算法。

这个算法说白了就是最大流的算法,但是它跟据二分图匹配这个问题的特点,把最大流算法做了简化,提高了效率。

最大流算法的核心问题就是找增广路径(augment path)。

匈牙利算法也不例外,它的基本模式就是:初始时最大匹配M为空while 找得到增广路径do 把增广路径加入到最大匹配中去可见和最大流算法是一样的。

但是这里的增广路径就有它一定的特殊性,下面我来分析一下。

(注:匈牙利算法虽然根本上是最大流算法,但是它不需要建网络模型,所以图中不再需要源点和汇点,仅仅是一个二分图。

每条边也不需要有方向。

)图1是我给出的二分图中的一个匹配:[1,5]和[2,6]。

图2就是在这个匹配的基础上找到的一条增广路径:3->6->2->5->1->4。

我们借由它来描述一下二分图中的增广路径的性质:(1)有奇数条边。

(2)起点在二分图的左半边,终点在右半边。

(3)路径上的点一定是一个在左半边,一个在右半边,交替出现。

(其实二分图的性质就决定了这一点,因为二分图同一边的点之间没有边相连。

)(4)整条路径上没有重复的点。

(5)起点和终点都是目前还没有配对的点,而其它所有点都是已经配好对的。

(如图1、图2所示,[1,5]和[2,6]在图1中是两对已经配好对的点;而起点3和终点4目前还没有与其它点配对。

)(6)路径上的所有第奇数条边都不在原匹配中,所有第偶数条边都出现在原匹配中。

(如图1、图2所示,原有的匹配是[1,5]和[2,6],这两条配匹的边在图2给出的增广路径中分边是第2和第4条边。

而增广路径的第1、3、5条边都没有出现在图1给出的匹配中。

)(7)最重要的一条,把增广路径上的所有第奇数条边加入到原匹配中去,并把增广路径中的所有第偶数条边从原匹配中删除(这个操作称为增广路径的取反),则新的匹配数就比原匹配数增加了1个。

二分图(匈牙利,KM算法详解)

二分图(匈牙利,KM算法详解)
3,假如我们在1,2步过程中找到一条增广路, 那么修改各自 对应的匹配点,转步骤4,若无增广路, 则退出.
4,匹配数+1;
最小点覆盖
最小覆盖: 最小覆盖要求用最少的点(X集 合或Y集合的都行)让每条边都至少和其中一 个点关联。可以证明:最少的点(即覆盖数) =最大匹配数 M
简单的证明如下:
1
4
1
4
2
5 把图中红色线去掉
2
5
蓝色线加上
3
6
3
6
1
4
更改各自的匹配点
找到一个更好的匹配 2
5
3
6
总结
所以流程就是:
1,对于一个未匹配的节点u,寻找它的每条边,如果它的边上 的另一个节点v还没匹配则表明找到了一个匹配,直接转步 骤4;
2,假如节点u它边上的另一个节点v已经匹配,那么就转向跟 v匹配的节点,假设是w,然后再对w重复1,2的步骤,即寻找增 广路.
现在我们假设要求的是最大距离.那么就是求最大权 匹配. 下面我们先介绍一下KM算法
KM算法
基本概念:可行顶标和相等子图
可行顶标:L是一个关于结点的函数,L(x)是顶点x对应 的顶标值。可行顶标对于图中的每条边(x,y)都有 L(x)+L(y)>=w(x,y)
相等子图:只包含L(x)+L(y)=w(x,y)的边的子图
KM算法
定理:如果一个相等子图中包含完备匹配,那 么这个匹配就是最优匹配
证明:由于在算法过程一直保持顶标的可行性, 所以任意一个匹配的权值和肯定小于等于所有 结点的顶标之和,则相等子图中的完备匹配肯 定是最优匹配
KM算法
算法流程 设顶点Xi的顶标为a[i],顶点Yi的顶标为b[i] ⅰ.初始时,a[i]为与Xi相关联的边的最大权值,

二分图匹配之匈牙利算法

二分图匹配之匈牙利算法

⼆分图匹配之匈⽛利算法⼆分图的基本概念:⼆分图⼜称作⼆部图,是图论中的⼀种。

设G=(V,E)是⼀个⽆向图,如果顶点V可分割为两个互不相交的⼦集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为⼀个⼆分图。

⽐如:我们⼀般把图的两部分称为X部和Y部,⼀个图的所有点如果能被两个独⽴的点集,那就认为这个图是⼆分图⼆分图的判断⼀般通过染⾊的⽅法来进⾏判段,可以写成dfs或则bfs的写法bool dfs(int v,int c){color[v]=c;for(int i=0;i<n;i++){if(edge[v][i]==1){if(color[i]==c) return false;if(color[i]==0&&!dfs(i,-c)) return false;}}return true;}void solve(){int flag=0;for(int i=0;i<n;i++){if(color[i]==0){if(!dfs(i,1)){cout<<"no"<<endl;flag=1;break;}}}if(!flag) cout<<"yes"<<endl;}bfs写法bool bfs(int s){color[s] = 1;queue<int> que;que.push(s);while(!que.empty()){int from = que.front();que.pop();for(int i = 1; i <= V; i++){// 如果相邻的点没有上⾊就给这个点上⾊if(G[from][i] && color[i] == 0){que.push(i);color[i] = -color[from];}// 如果相邻的颜⾊相同则返回falseif(G[from][i] && color[i] == color[from])return false;}}// 如果所有的点都被染过⾊,且相邻的点颜⾊都不⼀样,返回truereturn true;}接下来就是介绍匹配:⼀个匹配是⼀个边的集合,任何匹配的边之间没有公共顶点。

用匈牙利算法求二分图的最大匹配.

用匈牙利算法求二分图的最大匹配.

一、用匈牙利算法求二分图(二部图,偶图的最大匹配算法中的几个术语说明:1。

二部图:如果图G=(V,E的顶点集何V可分为两个集合X,Y,且满足X∪Y =V, X∩Y=Φ,则G称为二部图;图G的边集用E(G表示,点集用V(G表示。

2。

匹配:设M是E(G的一个子集,如果M中任意两条边在G中均不邻接,则称M是G的一个匹配。

M中的—条边的两个端点叫做在M是配对的。

3。

饱和与非饱和:若匹配M的某条边与顶点v关联,则称M饱和顶点v,并且称v是M-饱和的,否则称v是M-不饱和的。

4。

交互道:若M是二分图G=(V,E的一个匹配。

设从图G中的一个顶点到另一个顶点存在一条道路,这条道路是由属于M的边和不属于M的边交替出现组成的,则称这条道路为交互道。

5。

可增广路:若一交互道的两端点为关于M非饱和顶点时,则称这条交互路是可增广路。

显然,一条边的两端点非饱和,则这条边也是可增广路。

6。

最大匹配:如果M是一匹配,而不存在其它匹配M',使得|M'|>|M|,则称M是最大匹配。

其中|M|表示匹配M的边数。

7。

对称差:A,B是两个集合,定义A⊕B = (A∪B\(A∩B ,则称A⊕B为A和B的对称差。

定理:M为G的最大匹配的充要条件是G中不存在可增广道路。

Hall定理:对于二部图G,存在一个匹配M,使得X的所有顶点关于M饱和的充要条件是:对于X的任意一个子集A,和A邻接的点集为T(A,恒有:|T(A| >= |A|。

匈牙利算法是基于Hall定理中充分性证明的思想,其基本步骤为:1。

任给初始匹配M;2。

若X已饱和则结束,否则进行第3步;3。

在X中找到一个非饱和顶点x0,作V1 ←{x0},V2 ←Φ ;4。

若T(V1 = V2则因为无法匹配而停止,否则任选一点y∈T(V1\V2;5。

若y已饱和则转6,否则做一条从x0→y的可增广路P,M←M⊕E(P,转2;6。

由于y已饱和,所以M中有一条边(y,z,作V1 ←V1∪{z}, V2←V2∪{y},转4;二、用匈牙利算法求二分图的最大匹配(伪代码给定一个二分图(x,y,x中的点和y中点的连接情况,一个匹配是二分图的子图,其中任一个x中的点只能和最多一个y中的点连接,一个y中的点也只能和一个x中的点连接,最大匹配就是使子图中的边数(我们称为匹配边最多的匹配。

二分图匹配算法(最大流匈牙利)

二分图匹配算法(最大流匈牙利)

⼆分图匹配算法(最⼤流匈⽛利)⼆分图匹配相关概念⽆向⼆分图G(U⋃V,E):U是⼀个顶点集合,V是另⼀个顶点集合,对于⼀个集合内的点⽆边直接相连,⽽对于不同集合的点可以连边,即(u,v)∈E。

匹配:两两不含公共端点的边的集合M称为匹配(就是两个集合之间连的边,只不过不同边的端点不能重合)最⼤匹配:元素最多的M,即⽽G中两两不含公共端点的边的集合M⊆E的基数|M|的最⼤值就是最⼤匹配。

完美匹配:当最⼤匹配的匹配数满⾜2∗|M|=V时,称为完美匹配。

形象的解释就是⼀各集合的所有点到另⼀个集合都有互不相同且唯⼀对应的点。

(类似于函数的双射),想象⼀下图增⼴路:设M为⼆分图G已匹配边的集合,若P是图G中⼀条连通两个未匹配顶点的路径(P的起点在X部,终点在Y部,反之亦可),并且属M的边和不属M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的⼀条增⼴路径。

(就是连了两个还没有配对的顶点的路径,路径有⼀个配对边,⼀个⾮配对边交替组成)更多详细概念解释见匈⽛利部分的参考⽂章最⼤流的⽅法⼆分图的匹配可以看成是⼀种最⼤流问题(这谁想得出来啊)。

具体过程如下:现在有两个点集U和V,之间已经有了写连边,我们需要引⼊⼀个源,⼀个汇,把源跟集合U的所有点有向地连起来,把V的所有点和汇有向地连起来,就构成了⼀个流⽹络。

现在由于⼆分图匹配的限制,⼀个点不能连⾃⼰内部的点(在原来的⼆分图中这关系已经成⽴),不能连两个或多个边。

那么就把每个边的权值赋为⼀。

这样边的流量只有零壹两种,要么有边,要么不连边。

在上⾯跑最⼤流算法即可(具体讲解参见上篇博客)下⾯是代码:代码:#include <iostream>#include <memory.h>#include <vector>#include <queue>#define max_n 10005#define INF 0x3f3f3f3fusing namespace std;//邻接表struct edge{int v;//到达顶点int cap;//最⼤流量int rev;//对应反向边的编号};vector<edge> G[max_n];int level[max_n];//Dinic算法⽤到的层次图int iter[max_n];//当前弧优化void add_edge(int u,int v,int cap){G[u].push_back((edge){v,cap,G[v].size()});//最后⼀个表⽰uv这条边的反向边在G[v]⾥的标号G[v].push_back((edge){u,0,G[u].size()-1});}void bfs(int s)//处理层次图{memset(level,-1,sizeof(level));queue<int> que;level[s] = 0;que.push(s);while(!que.empty()){int v = que.front();que.pop();for(int i = 0;i<G[v].size();i++){edge& e = G[v][i];if(e.cap>0&&level[e.v]<0){level[e.v] = level[v]+1;que.push(v);}}}}int dfs(int v,int t,int f)//Dinic的dfs{if(v==t) return f;for(int& i = iter[v];i<G[v].size();i++){edge& e = G[v][i];if(e.cap>0&&level[e.v]==level[v]+1){int d = dfs(v,t,min(f,e.cap));if(d>0){G[e.v][e.rev].cap+=d;return d;}}}return 0;}int max_flow(int s,int t)//Dinic算法{int flow = 0;for(;;){bfs(s);if(level[t]<0){return flow;}memset(iter,0,sizeof(iter));int f;while(f=dfs(s,t,INF)>0){flow += f;}}}int N,K;//N,K为两个集合的点数bool can[max_n][max_n];//⼆分图中原有的边void solve(){//0~N-1是U中的点//N~N+K-1是V中的点int s = N+K;int t = s+1;for(int i = 0;i<N;i++)//s与U连边{add_edge(s,i,1);}for(int i = 0;i<K;i++)//t与V连边{add_edge(i+N,t,1);}for(int i = 0;i<N;i++){for(int j = 0;j<K;j++){if(can[i][j]){add_edge(i,j,1);//⼆分图原有边的链接}}}cout << max_flow(s,t) << endl;//求最⼤流即得最⼤匹配}int main(){cin >> N >> K;solve();return 0;}匈⽛利算法这个算法是专门处理⼆分图的最⼤匹配问题的,有很好的博客讲解,下⾯是推荐阅读⽅式:我上⾯的概念可能不太全,那就先来看看⼆分图的相关概念:可能还对增⼴路径不是很理解,什么是增⼴路,⾮配对配对交替什么的很混乱,那不妨先看看这个:现在到了算法流程了,在正式介绍前,先有个有趣⽽深刻的认识,下⾯是⼗分清晰的讲解:好了,该正式⼀点了,相信你也有了⼀定的了解:上⾯的代码其实已经够清晰了,如果还想看⼀下,就这篇吧:代码:#include <iostream>#include <memory.h>#define max_n 200005using namespace std;int n,m;int con_y[max_n];int visit[max_n];int head[max_n];struct edge{int v;int nxt;}e[max_n<<1];int cnt = 0;void add(int u,int v){++cnt;e[cnt].v = v;e[cnt].nxt = head[u];head[u] = cnt;}int dfs(int u){for(int i = head[u];i;i=e[i].nxt){int v = e[i].v;if(visit[v]==0){if(con_y[v]==-1||dfs(v))//结合上⾯算法流程部分的有趣博客再理解了⼀下这⾥的递归,好奇妙 {con_x[u] = v;con_y[v] = u;return 1;}}}return 0;}int Hungary(){memset(con_x,-1,sizeof(con_x));memset(con_y,-1,sizeof(con_y));int ans = 0;for(int i = 1;i<=n;i++){memset(visit,0,sizeof(visit));ans += dfs(i);}return ans;}int main(){cin >> n >> m;for(int i = 1;i<=m;i++){int a,b,c;cin >> a >> b;add(a,b);}cout << Hungary() << endl;return 0;}参考⽂章以上Processing math: 100%。

km(kuhn-munkres)算法的具体表达

km(kuhn-munkres)算法的具体表达

km(kuhn-munkres)算法的具体表达KM算法,即Kuhn-Munkres算法(又称为匈牙利算法),是一种用于求解二分图最大权匹配问题的经典算法。

它由Eugene L. Lawler于1960年首次提出,后来由James Munkres在1957年独立发表,因此常称为Kuhn-Munkres算法。

二分图最大权匹配问题是指给定一个带权二分图,要求在图中选取权重之和最大的边集合,使得任意两条边不属于同一个顶点。

其中,带权二分图是指图的每条边都带有一个非负权重。

KM算法使用了两个关键的概念:交错树和相等子图。

交错树是指一个T=(S,P)的有向树,其中S是原二分图的所有顶点的集合,P是树中的边集合。

相等子图是指原二分图的一个子图,其中T中所有入树边的权重之和等于所有出树边的权重之和。

KM算法具体的步骤如下:1.初始化:将图中所有边的权重初始化为0,构建了一个初始的交错树T=(X,Y)(其中X,Y分别表示两个顶点集合)。

2.判断相等子图:对于T中的每个顶点x,如果存在一条满足lx+ly=W(其中lx是x在T中所有入树边的权重之和,ly是x在T中所有出树边的权重之和,W是x在原图G中的权重),则把x加入到相等子图中。

3.寻找未匹配节点:在相等子图中寻找未匹配节点,并标记为未父节点。

4.寻找增广路:如果存在未匹配节点,则从中选择一个起始节点,寻找一条与之交替出现的边构成的路径,使路径的最后一个边是一条与未匹配节点关联的边。

这样的路径称为增广路。

5.修改标号:对于相等子图中的每个顶点x,从已匹配节点中选择一个与之关联的顶点y,并计算d=min(lx+ly-w)(其中w是x和y之间的边的权重),为了使增广路更优,将T中所有x节点的入树边的权重减去d,将T中所有y节点的出树边的权重加上d。

6.更新交错树:将增广路中的所有边和T中的所有非树边进行调整,得到新的交错树。

7.重复步骤3-6,直到没有未匹配节点为止。

最大权匹配KM算法

最大权匹配KM算法

最大权匹配KM算法KM 算法(Kuhn–Munkres 算法)又被称为匈牙利算法或者二分图最佳匹配算法,是用来解决二分图最大权匹配问题的一种有效算法。

它的时间复杂度为 O(n^3),其中 n 是二分图中的顶点数。

在二分图最大权匹配问题中,给定一个二分图,找到一种边的匹配方式,使得所有边的权重之和达到最大。

其中,二分图是一种图,其顶点可以分为两个不相交的集合U和V,并且图中的每条边都连接U和V中的两个顶点之一KM 算法的主要思想是通过设置两个顶标数组 lx 和 ly 来记录每个顶点的可行匹配权值。

算法的基本步骤如下:1. 初始化顶标数组 lx 和 ly 为 0。

2. 对于每个顶点 u 属于集合 U,找出 u 在集合 V 中的邻接顶点 v,计算顶点 u 和 v 之间的权值与当前顶标数组的差值 delta,如果 delta 大于顶点 v 的顶标,则更新顶标 lx[u] 为 delta。

3.从集合U中选择一个顶点u,对于该顶点u在集合V中的每个邻接顶点v,如果u和v当前没有匹配,则尝试将u和v进行匹配,并将顶点u标记为已匹配。

4. 如果存在未匹配的顶点,则尝试改变标号以寻找更大的匹配权重。

具体操作是分别减小顶标 lx[u] 和增加顶标 ly[v],保持所有匹配的边不变。

5.重复步骤2-4,直到无法找到更大权重的匹配。

KM 算法的关键在于通过改变顶标数组 lx 和 ly,以及不断改变已匹配的顶点来寻找更大的权重匹配。

当顶标数组不再改变时,即找到了最大权匹配。

需要注意的是,KM算法只能解决二分图的最大权匹配,如果给定的图不是二分图,需要先进行二分图的判定。

此外,KM算法也可以用来解决最小权匹配问题,只需要将边的权重取相反数即可。

总结来说,KM算法通过设置顶标数组和不断改变匹配的顶点来寻找二分图的最大权匹配。

它是一种时间复杂度较低、效率较高的算法,有着广泛的应用场景,例如任务分配、资源分配等问题。

二分图匹配之最佳匹配——KM算法

二分图匹配之最佳匹配——KM算法

⼆分图匹配之最佳匹配——KM算法今天也⼤致学了下KM算法,⽤于求⼆分图匹配的最佳匹配。

何为最佳?我们能⽤匈⽛利算法对⼆分图进⾏最⼤匹配,但匹配的⽅式不唯⼀,如果我们假设每条边有权值,那么⼀定会存在⼀个最⼤权值的匹配情况,但对于KM算法的话这个情况有点特殊,这个匹配情况是要在完全匹配(就是各个点都能⼀⼀对应另⼀个点)情况下的前提。

⾃然,KM算法跟匈⽛利算法有相似之处。

其算法步骤如下:1.⽤邻接矩阵(或其他⽅法也⾏啦)来储存图,注意:如果只是想求最⼤权值匹配⽽不要求是完全匹配的话,请把各个不相连的边的权值设置为0。

2.运⽤贪⼼算法初始化标杆。

3.运⽤匈⽛利算法找到完备匹配。

4.如果找不到,则通过修改标杆,增加⼀些边。

5.重复3,4的步骤,直到完全匹配时可结束。

⼀⾔不合地冒出了个标杆??标杆是什么???在解释这个问题之前,我们先来假设⼀个很简单的情况,⽤我们⼈类伟⼤的智能思维去思考思考。

如上的⼀个⼆分图,我们要求它的最⼤权值匹配(最佳匹配)我们可以思索思索⼆分图最佳匹配还是⼆分图匹配,所以跟和匈⽛利算法思路差不多⼆分图是特殊的⽹络流,最佳匹配相当于求最⼤(⼩)费⽤最⼤流,所以FF⽅法也能实现所以我们可以把这匈⽛利算法和FF⽅法结合起来FF⽅法⾥⾯,我们每次是找最长(短)路进⾏通流所以⼆分图匹配⾥⾯我们也找最⼤边进⾏连边!但是遇到某个点被匹配了两次怎么办?那就⽤匈⽛利算法进⾏更改匹配!这就是KM算法的思路了:尽量找最⼤的边进⾏连边,如果不能则换⼀条较⼤的。

所以,根据KM算法的思路,我们⼀开始要对边权值最⼤的进⾏连线,那问题就来了,我们如何让计算机知道该点对应的权值最⼤的边是哪⼀条?或许我们可以通过某种⽅式记录边的另⼀端点,但是呢,后⾯还要涉及改边,⼜要记录边权值总和,⽽这个记录端点⽅法似乎有点⿇烦,于是KM采⽤了⼀种⼗分巧妙的办法(也是KM算法思想的精髓):添加标杆(顶标)是怎样⼦呢?我们对左边每个点Xi和右边每个点Yi添加标杆Cx和Cy。

图论中的二分图匹配问题及其算法设计思路

图论中的二分图匹配问题及其算法设计思路

图论中的二分图匹配问题及其算法设计思路图论是数学中一个重要的分支,研究图的性质和结构,以及解决与图相关的问题。

其中,二分图匹配问题是图论中的经典问题之一。

本文将介绍二分图匹配问题的定义、特性,并讨论相关的算法设计思路。

一、二分图匹配问题的定义二分图是一种特殊的图结构,其中的顶点可以分为两个互不相交的集合,且每条边都只连接两个集合之间的顶点。

对于一个二分图,如果存在一种边的划分方式,使得每个顶点都与边集中的一条边相连,那么我们称这个边集为二分图的一个匹配。

二分图匹配问题的目标是寻找出一个匹配,使得匹配的边数最大。

这个问题在实际应用中有许多场景,比如婚姻匹配、求职配对等。

为了解决这个问题,人们提出了多种算法,下面将介绍其中两个常用的算法。

二、匈牙利算法匈牙利算法是用于求解二分图最大匹配的一种经典算法,它基于深度优先搜索的思想。

算法的基本思路是从一个没有匹配边的顶点开始,逐个尝试与其相连的顶点进行匹配,如果能成功匹配则将边加入匹配集合中,如果不能成功匹配则继续尝试下一个顶点。

当所有的顶点都尝试过后,即得到一个最大匹配。

以下是匈牙利算法的伪代码:1. 初始化匹配集合为空2. 从一个未匹配的顶点开始,对其进行深度优先搜索3. 如果找到了增广路径,则更新匹配集合4. 重复步骤2和3,直到无法找到增广路径5. 返回最大匹配匈牙利算法的时间复杂度为O(V*E),其中V表示顶点数,E表示边数。

虽然算法的时间复杂度较高,但它在实际应用中仍然具有一定的效率和适用性。

三、Hopcroft-Karp算法Hopcroft-Karp算法是用于求解二分图最大匹配的另一种算法,它是对匈牙利算法的改进和优化。

Hopcroft-Karp算法的核心思想是通过多次的广度优先搜索来寻找增广路径,从而提高算法的效率。

以下是Hopcroft-Karp算法的伪代码:1. 初始化匹配集合为空2. 初始化标记集合为空3. 利用广度优先搜索寻找增广路径4. 如果找到增广路径,则更新匹配集合5. 重复步骤3和4,直到无法找到增广路径6. 返回最大匹配Hopcroft-Karp算法的时间复杂度为O(E*sqrt(V)),相比于匈牙利算法有较大的优势。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

前言:
高中时候老师讲这个就听得迷迷糊糊,有一晚花了通宵看KM的Pascal代码,大概知道过程了,后来老师说不是重点,所以忘的差不多了。

都知道二分图匹配是个难点,我这周花了些时间研究了一下这两个算法,总结一下
1.基本概念
M代表匹配集合
未盖点:不与任何一条属于M的边相连的点
交错轨:属于M的边与不属于M的边交替出现的轨(链)
可增广轨:两端点是未盖点的交错轨
判断M是最大匹配的标准:M中不存在可增广轨
2.最大匹配,匈牙利算法
时间复杂度:O(|V||E|)
原理:
寻找M的可增广轨P,P包含2k+1条边,其中k条属于M,k+1条不属于M。

修改M 为M&P。

即这条轨进行与M进行对称差分运算。

所谓对称差分运算,就是比如X和Y都是集合,X&Y=(X并Y)-(x交Y)
有一个定理是:M&P的边数是|M|+1,因此对称差分运算扩大了M
实现:
关于这个实现,有DFS和BFS两种方法。

先列出DFS的代码,带注释。

这段代码来自中山大学的教材
核心部分在dfs(x),来寻找可增广轨。

如果找到的话,在Hungarian()中,最大匹配数加一。

这是用了刚才提到的定理。

大家可以想想初始状态是什么,又是如何变化的
view plaincopy to clipboardprint?
第二种方法BFS,来自我的学长cnhawk
核心步骤还是寻找可增广链,过程是:
1.从左的一个未匹配点开始,把所有她相连的点加入队列
2.如果在右边找到一个未匹配点,则找到可增广链
3.如果在右边找到的是一个匹配的点,则看它是从左边哪个点匹配而来的,将那个点出发的所有右边点加入队列
这么说还是不容易明白,看代码吧
view plaincopy to clipboardprint?
3.最佳匹配
加权图中,权值最大的最大匹配
KM算法:
概念:
f(v)是每个点的一个值,使得对任意u,v C V,f(u)+f(v)>=w[e u,v]集合H:一个边集,使得H中所有u,v满足f(u)+f(v)=w[e u,v]
等价子图:G f(V,H),标有f函数的G图
理论:
对于f和G f,如果有一个理想匹配集合M p,则M p最优。

对于任意匹配集合M,weight(M)<weight(M p)
KM算法的实质是扩展G f,直到找到理想的匹配集合
伪代码
view plaincopy to clipboardprint?
最后给一个代码,跟伪代码的思路不是很一样。

从网上找的
view plaincopy to clipboardprint?。

相关文档
最新文档