目录

1 问题描述

2 解决方案

 


1 问题描述

何为二分图的最大权匹配问题?

最大权二分匹配问题就是给二分图的每条边一个权值,选择若干不相交的边,得到的总权值最大。


2 解决方案

对于此问题的讲解,引用文末参考资料1:

解决这个问题可以用KM算法。理解KM算法需要首先理解“可行顶标”的概念。可行顶标是指关于二分图两边的每个点的一个值lx[i]或ly[j],保证对于每条边w[i][j]都有lx[i]+ly[j]-w[i][j]>=0。如果所有满足lx[i]+ly[j]==w[i][j]的边组成的导出子图中存在一个完美匹配,那么这个完美匹配肯定就是原图中的最大权匹配。理由很简单:这个匹配的权值之和恰等于所有顶标的和,由于上面的那个不等式,另外的任何匹配方案的权值和都不会大于所有顶标的和。

但问题是,对于当前的顶标的导出子图并不一定存在完美匹配。这时,可以用某种方法对顶标进行调整。调整的方法是:根据最后一次不成功的寻找交错路的DFS,取所有i被访问到而j没被访问到的边(i,j)的lx[i]+ly[j]-w[i][j]的最小值d。将交错树中的所有左端点的顶标减小d,右端点的顶标增加d。经过这样的调整以后:原本在导出子图里面的边,两边的顶标都变了,不等式的等号仍然成立,仍然在导出子图里面;原本不在导出子图里面的边,它的左端点的顶标减小了,右端点的顶标没有变,而且由于d的定义,不等式仍然成立,所以他就可能进入了导出子图里。

初始时随便指定一个可行顶标,比如说lx[i]=max{w[i][j]|j是右边的点},ly[i]=0。然后对每个顶点进行类似Hungary算法的find过程,如果某次find没有成功,则按照这次find访问到的点对可行顶标进行上述调整。这样就可以逐步找到完美匹配了。

值得注意的一点是,按照上述d的定义去求d的话需要O(N^2)的时间,因为d需要被求O(N^2)次,这就成了算法的瓶颈。可以这样优化:设slack[j]表示右边的点j的所有不在导出子图的边对应的lx[i]+ly[j]-w[i][j]的最小值,在find过程中,若某条边不在导出子图中就用它对相应的slack值进行更新。然后求d只要用O(N)的时间找到slack中的最小值就可以了。

下面代码所使用的测试数据如下图:

具体代码如下:

package com.liuzhen.practice;

import java.util.Scanner;

