1. 程式人生 > >二分圖的基本知識

二分圖的基本知識

二分圖基礎知識

昨天晚上開始看二分圖,到現在基本的東西學會了

我就寫一下我自己的理解

 

首先什麼是二分圖

顧名思義就是能分成兩個部分的圖

要注意的是,‘分’的是點

並且這兩個集合(這裡我們稱作X集合和Y集合)內部所有的點之間沒有邊相連,也就是說X集合中任何兩點之間都不會有邊相連, Y亦然

 

定理1:無向圖G為二分圖的一個衝要條件是 1、G中至少包含兩個頂點  2、G中所有的迴路長度都必須是偶數

 

接下來是一些概念:

匹配:設G=<V, E>為二分圖,如果 M⊆E,並且 M 中沒有任何兩邊有公共端點,則成M為G的一個匹配。【也就是說匹配的實質是一些邊的集合。】

最大匹配:邊數最多的匹配

完備匹配與完全匹配:若 X 中所有的頂點都是匹配 M 中的端點。則稱 M 為X的完備匹配。 若M既是 X-完備匹配又是 Y-完備匹配,則稱M 為 G 的完全匹配。

最小點覆蓋:用盡可能少的點去覆蓋所有的邊【最小點覆蓋集是點的集合,其個數為最小點覆蓋數】

最大點獨立:跟網路流中的最大點權獨立集有點類似,這裡指的是最大獨立的個數

 

接下來是二分圖的一些性質:

設無向圖G有n個頂點,並且沒有孤立頂點,那麼,

1、點覆蓋數 + 點獨立數 = n

2、最小點覆蓋數 = 二分圖的最大匹配

3、最大點獨立數 = n - 最小點覆蓋數 = n - 最大匹配

 

二分圖的判定:

判斷一個圖是不是二分圖有兩條1、n>= 2   2、不存在奇圈

我們可以用黑白染色的方法進行判斷

const int maxn = 105;

int col[maxn];

bool is_bi(int u) {
    for(int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        if(col[v] == col[u]) return false;
        if(!col[v]) {
            col[v] = 3 - col[u];
            if(!is_bi(v)) return false;
        }
    }
    return true;
}

接下來介紹一下求二分圖最大匹配的匈牙利演算法。

匈牙利演算法的思想是這樣的:如果一個圖中存在增廣路,那麼沿著這條路增廣,匹配就會加1,知道不存在增廣路為止

這裡的增廣路是這麼定義的:對於一個未匹配或已經匹配好一部分的G來說

在X集合中的未匹配點出發,依次經過未匹配邊匹配邊未匹配邊匹配邊……而終點落在Y中的一個未訪問點上,那麼只要將該路上的匹配邊於未匹配邊調換,那麼新的匹配必將比原來的匹配多1,【詳細見http://blog.csdn.net/xuguangsoft/article/details/7861988中的圖】//如果不理解可以看劉汝佳大白書,一會動手模擬一下程式即可

下面是匈牙利演算法的鄰接矩陣和鄰接表程式

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int maxn = 105;
const int INF = 1000000000;

bool vis[maxn];//查詢右集合中的點有沒有被訪問過
int link[maxn];//link[i]表示右集合中的i點是由左集合中的哪個點連線的
int G[maxn][maxn];//鄰接矩陣
int x_cnt; int y_cnt;//左右集合的點的個數

bool find(int u) {//用來尋找增廣路
    for(int i = 1; i <= y_cnt; i++) {//遍歷右集合中的每個點
        if(!vis[i] && G[u][i]) {//沒有被訪問過並且和u點有邊相連
            vis[i] = true;//標記該點
            if(link[i] == -1 || find(link[i])){ //該點是增廣路的末端或者是通過這個點可以找到一條增廣路
                link[i] = u;//更新增廣路   奇偶倒置
                return true;//表示找到一條增廣路
            }
        }
    }
    return false;//如果查找了右集合裡的所有點還沒找到通過該點出發的增廣路,該點變不存在增廣路
}

int solve() {
    int num = 0;
    memset(link, -1, sizeof(link));//初始化為-1表示  不與左集合中的任何元素有link
    for(int i = 1; i <= x_cnt; i++) {//遍歷左集合
        memset(vis, false, sizeof(vis));//每一次都需要清除標記
        if(find(i)) num++;//找到一條增廣路便num++
    }
    return num;
}

匈牙利演算法--鄰接矩陣
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int maxn = 33;
const int INF = 1000000000;

struct Node {
    int to;
    int next;
}q[MaxEdge];

struct MaxMatch() {
    int head[MaxEdge];
    int tot;
    int vis[Y_cnt];
    int link[Y_cnt];

    void init(int x_cnt) {
        this -> x_cnr = x_cnt;
        tot = 0;
    }

    void AddEdge(int u, int v) {
        q[tot].to = v;
        q[tot].next = head[u];
        head[u] = tot ++;
    }

    bool find(int u) {
        for(int i = head[u]; i; i = q[i].next) {
            int v = q[i].to;
            if(!vis[v]) {
                vis[v] = 1;
                if(link[v] == -1 || find(link[v])) {
                    link[v] = u;
                    return true;
                }
            }
        }
        return false;
    }

    int Match() {
        int num = 0;
        memset(link, -1, sizeof(link));
        for(int i = 0; i < x_cnt; i++) { // ±éÀú×󼯺Ï
            memset(vis, 0, sizeof(vis));
            if(find(X[i])) num++;
        }
        return num;
    }
};

匈牙利演算法--鄰接表

可以用HDU2063熟悉模板

 

 

下面也是最重要也是最難理解的二分圖的最佳匹配

