圖論算法》關於匈牙利算法的兩三事


  這是一篇簡單的匈牙利算法的理解篇,首先匈牙利算法的名字聽起來就和匈牙利牛肉飯一樣讓人產生食欲(?)233。

  好的接下來我們開始正式帶大家了解什么叫匈牙利算法。

  那么要了解算法的基本原理,我們先看一張圖

在這張圖里,我們可以清楚的看出圖上的點被我們分成了兩種,一種是數字,另一種是字母,並且數字與數字、字母與字母之間是沒有互相連接的邊的,這種點被划為兩種、且每種之間沒有連邊的圖就叫做二分圖,而我們的匈牙利算法就是用來處理二分圖匹配的。

  什么叫二分圖匹配捏,很簡單,我們從這張圖中選出一些邊,舉個例子,我們選出1——C,2——A,4——D這三條邊,使得每條邊對應左右兩邊各一個點,且對於每個點來說,邊唯一。

  而對於匈牙利算法來說,它求的是二分圖最大匹配,什么叫最大匹配捏,就是說在二分圖中選擇盡可能多的邊,使它們遵從二分圖匹配的規則,如果我們選擇的邊數為全圖點數除以2的話,則叫做完全匹配。

  那么匈牙利算法具體怎么實現捏?具體如以下圖示

 

首先我們找到左邊的1,從1出發選擇1能連到的第一條邊為1——C(上圖中標紅的邊)

具體為我們從左邊第一個沒有連到邊的點(如原始圖所示為1)開始搜索,找到第一個可以連到的點C,然后選擇連接兩個點,記錄連接點C的是1。

接下來找到第二個點2,同樣連接 2 和 與2連接的第一個沒有被連接過的點(如上圖所示為A),記錄連接點A的是2。

  重點!!!!!

  接下來就是匈牙利算法的精髓所在,因為從3開始連接的時候我們就開始慌了,唯一和3連接的點A已經被連過了,那么接下來就是展現匈牙利算法的威力的時刻啦哈哈哈哈哈哈哈(蜜汁笑聲)

  那么我們要做的是用深度優先搜索找出首先找出與當前失匹配點3所能聯通的點(上圖青色邊為我們深搜的過程路徑),那么當前所找到的就為A,因為A已經被連接,所以我們可以直接找到連接A的是2,然后繼續深搜。

  然后繼續從2開始搜索,找一條能與2連接,並且不是當前來的路徑(2——A)的另一條路徑(根據匈牙利算法,如果這里找不到就返回上一層,知道搜索完深度優先搜索開始節點3的每一條邊,如果都找不到就判斷這次算法執行沒有更優結果),那么顯然的,我們找到了C這個點。

同樣的,我們之前記錄過連接C的節點(也就是1),那么繼續以1開始搜索與1連接,並且不是1——C這條路徑的另一條路徑,

   顯然的,找到1——E這條路徑,然后對點E進行判斷。

  這時我們發現E是沒有其他路徑與其他點連接的,那么這時我們開始return,那么看,此時圖中所有標為青色的邊是我們的搜索路徑,那么我們在return時要做的事情就是:把所有{只標青而沒有變紅的邊}變為新的連接邊,而吧所有{又標紅又標青的邊}取消連接.

  那么這時圖就變成

  那么對於當前情況,我們就相當於完成了局部最優匹配,然后接下來就是繼續按照這種方法完成全圖最優匹配就好了。

   那么完成圖就為

這時我們就求出了二分圖最大匹配

(PS寫在最后,我們是要記錄:{每次連接邊后,成功連接右邊某個點的左邊點的位置},同時如果我們做的找到環的話就只能回到上一層,所以要記錄{哪些點在當前搜索中用到(記錄一個vis數組)},這樣下次搜索就不會死循環)

 然后附上匈牙利算法的模板

 1 bool f(int x)
 2 {
 3     for(int i=head[x];i!=0;i=e[i].next)
 4     {
 5         if(v[i])continue;
 6         v[i]=true;
 7         if(!father[e[i].aim]||f(father[e[i].aim]))
 8         {
 9             father[e[i].aim]=x;
10             return true;
11         }
12     }
13     return false;
14 }

上述模板中判斷目標節點是否有父親和判斷目標節點的父親能否有更優解同時進行。

附上一道大水題 bzoj1191(話說bzoj里有這么水的題真不容易)

題目如下

Description

現在電視台有一種節目叫做超級英雄,大概的流程就是每位選手到台上回答主持人的幾個問題,然后根據回答問題的多少獲得不同數目的獎品或獎金。主持人問題准備了若干道題目,只有當選手正確回答一道題后,才能進入下一題,否則就被淘汰。為了增加節目的趣味性並適當降低難度,主持人總提供給選手幾個“錦囊妙計”,比如求助現場觀眾,或者去掉若干個錯誤答案(選擇題)等等。 這里,我們把規則稍微改變一下。假設主持人總共有m道題,選手有n種不同的“錦囊妙計”。主持人規定,每道題都可以從兩種“錦囊妙計”中選擇一種,而每種“錦囊妙計”只能用一次。我們又假設一道題使用了它允許的錦囊妙計后,就一定能正確回答,順利進入下一題。現在我來到了節目現場,可是我實在是太笨了,以至於一道題也不會做,每道題只好借助使用“錦囊妙計”來通過。如果我事先就知道了每道題能夠使用哪兩種“錦囊妙計”,那么你能告訴我怎樣選擇才能通過最多的題數嗎?

Input

輸入文件的一行是兩個正整數n和m(0 < n <1001,0 < m < 1001)表示總共有n中“錦囊妙計”,編號為0~n-1,總共有m個問題。
以下的m行,每行兩個數,分別表示第m個問題可以使用的“錦囊妙計”的編號。
注意,每種編號的“錦囊妙計”只能使用一次,同一個問題的兩個“錦囊妙計”可能一樣。

Output

第一行為最多能通過的題數p

Sample Input

5 6
3 2
2 0
0 3
0 4
3 2
3 2

Sample Output

4

 沒什么好講的,最裸的匈牙利,愉快的貼出代碼

 1 #include<cstdio>
 2 #include<cstring>
 3 struct shit{int aim,next;}e[2200];
 4 int father[2200],a,b,n,m,point,head[2200];
 5 bool v[2200];
 6 void  fuck(int x,int y)
 7 {
 8     e[++point].aim=y;
 9     e[point].next=head[x];
10     head[x]=point;
11 }
12 bool f(int x)
13 {
14     for(int i=head[x];i!=0;i=e[i].next)
15     {
16         if(v[i])continue;
17         v[i]=true;
18         if(!father[e[i].aim]||f(father[e[i].aim]))
19         {
20             father[e[i].aim]=x;
21             return true;
22         }
23     }
24     return false ;
25     
26 }
27 int main()
28 {
29     scanf("%d%d",&n,&m);
30     for(int i=1;i<=m;i++)
31     {
32         scanf("%d%d",&a,&b);
33         fuck(i,a),fuck(i,b);
34     }
35     int k;
36     for(k=1;k<=m;k++)
37     {
38         memset(v,0,sizeof(v));
39         if(!f(k))break;
40     }
41     printf("%d",k-1);
42     return 0;
43 }
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM