`
evasiu
  • 浏览: 169462 次
  • 性别: Icon_minigender_2
  • 来自: 广州
博客专栏
Fa47b089-e026-399c-b770-017349f619d5
TCP/IP详解卷一>阅读...
浏览量:12549
社区版块
存档分类
最新评论

我对KM算法的理解

 
阅读更多
一般对KM算法的描述,基本上可以概括成以下几个步骤:
(1) 初始化可行标杆
(2) 用匈牙利算法寻找完备匹配
(3) 若未找到完备匹配则修改可行标杆
(4) 重复(2)(3)直到找到相等子图的完备匹配

关于该算法的流程及实施,网上有很多介绍,基本上都是围绕可行标杆如何修改而进行的讨论,至于原理并没有给出深入的探讨。

KM算法是用于寻找带权二分图最佳匹配的算法。

二分图是这样一种图:所有顶点可以分成两个集:X和Y,其中X和Y中的任意两个在同一个集中的点都不相连,而来自X集的顶点与来自Y集的顶点有连线。当这些连线被赋于一定的权重时,这样的二分图便是带权二分图。

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

我们也可以换个角度看二分图的最大匹配,即二分图的每条边的默认权重为1,我们求到的二分图的最大匹配的权重最大。对于带权二分图,其边有大于0的权重,找到一组匹配,使其权重最大,即为带权二分图的最佳匹配。

匈牙利算法一般用于寻找二分图的最大匹配。算法根据一定的规则选择二分图的边加入匹配子图中,其基本模式为:

初始化匹配子图为空
While 找得到增广路径
Do 把增广路径添加到匹配子图中

增广路径有如下特性:
1. 有奇数条边
2. 起点在二分图的X边,终点在二分图的Y边
3. 路径上的点一定是一个在X边,一个在Y边,交错出现。
4. 整条路径上没有重复的点
5. 起点和终点都是目前还没有配对的点,其他的点都已经出现在匹配子图中
6. 路径上的所有第奇数条边都是目前还没有进入目前的匹配子图的边,而所有第偶数条边都已经进入目前的匹配子图。奇数边比偶数边多一条边
7. 于是当我们把所有第奇数条边都加到匹配子图并把条偶数条边都删除,匹配数增加了1.

例如下图,蓝色的是当前的匹配子图,目前只有边x0y0,然后通过x1找到了增广路径:x1y0->y0x0->x0y2



其中第奇数第边x1y0和x0y2不在当前的匹配子图中,而第偶数条边x0y0在匹配子图中,通过添加x1y0和x0y2到匹配子图并删除x0y0,使得匹配数由1增加到了2。每找到一条增广路径,通过添加删除边,我们总是能使匹配数加1.

增广路径有两种寻径方法,一个是深搜,一个是宽搜。例如从x2出发寻找增广路径,如果是深搜,x2找到y0匹配,但发现y0已经被x1匹配了,于是就深入到x1,去为x1找新的匹配节点,结果发现x1没有其他的匹配节点,于是匹配失败,x2接着找y1,发现y1可以匹配,于是就找到了新的增广路径。如果是宽搜,x1找到y0节点的时候,由于不能马上得到一个合法的匹配,于是将它做为候选项放入队列中,并接着找y1,由于y1已经匹配,于是匹配成功返回了。相对来说,深搜要容易理解些,其栈可以由递归过程来维护,而宽搜则需要自己维护一个队列,并对一路过来的路线自己做标记,实现起来比较麻烦。

对于带权重的二分图来说,我们可以把它看成一个所有X集合的顶点到所有Y集合的顶点均有边的二分图(把原来没有的边添加入二分图,权重为0即可),也就是说它必定存在完备匹配(即其匹配数为min(|X|,|Y|))。为了使权重达到最大,我们实际上是通过贪心算法来选边,形成一个新的二分图(我们下面叫它二分子图好了),并在该二分图的基础上寻找最大匹配,当该最大匹配为完备匹配时,我们可以确定该匹配为最佳匹配。(在这里我们如此定义最大匹配:匹配边数最多的匹配和最佳匹配:匹配边的权重和最大的匹配。)

贪心算法总是将最优的边优先加入二分子图,该最优的边将对当前的匹配子图带来最大的贡献,贡献的衡量是通过标杆来实现的。下面我们将通过一个实例来解释这个过程。

有带权二分图:


算法把权重转换成标杆,X集跟Y集的每个顶点各有一个标杆值,初始情况下权重全部放在X集上。由于每个顶点都将至少会有一个匹配点,贪心算法必然优先选择该顶点上权重最大的边(最理想的情况下,这些边正好没有交点,于是我们自然得到了最佳匹配)。最初的二分子图为:(可以看到初始化时X标杆为该顶点上的最大权重,而Y标杆为0)


从X0找增广路径,找到X0Y4;从X1找不到增广路径,也就是说,必须往二分子图里边添加新的边,使得X1能找到它的匹配,同时使权重总和添加最大。由于X1通往Y4而Y4已经被X0匹配,所以有两种可能,一个是为X0找一个新的匹配点并把Y4让给X1,或者是为X1找一个新的匹配点,现在我们将要看到标杆的作用了。根据传统的算法描述,能够进入二分子图的边的条件为L(x)+L(y)>=weight(xy)。当找不到增广路径时,对于搜索过的路径上的XY点,设该路径上的X顶点集为S,Y顶点集为T,对所有在S中的点xi及不在T中的点yj,计算d=min{(L(xi)+L(yj)-weight(xiyj))},从S集中的X标杆中减去d,并将其加入到T集中的Y的标杆中,由于S集中的X标杆减少了,而不在T中的Y标杆不变,相当于这两个集合中的L(x)+L(y)变小了,也就是,有新的边可以加入二分子图了。从贪心选边的角度看,我们可以为X0选择新的边而抛弃原先的二分子图中的匹配边,也可以为X1选择新的边而抛弃原先的二分子图中的匹配边,因为我们不能同时选择X0Y4和X1Y4,因为这是一个不合法匹配,这个时候,d=min{(L(xi)+L(yj)-weight(xiyj))}的意义就在于,我们选择一条新的边,这条边将被加入匹配子图中使得匹配合法,选择这条边形成的匹配子图,将比原先的匹配子图加上这条非法边组成的非法匹配子图的权重和(如果它是合法的,它将是最大的)小最少,即权重最大了。好绕口的。用数学的方式表达,设原先的不合法匹配(它的权重最大,因为我们总是从权重最大的边找起的)的权重为W,新的合法匹配为W’,d为min{W-W’i}。在这个例子中,S={X0, X1},Y={Y4},求出最小值d=L(X1)+L(Y0)-weight(X1Y0)=2,得到新的二分子图:


重新为X1寻找增广路径,找到X1Y0,可以看到新的匹配子图的权重为9+6=15,比原先的不合法的匹配的权重9+8=17正好少d=2。
接下来从X2出发找不到增广路径,其走过的路径如蓝色的路线所示。形成的非法匹配子图:X0Y4,X1Y0及X2Y0的权重和为22。在这条路径上,只要为S={X0,X1,X2}中的任意一个顶点找到新的匹配,就可以解决这个问题,于是又开始求d。
d=L(X0)+L(Y2)-weight(X0Y2)=L(X2)+L(Y1)-weight(X2Y1)=1.
新的二分子图为:



重新为X2寻找增广路径,如果我们使用的是深搜,会得到路径:X2Y0->Y0X1->X1Y4->Y4X0->X0Y2,即奇数条边而删除偶数条边,新的匹配子图中由这几个顶点得到的新的权重为21;如果使用的是宽搜,会得到路径X2Y1,另上原先的两条匹配边,权重为21。假设我们使用的是宽搜,得到的新的匹配子图为:


接下来依次类推,直到为X4找到一个匹配点。

KM算法的最大特点在于利用标杆和权重来生成一个二分子图,在该二分子图上面找最大匹配,而且,当些仅当找到完备匹配,才能得到最佳匹配。标杆和权重的作用在于限制新边的加入,使得加入的新边总是能为子图添加匹配数,同时又令权重和得到最大的提高。

下面是匈牙利算法的dfs和bfs实现,是用c++实现的:
//---------------------DFS---------------------------------
#include<iostream>
#include<memory.h>
using namespace std;

#define MAXN 10
int graph[MAXN][MAXN];
int match[MAXN];
int visitX[MAXN], visitY[MAXN];
int nx, ny;

bool findPath( int u )
{
	visitX[u] = 1;
	for( int v=0; v<ny; v++ )
	{
		if( !visitY[v] && graph[u][v] )
		{
			visitY[v] = 1;
			if( match[v] == -1 || findPath(match[v]) )
			{
				match[v] = u;
				return true;
			}
		}
	}
	return false;
}

int dfsHungarian()
{
	int res = 0;
	memset( match, -1, sizeof(match) );
	for( int i=0; i<nx; i++ )
	{
		memset( visitX, 0, sizeof(visitX) );
		memset( visitY, 0, sizeof(visitY) );
		if( findPath(i) )
			res++;
	}
	return res;
}

//-----------------------------BFS-------------------------------
#include<iostream>
#include<memory.h>
using namespace std;

#define MAXN 10
int graph[MAXN][MAXN];
//在bfs中,增广路径的搜索是一层一层展开的,所以必须通过prevX来记录上一层的顶点
//chkY用于标记某个Y顶点是否被目前的X顶点访问尝试过。
int matchX[MAXN], matchY[MAXN], prevX[MAXN], chkY[MAXN];
int queue[MAXN];
int nx, ny;

int bfsHungarian()
{
	int res = 0;
	int qs, qe;
	memset( matchX, -1, sizeof(matchX) );
	memset( matchY, -1, sizeof(matchY) );
	memset( chkY, -1, sizeof(chkY) );

	for( int i=0; i<nx; i++ )
	{
		if( matchX[i] == -1 )	//如果该X顶点未找到匹配点,将其放入队列。
		{
			qs = qe = 0;
			queue[qe++] = i;
			prevX[i] = -1;	//并且标记,它是路径的起点
			bool flag = 0;

			while( qs<qe && !flag )
			{
				int u = queue[qs];
				for( int v=0; v<ny&&!flag; v++ )
				{
					if( graph[u][v] && chkY[v]!=i )	//如果该节点与u有边且未被访问过
					{
						chkY[v] = i;	//标记且将它的前一个顶点放入队列中,也就是下次可能尝试这个顶点看能否为它找到新的节点
						queue[qe++] = matchY[v];
						if( matchY[v] >= 0 )
							prevX[matchY[v]] = u;
						else	//到达了增广路径的最后一站
						{
							flag = 1;
							int d=u, e=v;
							while( d!=-1 )	//一路通过prevX找到路径的起点
							{
								int t = matchX[d];
								matchX[d] = e;
								matchY[e] = d;
								d = prevX[d];
								e = t;
							}
						}
					}
				}
				qs++;
			}
			if( matchX[i] != -1 )
				res++;
		}
	}
	return res;
}


最优匹配算法因为是项目需要,我用的是java。
public class KuhnMunkres {
	
	private int maxN, n, lenX, lenY;
	private double[][] weights;
	private boolean[] visitX, visitY;
	private double[] lx, ly;
	private double[] slack;
	private int[] match;
	
	public KuhnMunkres( int maxN )
	{
		this.maxN = maxN;
		visitX = new boolean[maxN];
		visitY = new boolean[maxN];
		lx = new double[maxN];
		ly = new double[maxN];
		slack = new double[maxN];
		match = new int[maxN];
	}
	
	public int[][] getMaxBipartie( double weight[][], double[] result )
	{
		if( !preProcess(weight) )
		{
			result[0] = 0.0;
			return null;
		}
		//initialize memo data for class
		//initialize label X and Y
		Arrays.fill(ly, 0);
		Arrays.fill(lx, 0);
		for( int i=0; i<n; i++ )
		{
			for( int j=0; j<n; j++ )
			{
				if( lx[i]<weights[i][j])
					lx[i] = weights[i][j];
			}
		}
		
		//find a match for each X point
		for( int u=0; u<n; u++ )
		{
			Arrays.fill(slack, 0x7fffffff);
			while(true)
			{
				Arrays.fill(visitX, false);
				Arrays.fill(visitY, false);
				if( findPath(u) )	//if find it, go on to the next point
					break;
				//otherwise update labels so that more edge will be added in
				double inc = 0x7fffffff;
				for( int v=0; v<n; v++ )
				{
					if( !visitY[v] && slack[v] < inc )
						inc = slack[v];
				}
				for( int i=0; i<n; i++ )
				{
					if( visitX[i] )
						lx[i] -= inc;
					if( visitY[i] )
						ly[i] += inc;
				}
			}
		}
		result[0] = 0.0;
		for( int i=0; i<n; i++ )
		{
			if( match[i] >= 0 )
				result[0] += weights[match[i]][i];
		}
		return matchResult();
	}
	
	public int[][] matchResult()
	{
		int len = Math.min(lenX, lenY);
		int[][] res = new int[len][2];
		int count=0;
		for( int i=0; i<lenY; i++ )
		{
			if( match[i] >=0 && match[i]<lenX )
			{
				res[count][0] = match[i];
				res[count++][1] = i;
			}
		}
		return res;
	}
	
	private boolean preProcess( double[][] weight )
	{
		if( weight == null )
			return false;
		lenX = weight.length; lenY = weight[0].length;
		if( lenX>maxN || lenY>maxN )
			return false;
		Arrays.fill(match, -1);
		n = Math.max(lenX, lenY);
		weights = new double[n][n];
		for( int i=0; i<n; i++ )
			Arrays.fill(weights[i], 0.0);
		for( int i=0; i<lenX; i++ )
			for( int j=0; j<lenY; j++ )
				weights[i][j] = weight[i][j];
		return true;
	}
	
	private boolean findPath( int u )
	{
		visitX[u] = true;
		for( int v=0; v<n; v++ )
		{
			if( !visitY[v] )
			{
				double temp = lx[u]+ly[v]-weights[u][v];
				if( temp == 0.0 )
				{
					visitY[v] = true;
					if( match[v] == -1 || findPath(match[v]) )
					{
						match[v] = u;
						return true;
					}
				}
				else
					slack[v] = Math.min(slack[v], temp);
			}
		}
		return false;
	}

}
  • 大小: 10.6 KB
  • 大小: 15.8 KB
  • 大小: 15.4 KB
  • 大小: 18 KB
  • 大小: 17.4 KB
  • 大小: 20.1 KB
分享到:
评论

相关推荐

    KM_Algorithm22.rar_KM matlab_KM matlab_km算法matlab_matlab km算法_

    KM算法可以轻松地解决这个问题,因为它能够找到最佳的一对一匹配。在MATLAB中,通过将任务视为一个顶点集,工人视为另一个顶点集,边的权重表示完成任务的成本,可以利用KM算法找到最优的指派方案。 总的来说,KM...

    KM_KM算法_C++_ACM_

    **KM算法** KM算法,全称为Karmarkar's算法,是由印度计算机科学家 Narendra Karmarkar ...在ACM竞赛的背景下,尽管应用不常见,但学习和理解KM算法能提升对优化问题的解决能力,对于提升程序员的综合技能有积极意义。

    二分图匹配问题(匈牙利及KM算法)

    本文将深入探讨二分图、最大匹配、匈牙利算法以及KM算法。 首先,理解二分图的概念至关重要。二分图是指一个无向图的顶点集可以被分为两个互不相交的子集X和Y,使得每条边连接的两个顶点分别属于这两个不同的子集。...

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

    本资源介绍了二分图,二分图的最大匹配,二分图的完备匹配,二分图的最佳匹配。 以及介绍了 匈牙利算法,KM算法的步骤。并且有详细的图解,方便理解。

    KM_KM算法_C++_ACM_源码.zip

    在实际应用中,KM算法有其局限性,比如对初始质心敏感、不适用于非凸形状的类别以及对异常值敏感等。因此,可能需要结合其他聚类算法或进行预处理以改进结果。 为了更好地理解和学习KM算法,可以从以下几个方面入手...

    二分图匹配匈牙利算法和KM算法简介.pptx

    在现代管理信息化的背景下,二分图匹配问题及其解决算法——匈牙利算法和KM算法,显得尤为重要。这些算法不仅在理论上极具价值,而且在实际应用中发挥着不可替代的作用。 首先,我们要了解什么是二分图。二分图,又...

    二分图的完美匹配 KM算法.docx

    以下是对KM算法的详细步骤: 1. 初始化:将所有边的权重设为无穷大(表示无边),然后用给定的边权重更新这些值。创建两个数组`xl`和`yl`,分别表示左部和右部顶点的最小期望值。再初始化一个匹配数组`match`,用于...

    图论- 二分图- KM 算法.rar

    KM算法,全称为Kuhn-Munkres算法,是解决二分图最大匹配问题的一种高效算法,特别适用于处理大规模数据。 首先,我们来理解什么是二分图。在无向图G中,如果可以将其顶点集合V划分为两个不相交的子集V1和V2,使得每...

    ISODATA.rar_KM算法_isodata

    该算法源于K-Means(KM)算法,但通过引入迭代和合并策略,使其在处理聚类问题时更加灵活和精确。ISODATA算法的核心思想是在每一轮迭代中,不仅对数据点进行分类,还会根据类别内的数据分布情况动态调整类别数量,...

    广告商收益化最大问题_M?n_KM算法_二分图最大匹配_

    n-KM算法和二分图最大匹配理论扮演着至关重要的角色。 M?n-KM算法,也被称为M?n-Konig定理或KM算法,是解决二分图最大匹配问题的一种高效方法。在广告场景中,我们可以将广告商看作图的一边(二分图的一类顶点),...

    电信设备-基于迭代KM算法的跨层异构多信道频谱感知方法.zip

    "基于迭代KM算法的跨层异构多信道频谱感知方法"是解决这一问题的一种创新技术,旨在优化频谱效率并提高系统的整体性能。本文将深入探讨这一方法,以及它如何在电信设备中发挥作用。 首先,我们要理解频谱感知的基本...

    鬼谷子问数+KM.zip

    `HungarianAlgorithm.java`这个文件很可能就是KM算法的具体实现,通过阅读和学习这段代码,我们可以直观地理解算法的运行流程和逻辑。 在编程实践中,理解并能够熟练运用这些算法对于提升编程技能和解决问题的能力...

    基于dijkstra公交最小换乘算法

    **基于Dijkstra公交最小换乘算法** 在城市公共交通系统中,如何找到从起点到终点的最短路径并最少换乘是出行者关心的问题之一。...通过对算法的理解和代码实现,我们可以为智能交通系统贡献更高效、人性化的解决方案。

    Kuhn—Munkres算法.zip

    《Kuhn-Munkres算法详解及其在最大...对于学习和理解图论、运筹学和优化理论的学生,掌握KM算法及其应用是十分重要的。在进行数学建模时,合理利用这种算法,往往能够帮助我们找到更优的解决方案,提升模型的实用价值。

    最佳分配算法C#示例.rar

    学习和理解KM算法的C#实现可以帮助开发者更好地处理实际问题,提高资源利用效率。 总的来说,"最佳分配算法C#示例.rar"这个压缩包包含了一个使用C#语言实现的KM算法,用于解决二阵图的最佳分配问题。通过解析和学习...

    配对序列生成算法实现与分析

    4. **图论**:在某些情况下,配对问题可以表示为图的匹配问题,如二分图的最大匹配问题,Kuhn-Munkres(KM)算法或者Blossom算法是解决这类问题的有效工具。 5. **时间复杂度和空间复杂度分析**:理解算法的效率是...

    ACM 常用的算法代码

    在ACM(国际大学生程序设计竞赛)中,程序员经常需要掌握一些特定的算法来解决复杂的问题。本篇文章将深入探讨两个常见的算法:凸包算法和判断两条线段是否相交...理解并能正确实现这些算法是提升编程能力的重要一步。

    利用C#语言开发K-Means聚类算法

    通过对代码的学习,不仅可以掌握K-Means算法的原理,还能进一步熟悉C#编程技巧,特别是处理数据和实现算法逻辑的部分。对于想要提升自己在数据科学和机器学习领域能力的C#开发者来说,这是一个非常有价值的资源。

    matlab实现匈牙利算法二分图最大匹配的程序

    匈牙利算法,又称为Kuhn-Munkres算法或KM算法,是由Eldredge和Kuhn以及Munkres独立提出的。这个算法通过迭代过程,不断调整权重,最终找到二分图的最大匹配。在实际应用中,这种算法常用于任务分配、调度问题、网络...

Global site tag (gtag.js) - Google Analytics