匈牙利算法 (poj1422-Air Raid,poj1469-COURSES)

首先注意区分“最小路径覆盖”(minimum path cover)和“最小边覆盖”(minimum edge cover)以及“最小点覆盖”(minimum vertex cover)之间的区别。详细资料可以查询Wiki。

最小路径覆盖可以转化为二分图的最大匹配(maximum bipartite matching) 。公式为最小路径覆盖数=原图节点数-二分图最大匹配数。

求最大匹配的方法有两种:一是将二分图增添两个节点(source起点和sink终点)构造一个流网络(flow network),然后就可以按求最大流的算法来计算,基本的算法有Ford-Fulkerson算法。不过这样实现起来比较麻烦,下面着重介绍第二种方法:匈牙利算法(Hungarian algorithm )

 

匈牙利算法其实和基于增广路径(augmenting path)的最大流算法是比较相似的。不同点在于:

  一,匈牙利算法不需要构造流网络,也就是说不需要增添节点,并且二分图是无向图,不需要转换为流网络中的有向图。

  二,匈牙利算法的增广路径(设为P)有附加条件:P的第一条边必须是尚未匹配的边,第二条边是已经匹配的边,如此未匹配和已匹配的边交替出现,最后一条边仍然是未匹配边(注意P可以是只有一条未匹配边的路径)。根据增广路径的附加条件可以看出一个特点:P的未匹配边一定比已匹配边多一条。

  三,匈牙利算法依次对二分图的半边(左半边或者右半边,条件是节点数少的一边)的每个节点开始找增广路径,如果找到则取反:未匹配的边变成匹配边,已匹配边去掉匹配关系。这样的结果是路径P的匹配边数量增加一条,未匹配边数量减少一条。寻找增广路径的方法为DFS或者BFS递归。

  四,当二分图中不存在增广路径时即产生最大匹配数。

匈牙利算法的实质就是找增广路径,并对增广路径做取反操作,直到没有增广路径为止。

 

匈牙利算法的基本流程:

初始时最大匹配为空
  
for  二分图左半边的每个点i
      
do  从点i出发寻找增广路径。如果找到,则把它取反(即增加了总了匹配数)。


下面是poj1422匈牙利算法的实现:

ContractedBlock.gif ExpandedBlockStart.gif Code
 1 //匈牙利算法实现
 2 #include <iostream>
 3 #include <string>
 4 #define SIZE 121
 5 using namespace std;
 6 
 7 int vtx, eg;
 8 int match[SIZE];        //存储与二分图右边集合的节点相匹配的左集合节点的索引
 9 bool visited[SIZE];        //右边集合相应节点是否被访问过
10 bool map[SIZE][SIZE];    //图的邻接矩阵
11 
12 //DFS搜索是否有增广路径
13 bool dfs(int left)
14 {
15     int t, i;
16     for (i=1;i<=vtx;i++){
17         if (map[left][i] && !visited[i]){
18             visited[i]=true;
19             t=match[i];            //将match[i]临时保存
20             match[i]=left;        //路径取反操作
21             if (t==-1 || dfs(t)){//寻找是否为增广路径
22                 return true;
23             }
24             match[i]=t;            //如果不是增广路径,路径恢复原值
25         }
26     }
27     return false;
28 }
29 
30 int main()
31 {
32     int test, u, v, i, sum;
33     freopen("input.txt""r", stdin);
34     cin >> test;
35     while (test--){
36         cin >> vtx >> eg;
37         memset(map, 0sizeof(map));
38         memset(match, -1sizeof(match));
39         sum=0;
40         for (i=0;i<eg;i++){
41             cin >> u >> v;
42             map[u][v]=true;
43         }
44 
45         for (i=1;i<=vtx;i++){
46             memset(visited, 0sizeof(visited));
47             if (dfs(i)){
48                 sum++;
49             }
50         }
51         cout << vtx-sum << endl;
52     }
53     return 0;
54 }


poj1469匈牙利算法:

ContractedBlock.gif ExpandedBlockStart.gif Code
#include <iostream>
#define cNUM 101
#define sNUM 301
using namespace std;

int P, N;
int map[cNUM][sNUM];
int match[sNUM];
int vsted[sNUM];

bool Hungarian(int left)
{
    
int i, t;
    
for (i=1;i<=sNUM;i++){
        
if (map[left][i] && !vsted[i]){
            vsted[i]
=1;
            t
=match[i];
            match[i]
=left;
            
if (t==-1 || Hungarian(t)){
                
return true;
            }
            match[i]
=t;
        }
    }
    
return false;
}

