二分图
二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。
简而言之,就是顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。
染色法判断二分图
给定一个 n n n 个点 m m m 条边的无向图,图中可能存在重边和自环。
请你判断这个图是否是二分图。
输入格式
第一行包含两个整数 n n n 和 m m m。
接下来 m m m 行,每行包含两个整数 u u u 和 v v v,表示点 u u u 和点 v v v 之间存在一条边。
输出格式
如果给定图是二分图,则输出 Yes
,否则输出 No
。
数据范围
1 ≤ n , m ≤ 105 1≤n,m≤105 1≤n,m≤105
输入样例:
4 4
1 3
1 4
2 3
2 4
输出样例:
Yes
代码:
import java.util.*;
public class Main {
static int n, m, idx;
static int N = 200010;
static int[] h = new int[N], e = new int[N], ne = new int[N];
static int[] st = new int[N]; // 用于表示结点的颜色状态
public static void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
// 传入两个参数,一个是点的编号,另一个是颜色值分别用1和2表示两个颜色
public static boolean dfs(int u, int c) {
st[u] = c; // 现将该点的颜色染成c
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if (st[j] == 0) { // 如果没有被染色过,就进行染色
if(!dfs(j, 3 - c)) return false; // 递归进行染色,如果递归染色失败,就返回false
} else if(st[j] == c) return false; // j表示的是与u相连的结点,如果染成了与u相同的颜色,就返回false
}
return true;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Arrays.fill(h, -1); // 初始化头结点数组
n = sc.nextInt(); // 输入点数
m = sc.nextInt(); // 边数
while (m-- > 0) {
int a = sc.nextInt(), b = sc.nextInt();
add(a, b);
add(b, a);
}
boolean flag = true;
for (int i = 1; i <= n; i++) { // 循环n次,为每个点染色
if (st[i] == 0) {
if (!dfs(i, 1)) {
flag = false;
break;
}
}
}
if (flag) System.out.println("Yes");
else System.out.println("No");
}
}
匈牙利算法
匈牙利算法是一种在多项式时间内求解任务分配问题的组合优化算法,并推动了后来的原始对偶方法。1955年,库恩(W.W.Kuhn)利用匈牙利数学家康尼格(D.Kőnig)的一个定理构造了这个解法,故称为匈牙利法。
例题:
给定一个二分图,其中左半部包含 n 1 n1 n1 个点(编号 1 ∼ n 1 1∼n1 1∼n1),右半部包含 n 2 n2 n2 个点(编号 1 ∼ n 2 1∼n2 1∼n2),二分图共包含 m m m 条边。
数据保证任意一条边的两个端点都不可能在同一部分中。
请你求出二分图的最大匹配数。
二分图的匹配:给定一个二分图 G G G,在 G G G 的一个子图 M M M 中,MM 的边集 E {E} E 中的任意两条边都不依附于同一个顶点,则称 M M M 是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。
代码:
import java.util.*;
public class Main {
static int n1, n2, m;
static int N = 510, M = 100010; // 分别是点数数组大小和边数数组大小
static int[] h = new int[N], e = new int[M], ne = new int[M];
static int idx;
static boolean[] st = new boolean[N];
static int[] match = new int[N]; // 储存n1点集中的点匹配了n2点集中的点的信息。
public static void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
public static boolean find(int x) {
for (int i = h[x]; i != -1; i = ne[i]) { // 这里遍历的其实就是与n1中点集相连的n2中的点(以为存边的信息时只存了n1到n2点的边)
int j = e[i];
if (!st[j]) {
st[j] = true;
if (match[j] == 0 || find(match[j])) {
match[j] = x;
return true;
}
}
}
return false;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n1 = sc.nextInt();
n2 = sc.nextInt();
m = sc.nextInt();
Arrays.fill(h, -1);
while (m-- > 0) {
int a = sc.nextInt(), b = sc.nextInt();
// 只需要添加a到b的一条边
add(a, b);
}
int res = 0;
for (int i = 1; i <= n1; i++) {
// 每次循环都赋予初值false
Arrays.fill(st, false);
if (find(i)) res++;
}
System.out.println(res);
}
}