public class Main {
public static int MAX = 100;
public static int n;
public static int[][] value = new int[MAX][MAX]; //给定二分图的权重值
public static int[] lx = new int[MAX]; //记录二分图左半部分顶点的可行顶标
public static int[] ly = new int[MAX]; //记录二分图右半部分顶点的可行顶标
public static boolean[] sx = new boolean[MAX];//用于记录二分图左半部分顶点是否在最终结果中
public static boolean[] sy = new boolean[MAX];//用于记录二分图右半部分顶点是否在最终结果中
public static int[] pre = new int[MAX]; //用于记录最终结果中顶点y匹配的顶点x public boolean dfs(int x) { //采用匈牙利算法找增广路径
sx[x] = true; //代表左半部分顶点x包含在最终结果中
for(int y = 0;y < n;y++) {
if(!sy[y] && lx[x] + ly[y] == value[x][y]) {
sy[y] = true; //代表右半部分顶点y包含在最终结果中
if(pre[y] == -1 || dfs(pre[y])) {
pre[y] = x;
return true;
}
}
}
return false;
} public int getKM(int judge) {
if(judge == -1) { //代表寻找二分图的最小权匹配
for(int i = 0;i < n;i++)
for(int j = 0;j < n;j++)
value[i][j] = -1 * value[i][j]; //把权值变为相反数,相当于找最大权匹配
}
//初始化lx[i]和ly[i]
for(int i = 0;i < n;i++) {
ly[i] = 0;
lx[i] = Integer.MIN_VALUE;
for(int j = 0;j < n;j++) {
if(value[i][j] > lx[i])
lx[i] = value[i][j];
}
} for(int i = 0;i < n;i++)
pre[i] = -1; //初始化右半部分顶点y的匹配顶点为-1 for(int x = 0;x < n;x++) { //从左半部分顶点开始,寻找二分图完美匹配的相等子图完美匹配
while(true) {
for(int i = 0;i < n;i++) {//每次寻找x的增广路径,初始化sx[i]和sy[i]均为被遍历
sx[i] = false;
sy[i] = false;
}
if(dfs(x)) //找到从x出发的增广路径,结束循环,寻找下一个x的增广路径
break;
//下面对于没有找到顶点x的增广路径进行lx[i]和ly[i]值的调整
int min = Integer.MAX_VALUE;
for(int i = 0;i < n;i++) {
if(sx[i]) { //当sx[i]已被遍历时
for(int j = 0;j < n;j++) {
if(!sy[j]) { //当sy[j]未被遍历时
if(lx[i] + ly[j] - value[i][j] < min)
min = lx[i] + ly[j] - value[i][j];
}
}
}
}
if(min == 0)
return -1;
for(int i = 0;i < n;i++) {
if(sx[i])
lx[i] = lx[i] - min;
if(sy[i])
ly[i] = ly[i] + min;
}
}
} int sum = 0;
for(int y = 0;y < n;y++) {
System.out.println("y顶点"+y+"和x顶点"+pre[y]+"匹配");
if(pre[y] != -1)
sum = sum + value[pre[y]][y];
}
if(judge == -1)
sum = -1 * sum;
return sum;
} public static void main(String[] args) {
Main test = new Main();
Scanner in = new Scanner(System.in);
n = in.nextInt();
int k = in.nextInt(); //给定二分图的有向边数目
for(int i = 0;i < k;i++) {
int x = in.nextInt();
int y = in.nextInt();
int v = in.nextInt();
value[x][y] = v;
}
System.out.println(test.getKM(1));
}
}

运行结果:

5
10
0 0 2
0 1 3
1 0 2
2 0 4
2 2 2
3 2 1
3 3 3
3 4 2
4 3 8
4 4 3
y顶点0和x顶点2匹配
y顶点1和x顶点0匹配
y顶点2和x顶点1匹配
y顶点3和x顶点4匹配
y顶点4和x顶点3匹配
17

参考资料:

1.求最大权二分匹配的KM算法

2.二分图最大权匹配-km算法

3.二分图带权匹配 KM算法与费用流模型建立

