二分圖


超級詳細的基礎算法和數據結構合集:
https://blog.csdn.net/GD_ONE/article/details/104061907

摘要

本文主要介紹二分圖的基本概念以及如何用染色法判斷二分圖,如何用匈牙利算法求二分圖的最大匹配。

什麼是二分圖

二分圖又稱作二部圖,是圖論中的一種特殊模型。…(百度百科)
簡單來說就是:如果一個圖的所有頂點可以被分成左右兩個集合,兩個集合中不存在任意兩點是直接相連的。

如圖1所示:


圖一

雖然它看起來並不是一個二分圖,但是我們可以對圖中點的位置進行一些該動。
如圖二所示:


圖二

這樣看着就直觀多了。 135被分爲一個點集,24被分爲一個點集,並且135之間沒有能直接連通的兩個點,24中也沒有。所以上圖就是二分圖。
另外,二分圖可以是不連通的。

染色法判斷二分圖

通過二分圖的定義我們可以知道,同一個點集中任意兩個點之間沒有邊,也就是說一條邊的兩個頂點一定不在同一個集合。然後我們可以根據這個性質給每個點設置一個標記,如果一個點標記爲1那麼將它的直接後繼結點標記爲2如果一個點被標記兩次不同的值,那麼說明圖中存在奇數邊的環,該圖不是二分圖

更形象的來說,用染色的方式給每個點依次進行染色,如果一個點被染上兩種顏色,則該圖不是二分圖。這就是染色法的由來。

那麼爲什麼存在奇數環時,該圖一定不是二分圖呢?

如圖三所示:



圖三

可以看到2,3,4號點連成了一個奇數環,那麼根據定義,2號點和3號點相鄰,所以2,3一定在不同的集合,假如2號點在1集合,3號在2集合,那麼4號既不能和2號在一個集合也不能和3號在一個集合,此時就矛盾了,構不成二分圖。

接下來就是實現代碼了:

依次對每個點進行染色,所以我們可以直接對圖DFS一遍。如果遇到某個點被染了兩次,就結束。說明該圖不是二分圖。如果所有點都被成功染色,說明該圖是二分圖。

public static Boolean dfs(int u, int c){// u是當前染色的結點,c是顏色
        color[u] = c;  // 用1和2代表兩種顏色,0代表還未染色
        for(int i = h[u]; i != 0; i = ne[i]){
            int j = e[i];
            if(color[j] == 0){//如果u的鄰接點e[i]還未染色,則遞歸對其染色
                dfs(j, 3-c);// 如果u是1, 那麼j就是 3-1 = 2, u是2, j就是3-2 = 1
            }
            else if(color[u] == color[j]){//如果u和j的顏色相同,說明u和j在同一個點集內,存在奇數環,該圖不是二分圖
                return false;
            }
        }
        return true;// 染色成功
    }    

看一道模板題:
染色法判定二分圖

代碼:

import java.io.*;
import java.util.*;

public class Main{
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    static final int N = 2*100010;
    static int[] e = new int[N], ne = new int[N], h = new int[N], color = new int[N];
    static int n, m, idx = 1;
    public static int Int(String s){
        return Integer.parseInt(s);
    }
    
    public static void add(int a, int b){
        e[idx] = b;  ne[idx] = h[a]; h[a] = idx++;
    }
    
    public static Boolean dfs(int u, int c){
        color[u] = c;
        for(int i = h[u]; i != 0; i = ne[i]){
            int j = e[i];
            if(color[j] == 0){
                dfs(j, 3-c);
            }
            else if(color[u] == color[j]){
                return false;
            }
        }
        return true;
    }    
    
    public static void main(String[] agrs) throws Exception{
        String[] s = in.readLine().split(" ");
        n = Int(s[0]);
        m = Int(s[1]);
        
        for(int i = 0; i < m; i++){
            String[] s1 = in.readLine().split(" ");
            int a = Int(s1[0]);
            int b = Int(s1[1]);
            
            add(a, b); add(b, a);
        }
        
        Boolean f = true;
        for(int i = 1; i <= n; i++){// 對每個點都進行染色,防止圖是不連通的
            if(color[i] == 0){
                if(!dfs(i, 1)){
                    if(!dfs(i, 2)){
                        f = false;
                        break;
                    }
                }
            }
        }
           
        if(f) out.write("Yes\n");
        else out.write("No\n");
        out.flush();
    }
}