int main()
{
    
int test, cnt, i, j, st, sum;
    
//freopen("input.txt", "r", stdin);
    scanf("%d"&test);
    
while (test--){
        memset(match, 
-1sizeof(match));
        memset(map, 
0sizeof(map));
        scanf(
"%d %d"&P, &N);
        
for (i=1;i<=P;i++){
            scanf(
"%d"&cnt);
            
for (j=1;j<=cnt;j++){
                scanf(
"%d"&st);
                map[i][st]
=1;
            }
        }
        
//Hungarian algorithm
        sum=0;
        
for (i=1;i<=cNUM;i++){
            memset(vsted, 
0sizeof(vsted));
            
if (Hungarian(i))
                sum
++;
        }
        
if (sum==(P<N?P:N)){
            printf(
"YES\n");
        }
        
else{
            printf(
"NO\n");
        }
    }
    
return 0;
}

参考:用匈牙利算法求二分图的最大匹配

   http://www.matrix67.com/blog/archives/39

   http://blog.163.com/baobao_zhang@126/blog/static/48252367200862682748461/

     http://old.blog.edu.cn/user3/Hailer/archives/2007/1829623.shtml

 


PS:很神奇的一件事,Air Raid这题用EK最大流算法在zoj上WA,在poj上居然AC了。。。以下是poj1422EK算法的实现:

ContractedBlock.gif ExpandedBlockStart.gif Code
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #define GSIZE 251
  5 
  6 typedef struct{
  7     int queue[GSIZE];
  8     int head;
  9     int tail;
 10 }QUEUE;
 11 
 12 int vtx, eg;
 13 QUEUE q;
 14 int color[GSIZE], par[GSIZE];
 15 int resNet[GSIZE][GSIZE];    //模拟链表:y轴为节点索引,每一行存放此节点指向的节点索引,
 16                             //若为-1则说明指向的节点为空
 17 int InitQueue(){
 18     q.head=q.tail=0;
 19     return 1;
 20 }
 21 int EnQueue(int x){
 22     q.queue[q.tail]=x;
 23     q.tail++;
 24     return 1;
 25 }
 26 int DeQueue(){
 27     int x;
 28     x=q.queue[q.head];
 29     q.head++;
 30     return x;
 31 }
 32 
 33 int BFSFindPath()
 34 {
 35     int flag, i, u, v;
 36     flag=0;
 37     InitQueue();
 38     for (i=1;i<=vtx;i++){
 39         color[i]=color[i+120]=0;
 40         par[i]=par[i+120]=-1;
 41     }
 42     color[250]=0;
 43     par[250]=-1;
 44     color[0]=1;
 45     par[0]=-1;
 46     EnQueue(0);
 47     while (q.head!=q.tail && flag==0){
 48         u=DeQueue();
 49         i=0;
 50         while (resNet[u][i]!=-1 && resNet[u][i]!=0 && i<GSIZE){
 51             v=resNet[u][i];
 52             if (color[v]==0){
 53                 color[v]=1;
 54                 par[v]=u;
 55                 EnQueue(v);
 56             }
 57             if (v==250){//当搜索到一条最短路径后停止
 58                 flag=1;
 59                 break;
 60             }
 61             i++;    
 62         }
 63         color[u]=2;
 64     }
 65     return flag;    
 66 }
 67 int Ford_Fulkerson()
 68 {
 69     int pathNum, v, i, del;
 70     pathNum=0;
 71     while (BFSFindPath()){
 72         v=250;
 73         //par[v]---->v修改为v--->par[v]
 74         while (par[v]!=-1){
 75             i=0;
 76             while (resNet[v][i]!=-1 && i<GSIZE){
 77                 i++;
 78             }
 79             resNet[v][i]=par[v];
 80             i=0;
 81             while (resNet[par[v]][i]!=-1 && i<GSIZE){
 82                 if (resNet[par[v]][i]==v){
 83                     del=i;
 84                 }
 85                 i++;
 86             }
 87             resNet[par[v]][del]=resNet[par[v]][i-1];
 88             resNet[par[v]][i-1]=-1;
 89 
 90             v=par[v];
 91         }
 92         pathNum++;    
 93     }
 94     return pathNum;
 95 }
 96 int main()
 97 {    
 98     int c, i, st, end, cnt, ans;
 99     //freopen("input.txt", "r", stdin);
100     scanf("%d"&c);
101     while (c--){
102         memset(resNet, -1sizeof(resNet));
103         scanf("%d %d"&vtx, &eg);
104         //设起点下标0,终点下标250,构造剩余网络
105         for (i=1;i<=vtx;i++){
106             resNet[0][i-1]=i;
107             resNet[i+120][0]=250;
108         }
109         for (i=1;i<=eg;i++){
110             cnt=0;
111             scanf("%d %d"&st, &end);
112             while (resNet[st][cnt]!=-1){ 
113                 cnt++;
114             }
115             resNet[st][cnt]=end+120;
116         }
117         if (eg==0){
118             ans=vtx;
119         }
120         else{
121             ans=vtx-Ford_Fulkerson();
122         }
123         printf("%d\n", ans);
124     }
125     return 0;
126 }

 

 

转载于:https://www.cnblogs.com/zen_chou/archive/2009/07/13/1522246.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值