1. 程式人生 > >二分圖的判定,最大匹配

二分圖的判定,最大匹配

<1>.什麼是二分圖

如果一個圖的頂點可以分為兩個集合X和Y,圖的所有邊一定是有一個頂點屬於集合X,另一個頂點屬於集合Y,則稱該圖為“二分圖”( Bipartite Graph )

<2>.二分圖的判定

  • 如果一個圖是連通的,可以用如下的方法判定是否是二分圖:
  • 在圖中任選一頂點v,定義其距離標號為0,然後把它的鄰接點的距離標號均設為1,接著把所有標號為1的鄰接點均標號為2(如果該點未標號的話),如圖所示,以此類推。
  • 標號過程可以用一次BFS實現。標號後,所有標號為奇數的點歸為X部,標號為偶數的點歸為Y部。
  • 接下來,二分圖的判定就是依次檢查每條邊,看兩個端點是否是一個在X部,一個在Y部。

(如果一個圖不連通則在每個連通塊中作判定)



演算法實現;

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <queue>
#include <vector>
#include <cmath>
#include <set>
#include <deque>
#include <map>
#include <string>

using namespace std;

const int maxn=200+10;   //結點個數  任意值 
int jilu[maxn],erfen[maxn][maxn]; 


//BFS遍歷圖 
bool bfs(int s,int n){
	queue<int> q;
	q.push(s);
	jilu[s]=1;
	while(!q.empty()){
		int from=q.front();
		q.pop();
		for(int i=1;i<=n;++i){
			if(erfen[from][i]&&jilu[i]==-1){
				q.push(i);
				jilu[i]=!jilu[from];
			}
			if(erfen[from][i]&&jilu[from]==jilu[i])
			return false;
		}
	}
	return true;
}



int main(){
//   freopen("C:\\Users\\24398\\Desktop\\in-project.txt","r",stdin);
    int n,m,a,b;
    memset(jilu,0,sizeof(jilu));
    scanf("%d%d",&n,&m);    
    //n頂點個數      m邊數集 
    
	for(int i=0;i<m;++i){      //採用鄰接矩陣儲存   intput()
    	scanf("%d%d",&a,&b);
    	erfen[a][b]=erfen[b][a];	
	}
	
	bool panduan=false;
	for(int j=0;j<n;++j){
		if(erfen[j]==0&&bfs(j,n)){
			panduan=true;      //遍歷每一個連通分支
			break; 
		}
	}
	
	//output()
	if(panduan)   printf("二分圖\n");
	else     printf("非二分圖\n"); 
	
	return 0;
} 

<3>.最大匹配

匹配定義; 令G(V,E) 是一個無向圖,若存在邊集M ,使得M中所有的邊都沒有公共頂點,則稱M是G的一個匹配(matching)。

最大匹配;邊數最多的匹配稱為最大匹配

完美匹配;,如果所有的點都在匹配邊上,稱這個最大匹配是完美匹配

增廣路徑

如果P是圖G中一條連通兩個未匹配頂點的路徑(P以未匹配點為起點和終點的路徑),並且屬M的邊和不屬於M的邊(即已匹配和待匹配的邊)在P上交替出現,則稱P為現對於M的一條增廣路徑

二分圖的最大匹配-增廣路徑

令M是G的一個匹配,則P是M的一條增廣路徑;則是G的一個大小為|M|+1的匹配。





新增的邊或者是M的邊,或者是P的邊,但不是即在M的邊,也在P的邊。

舉例;



邊集;M={(b,c),(e,f),(i,j),(h,l)}  







推出三個結論;

1)P的路徑長度必定為奇數,第一條邊和最後一條邊都不屬於M

2)P經過取反操作可以得到一個更大的匹配M

3) M為G的最大匹配當且僅當不存在相對於M的增廣路徑

<4>.匈牙利演算法(求二分圖的最大匹配)

<匈牙利樹>;一般由 BFS 構造(類似於 BFS 樹)。從一個未匹配點出發運行 BFS(唯一的限制是,必須走交替路),直到不能再擴充套件為止。例如,由圖 1,可以得到如圖 2的一棵 BFS 樹,如果所有的葉子都是匹配點,則這課BFS樹是匈牙利樹 

 

演算法實現

(1). 置M(匹配)為空

(2). 找出一條增廣路徑P,通過取反操作獲得更大的匹配M’代替M

(3). 重複(2)操作直到找不出增廣路徑為止。

>>>細節

對於二分圖A側(可以把點少的一側看作A側)的每個點,依次如下操作:

1.設定A[i]為當前點。

2.用A側和B側的點輪流進行如下操作。

(1)對A側的點A[i]:找到一條沒有匹配的邊。如果邊的B側的點不屬於匹配中另外的邊,那麼這個時候表示增廣路徑找到了(初始情況下直接相連的一條邊的最大匹配就屬於這種情況),修改最大匹配。如果邊的B側的點屬於匹配中另外的邊,設B側的點為B[j],則進入(2)。如果這些都做不到,需要進行回溯。如果回溯到了起點,表明從起點A[i]找增廣路徑失敗了。注意,能否找到增廣路徑全由A側的點決定(找邊和回溯),B側的點做的操作非常簡單。

(2)對B側的點B[j],由於是找增廣路徑,已匹配的邊需要用到,故直接找到B[j]所在的匹配邊在A側對應的點A[k],把A[k]置為當前點繼續進行步驟(1)。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <queue>
#include <vector>
#include <cmath>
#include <set>
#include <deque>
#include <map>
#include <string>

using namespace std;


/*
   匈牙利樹 (BFS構造)

find()    匈牙利演算法實現
main()主函式    基本輸入格式
 */

const int maxn=1010;     //假設頂點數

int erfen[maxn][maxn];
int used[maxn],d[maxn];
int n,m;
bool find(int x)
{
    for(int i=0;i<n;i++)
    {
        if(erfen[x][i]==true&&used[i]==false)
        {
            used[i]=1;    //標記
            if(d[i]==0||find(d[i]))   //遞迴實現
            {
                d[i]=x;
                return true;
            }
        }
    }
    return false;
}

int main()
{
//	freopen("C:\\Users\\24398\\Desktop\\in-project.txt","r",stdin);
    while(scanf("%d",&n)!= EOF)
    {
        int x,y;
        int sum=0;
        memset(d,0, sizeof(d));
        memset(erfen,0, sizeof(erfen));
        for(int i=0;i<n;i++)
        {
            scanf("%d: (%d) ",&x,&m);
            while(m--)
            {
                scanf("%d,",&y);
                erfen[x][y]=1;
            }
        }
        for(int j=0;j<n;j++)
        {
            memset(used,0, sizeof(used));
            if(find(j))
                sum++;
        }
        printf("%d\n",n-sum/2);
    }
    return 0;
}