匈牙利算法

匈牙利算法用於求二分圖的最大匹配數。

二分圖的最大匹配數:

所謂最大匹配數,意思就是求二分圖兩個點集之間一共有多少直接相連的兩兩成對的點對。兩兩成對,跟找對象是一樣的,一個點只能有一個對象,不能存在腳踏兩隻船的情況。

如圖四所示:



圖四

對於圖四,最大匹配數是2, (1 , 2) 和(3, 4),5號點沒人要,單身。

求最大匹配數,可以看做是給點集1中的點從點集2中找對象,點集1中的點都是男的,點集2中的點都是女的,點集1中的男的都很好說話只要當前給他介紹的女的是單身同意匹配,而且點集1中的男的還很善良,哪怕當前介紹的女孩兒不是單身,只要她對象可以跟她立馬分手,他是可以同意匹配的。

如圖五所示:



圖五

  1. 先將2號點介紹給1號點, 此時(2,1)匹配,2的男票是1了。(我們只關心女孩有沒有對象)

  2. 然後輪到給3號點介紹對象了, 3號點和2號點相連,但是問題是2號點此時已經有男朋友了, 不過不要緊,我們來挖強jio,隨着渣男語錄的強大攻勢,2號點和她對象分手了,原因是1號懷疑2號出軌,然後1號就去找6號匹配了。
    最後,皆大歡喜, (2, 3)匹配, (6,1)匹配。

  3. 接着,要給5號點介紹對象了,5和6相連,但是6號已經有男朋友了,對,還是1號,那麼怎麼辦呢,雖然5號驚天地泣鬼神的求愛很感人,但6號還是沒有和1號分手,原因是1號沒有其他的beitai了。因爲5號和4號不相連,所以最終5號和4號單身。該圖的最大匹配爲2。

匈牙利算法就是對以上過程的模擬。
首先用一個數組標記女孩們是否有男朋友。然後遍歷點集1中的所有點,給他們介紹對象。對於每一次介紹對象,如果一個女孩被介紹給別人過了,就不在重複介紹。所以還需要一個數組來標記在匹配過程中,女孩是否被介紹過。
具體看代碼:

public static Boolean find(int x){
    for(int i = h[x]; i != 0; i = ne[i]){
        int j = e[i];
        if(st[j] == 0){ 
            st[j] = 1;
           //如果女孩j沒有男朋友或者女孩j的對象還有其他選擇,就將j匹配給x
           if(match[j] == 0 || find(match[j])){
                match[j] = x; 
                return true; // 匹配成功
           }
        }
    }
        return false;
}

然後是例題:
二分圖的最大匹配

代碼:

import java.io.*;
import java.util.*;

public class Main{
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    static final int N = 100010;
    static int[] e = new int[N], ne = new int[N], h = new int[505], match = new int[N], st = new int[N];
    static int n1, n2, m, idx = 1;
    
    public static int Int(String s){
        return Integer.parseInt(s);
    }
    
    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 != 0; i = ne[i]){
            int j = e[i];
            if(st[j] == 0){
                st[j] = 1;
                
                if(match[j] == 0 || find(match[j])){
                    match[j] = x; 
                    return true; 
                }
            }
        }
        return false;
    }
    
    public static void main(String[] agrs) throws Exception{
        String[] s = in.readLine().split(" ");
        n1 = Int(s[0]);
        n2 = Int(s[1]);
        m = Int(s[2]);
        
        for(int i = 0; i < m; i++){
            String[] s1 = in.readLine().split(" ");
            int a = Int(s1[0]);
            int b = Int(s1[1]);
            add(a, b); 
        }
        
        int res = 0;
        
        for(int i = 1; i <= n1; i++){
            Arrays.fill(st, 0);//每次給一個點匹配對象前都要進行初始化
            if(find(i)) res ++;
        }
       
        out.write(res+"\n");
        out.flush();
    }
}

快去試試你能不能匹配成功吧。yxc說每個點都要嘗試匹配一下,哪怕沒有結果也不能錯過。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章