算法笔记_139:二分图的最大权匹配(Java)的更多相关文章

  1. Java实现二分图的最大权匹配

    1 问题描述 何为二分图的最大权匹配问题? 最大权二分匹配问题就是给二分图的每条边一个权值,选择若干不相交的边,得到的总权值最大. 2 解决方案 解决这个问题可以用KM算法.理解KM算法需要首先理解& ...

  2. 算法笔记_137:二分图的最大匹配(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 何为二分图的最大匹配问题? 引用自百度百科: 首先得说明一下何为匹配: 给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于 ...

  3. hdu 3722 Card Game 二分图的最大权匹配

    题目可以转化为2个集合,x集合和y集合,其中的元素是1-n个字符串. 首先预处理点与点的边权,然后直接用二分图的最大权匹配模板. #include<stdio.h> #include< ...

  4. 【算法笔记】二分图与KM算法(当你试图只看蓝书学算法

    前言 呜,好久没写博客了,DDL 也有好多,一不留神就轮到我了呜. 看了一眼其它同学写的博客,什么数模啊,什么 CTF 啊,什么 Python 爬虫啊,感觉自己真是越来越菜了呜. 然后在我一愁莫展之际 ...

  5. 算法笔记_180:历届试题 国王的烦恼(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 问题描述 C国由n个小岛组成,为了方便小岛之间联络,C国在小岛间建立了m座大桥,每座大桥连接两座小岛.两个小岛间可能存在多座桥连接.然而,由于海水冲 ...

  6. 算法笔记_133:最大连续乘积子数组(Java)

    目录 1 问题描述 2 解决方案 2.1 蛮力法 2.2 动态规划法   1 问题描述 给定一个浮点数组,任意取出数组中的若干个连续的数相乘,请找出其中乘积最大的子数组. 2 解决方案 2.1 蛮力法 ...

  7. 算法笔记_131:出现次数超过一半的数(Java)

    目录 1 问题描述 2 解决方案 2.1 每次删除两个不同的数 2.2 记录两个值   1 问题描述 数组中有一个数出现的次数超过了数组长度的一半,请找出这个数. 2 解决方案 2.1 每次删除两个不 ...

  8. 算法笔记_044:表达式计算求值(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 问题描述 输入一个只包含加减乖除和括号的合法表达式,求表达式的值.其中除表示整除. 输入格式 输入一行,包含一个表达式. 输出格式 输出这个表达式的 ...

  9. 算法笔记_052:蓝桥杯练习Multithreading(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 问题描述 现有如下一个算法: repeat ni times yi := y y := yi+1 end repeat 令n[1]为你需要算加法的第 ...

随机推荐

  1. 【BZOJ 1566】 1566: [NOI2009]管道取珠 (DP)

    1566: [NOI2009]管道取珠 Time Limit: 20 Sec  Memory Limit: 650 MBSubmit: 1659  Solved: 971 Description In ...

  2. openssl-0.9.8k_WIN32(RSA密钥生成工具

    韩梦飞沙  韩亚飞  313134555@qq.com  yue31313  han_meng_fei_sha openssl-0.9.8k_WIN32(RSA密钥生成工具

  3. BZOJ1084 SCOI2005最大子矩阵

    考虑DP f[i][j][k]表示一行到i一行到j共取k块最大值,类似于最长公共子序列n^2那种 注意相等时可以一起拿 By:大奕哥 #include<bits/stdc++.h> usi ...

  4. [NOIP2017]列队(线段树/裂点splay)

    考虑n=1的做法,就是支持: 1.在线删一个数 2.在结尾加一个数 3.查询序列的第y个数 用线段树记录区间内被删元素的个数,可以通过线段树上二分快速得解,对于新增的数,用vector记录即可. 对于 ...

  5. 使用 Start 屏幕查找 Windows 更新

    使用 Start 屏幕查找 Windows 更新   从屏幕右侧边缘扫入,然后点击“搜索”.      如果您正在使用鼠标,请指向屏幕右下角,然后单击“搜索”.  在搜索框内输入 Windows 更新 ...

  6. MAC 更新SVN到1.8

    经过谷歌和百度N次后,最终搞定SVN的升级,Intellij Idea和Xcode5.1都能够正常使用. 步骤: 1. 下载Subverion的Max安装版.(推荐.使用其它brew和port都试过, ...

  7. MEF 导入(Import)和导出(Export)

    前言: MEF不同于其他IOC容器(如:Castle)很重要的原因在于它使用了特性化编程模型(涉及到两个概念:“特性”和“编程模型”). 特性(Attribute):举例来说就是我们在开发过程中在类上 ...

  8. linux中的dup()系统调用

    参考1:http://www.blogjava.net/lihao336/archive/2011/12/13/366231.html 在linux纷繁复杂的内核代码中,sys_dup()的代码也许称 ...

  9. Redis 3.0版本启动时出现警告的解决办法

    原文:http://m.blog.csdn.net/article/details?id=50864933 Redis 3.0.7版本启动时出现警告的解决办法 发表于2016/3/12 12:52:4 ...

  10. jq设置样式

    单个样式: $(this).css("color","red"); 多个样式: $(this).css({color:"red",backg ...