上面介紹的匈牙利演算法只能求出匹配邊的條數,現在我們來加個條件:讓二分圖的每個邊上都加一個權值

現在讓你求出最大(最小)權值的匹配

這裡有個常用演算法--KM演算法

首先要引入一個概念:可行頂標。

設頂點 Xi 的頂標為 lx[i],頂點 Yj 的頂標為 ly[j],頂點 Xi 與 Yj 之間的邊權為 w[i][j] 。在演算法執行過程中的任一時刻,對於任一條邊 (i,j),lx[i]+ly[j]>=w[i,j] 始終成立。

那麼Lx[i] 為i可行頂標,Ly[j]為j的可行頂標

從這個角度考慮,如果滿足lx[i]+ly[j]==w[i][j]的條件下的一個子圖中存在一個完美匹配的話,那麼這個匹配就一定是原圖的最大全匹配

證明:由於該匹配的可行頂標之和等於匹配的權值之和,而由於lx[i]+ly[j]>=w[i,j]其它的所有匹配的防方案權值一定會小於頂標之和。

所以問題就轉化成了通過修改可行頂標,求得最理想的匹配。

KM演算法調整的方法是: 根據最後一次不成功的尋找交錯路的 DFS,取所有 i 頂點被訪問到而 j 頂點沒被訪問到的邊 (i,j) 的 lx[i]+ly[j]-w[i][j] 的最小值 d。將交錯樹中的所有左端點的頂標減小d,右端點的頂標增加 d。

經過這樣的調整以後: 原本在匯出子圖裡面的邊,兩邊的頂標都變了,不等式的等號仍然成立,仍然在匯出子圖裡面;原本不在匯出子圖裡面的邊,它的左端點的頂標減小了,右端點的頂標沒有變,而且由於 d 的定義,不等式仍然成立,所以他就可能進入了匯出子圖裡,這樣經過不斷的調整,最後就可以找到 一個有完美匹配的匯出子圖(原圖的完備匹配),也就求出了該圖的最大權匹配。

程式碼是劉汝佳大白書上抄的:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 500 + 10;
const int INF = 1000000000;

int W[maxn][maxn], n;
int Lx[maxn], Ly[maxn]; // 頂標
int left[maxn];         // left[i]為右邊第i個點的匹配點編號
bool S[maxn], T[maxn];   // S[i]和T[i]為左/右第i個點是否已標記

bool match(int i) {
    S[i] = true;
    for(int j = 1; j <= n; j++) if (Lx[i]+Ly[j] == W[i][j] && !T[j]){
        T[j] = true;
        if (!left[j] || match(left[j])){
            left[j] = i;
            return true;
        }
    }
    return false;
}

void update() {
    int a = INF;
    for(int i = 1; i <= n; i++) if(S[i])
        for(int j = 1; j <= n; j++) if(!T[j])
            a = min(a, Lx[i]+Ly[j] - W[i][j]);
    for(int i = 1; i <= n; i++) {
        if(S[i]) Lx[i] -= a;
        if(T[i]) Ly[i] += a;
    }
}

void KM() {
    for(int i = 1; i <= n; i++) {
        left[i] = Lx[i] = Ly[i] = 0;
        for(int j = 1; j <= n; j++)
            Lx[i] = max(Lx[i], W[i][j]);
    }
    for(int i = 1; i <= n; i++) {
        for(;;) {
        for(int j = 1; j <= n; j++) S[j] = T[j] = false;
            if(match(i)) break; else update();
        }
    }
}

KM鄰接矩陣版--劉汝佳
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;

const int maxn = 500 + 5; // 頂點的最大數目
const int INF = 1000000000;

// 最大權匹配
struct KM {
    int n;                  // 左右頂點個數
    vector<int> G[maxn];    // 鄰接表
    int W[maxn][maxn];      // 權值
    int Lx[maxn], Ly[maxn]; // 頂標
    int left[maxn];         // left[i]為右邊第i個點的匹配點編號,-1表示不存在
    bool S[maxn], T[maxn];  // S[i]和T[i]為左/右第i個點是否已標記

    void init(int n) {
        this->n = n;
        for(int i = 0; i < n; i++) G[i].clear();
        memset(W, 0, sizeof(W));
    }

    void AddEdge(int u, int v, int w) {
        G[u].push_back(v);
        W[u][v] = w;
    }

    bool match(int u){
        S[u] = true;
        for(int i = 0; i < G[u].size(); i++) {
            int v = G[u][i];
            if (Lx[u]+Ly[v] == W[u][v] && !T[v]){
                T[v] = true;
                if (left[v] == -1 || match(left[v])){
                    left[v] = u;
                    return true;
                }
            }
        }
        return false;
    }

    void update(){
        int a = INF;
        for(int u = 0; u < n; u++) if(S[u])
            for(int i = 0; i < G[u].size(); i++) {
                int v = G[u][i];
                if(!T[v]) a = min(a, Lx[u]+Ly[v] - W[u][v]);
            }
        for(int i = 0; i < n; i++) {
            if(S[i]) Lx[i] -= a;
            if(T[i]) Ly[i] += a;
        }
    }

    void solve() {
        for(int i = 0; i < n; i++) {
            Lx[i] = *max_element(W[i], W[i]+n);
            left[i] = -1;
            Ly[i] = 0;
        }
        for(int u = 0; u < n; u++) {
            for(;;) {
                for(int i = 0; i < n; i++) S[i] = T[i] = false;
                    if(match(u)) break; else update();
            }
        }
    }
};

KM鄰接表版--劉汝佳

原創的作者

暱稱:悠悠我心。
園齡:5年
粉絲:6
關注:2