匈牙利算法

52 阅读1分钟
  • 时间复杂度是 O(nm),实际情况远小于O(nm), n表示点数,m表示边数
  • 匈牙利算法主要用来求二分图的最大匹配

Snipaste_2023-03-18_16-37-52.png

模板

  • C++
int n1, n2;     // n1表示第一个集合中的点数,n2表示第二个集合中的点数
int h[N], e[M], ne[M], idx;  // 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边
int match[N];       // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个
bool st[N];     // 表示第二个集合中的每个点是否已经被遍历过

bool find(int x)
{
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])
        {
            st[j] = true;
            if (match[j] == 0 || find(match[j]))
            {
                match[j] = x;
                return true;
            }
        }
    }

    return false;
}

// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
int res = 0;
for (int i = 1; i <= n1; i ++ )
{
    memset(st, false, sizeof st);
    if (find(i)) res ++ ;
}
  • Java
// 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边
public static int[] h = new int[N];
public static int[] e = new int[M];
public static int[] ne = new int[M];
public static int[] match = new int[N];  // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个
public static boolean[] st = new int[N];  // 表示第二个集合中的每个点是否已经被遍历过
public static int n1, n2;  // n1表示第一个集合中的点数,n2表示第二个集合中的点数

// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
int res = 0;
for (int i = 1; i <= n1; i++) {
    Arrays.fill(st, false);
    if (find(i)) {
        res++;
    }
} 

public boolean find(int x) {
    for (int i = h[x]; i != -1; i = ne[i]) {
        int j = e[i];
        if (!st[j]) {
            st[j] = true;
            //如果j没有被匹配或与j匹配的那个点能找到其他的它进行匹配
            if (match[j] == 0 || find(match[j])) {
                match[j] = x;
                return true;
            }
        }
    }
    return false;
}

练习

01 二分图的最大匹配

  • 题目

Snipaste_2023-03-18_16-39-27.png

  • 题解
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 510;
    public static final int M = 100010;
    // 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边
    public static int[] h = new int[N];
    public static int[] e = new int[M];
    public static int[] ne = new int[M];
    // 表示第二个集合中的每个点是否已经被遍历过
    public static boolean[] st = new boolean[N];
    // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个
    public static int[] match = new int[N];
    // n1表示第一个集合中的点数,n2表示第二个集合中的点数
    public static int n1, n2, m, idx;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        String[] str1 = br.readLine().split(" ");
        n1 = Integer.parseInt(str1[0]);
        n2 = Integer.parseInt(str1[1]);
        m = Integer.parseInt(str1[2]);
        //初始化
        Arrays.fill(h, -1);
        idx = 0;
        while (m-- > 0) {
            String[] str2 = br.readLine().split(" ");
            int u = Integer.parseInt(str2[0]);
            int v = Integer.parseInt(str2[1]);
            add(u, v);
        }

        // 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
        int res = 0;
        for (int i = 1; i <= n1; i++) {
            Arrays.fill(st, false);
            if (find(i)) {
                res++;
            }
        }
        pw.println(res);
        br.close();
        pw.close();
    }

    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]) {
            int j = e[i];
            if (!st[j]) {
                st[j] = true;
                //如果j没有被匹配或与j匹配的那个点能找到其他的它进行匹配
                if (match[j] == 0 || find(match[j])) {
                    match[j] = x;
                    return true;
                }
            }
        }
        return false;
    }
}