Maximum Bipartite Matching

来源:互联网 发布:安卓慢快门摄影软件 编辑:程序博客网 时间:2024/06/07 13:27

        算法旨在用尽可能简单的思路解决问题,理解算法也应该是一个越看越简单的过程,当你看到算法里的一串概念,或者一大坨代码,第一感觉是复杂,此时不妨从例子入手,通过一个简单的例子,并编程实现,这个过程其实就可以理解清楚算法里的最重要的思想,之后扩展,对算法的引理或者更复杂的情况,对算法进行改进。最后,再考虑时间和空间复杂度的问题。

       了解这个算法是源于在Network Alignment问题中,图论算法用得比较多,而对于alignment,特别是pairwise alignment, 又经常遇到maximum bipartite matching问题,解决这个问题,是通过Network Flow问题的解法来实现。


一、Network Flow

    Network Flow,指的是在从source 到 destination的路径组成一个network, 每条边有一个capacity, 表示从这条边上能通过的最大信息流,而Network Flow问题则要找出从源到目的地能通过的最大流, Maximum Flow. 信息在流动的过程中需要遵循两个原则;

    1. 对于每个节点,流入和流出的信息必须相等。

     2.流过每条边的信息不能超过边上的capacity.

      最大流问题和minimum cut是等价的,找最大流也就是找minimum cut,minimum cut是如下定义的:

     我们要在Network上删除一些边,删除掉这些边后,从source 就没有路径到目的地了,我们要找到尽可能少的边,来达到这个目的,这就是minimum cut。


二、 Ford-Fulkerson算法

      第一遍读这个算法的时候,不懂,现在读这个算法,觉得很清晰,现在把算法的思路复述一遍,不知道第一次读的人会不会觉得容易理解:

      1、 构建Residual graph:由于在原network上已经有了capacity, 现在给定这个网络一个流flow的值, 例如边是(u,v)我们计算capacity-f, 同时我们也计算(v,u),值为f(因为capacity为0),

              如果一条边的这个值为正,则保留,否则删除。

       2、augmenting path: 通过1得到的就是Residual graph,这个graph上的从source到destination的所有路径都叫做augmenting path.

       3、针对每条augmenting path: 改变path上所有边的capacity,改变规则如下(以(u,v)为例):

                       找到这条path上的最小的capacity, f,

                       减少u->v的capacity, 增加v->u的capacity.

        算法的时间复杂度 O(m+n)f),f是max-flow.

代码:

// C++ program for implementation of Ford Fulkerson algorithm#include <iostream>#include <limits.h>#include <string.h>#include <queue>using namespace std;// Number of vertices in given graph#define V 6/* Returns true if there is a path from source 's' to sink 't' in  residual graph. Also fills parent[] to store the path */bool bfs(int rGraph[V][V], int s, int t, int parent[]){    // Create a visited array and mark all vertices as not visited    bool visited[V];    memset(visited, 0, sizeof(visited));    // Create a queue, enqueue source vertex and mark source vertex    // as visited    queue <int> q;    q.push(s);    visited[s] = true;    parent[s] = -1;    // Standard BFS Loop    while (!q.empty())    {        int u = q.front();        q.pop();        for (int v=0; v<V; v++)        {            if (visited[v]==false && rGraph[u][v] > 0)            {                q.push(v);                parent[v] = u;                visited[v] = true;            }        }    }    // If we reached sink in BFS starting from source, then return    // true, else false    return (visited[t] == true);}// Returns tne maximum flow from s to t in the given graphint fordFulkerson(int graph[V][V], int s, int t){    int u, v;    // Create a residual graph and fill the residual graph with    // given capacities in the original graph as residual capacities    // in residual graph    int rGraph[V][V]; // Residual graph where rGraph[i][j] indicates                     // residual capacity of edge from i to j (if there                     // is an edge. If rGraph[i][j] is 0, then there is not)    for (u = 0; u < V; u++)        for (v = 0; v < V; v++)             rGraph[u][v] = graph[u][v];    int parent[V];  // This array is filled by BFS and to store path    int max_flow = 0;  // There is no flow initially    // Augment the flow while tere is path from source to sink    while (bfs(rGraph, s, t, parent))    {        // Find minimum residual capacity of the edhes along the        // path filled by BFS. Or we can say find the maximum flow        // through the path found.        int path_flow = INT_MAX;        for (v=t; v!=s; v=parent[v])        {            u = parent[v];            path_flow = min(path_flow, rGraph[u][v]);        }        // update residual capacities of the edges and reverse edges        // along the path        for (v=t; v != s; v=parent[v])        {            u = parent[v];            rGraph[u][v] -= path_flow;            rGraph[v][u] += path_flow;        }        // Add path flow to overall flow        max_flow += path_flow;    }    // Return the overall flow    return max_flow;}// Driver program to test above functionsint main(){    // Let us create a graph shown in the above example    int graph[V][V] = { {0, 16, 13, 0, 0, 0},                        {0, 0, 10, 12, 0, 0},                        {0, 4, 0, 0, 14, 0},                        {0, 0, 9, 0, 0, 20},                        {0, 0, 0, 7, 0, 4},                        {0, 0, 0, 0, 0, 0}                      };    cout << "The maximum possible flow is " << fordFulkerson(graph, 0, 5);    return 0;}

三、Maximum Bipartite Matching

      解决这个问题就很简单了,我们先添加上源和目的地节点,假设是任务分配问题,则源可以有边指向所有人,所有任务有边可以指向目的地,我们要找的是人和任务之间的最优匹配。

代码:

// A C++ program to find maximal Bipartite matching.#include <iostream>#include <string.h>using namespace std;// M is number of applicants and N is number of jobs#define M 6#define N 6// A DFS based recursive function that returns true if a// matching for vertex u is possiblebool bpm(bool bpGraph[M][N], int u, bool seen[], int matchR[]){    // Try every job one by one    for (int v = 0; v < N; v++)    {        // If applicant u is interested in job v and v is        // not visited        if (bpGraph[u][v] && !seen[v])        {            seen[v] = true; // Mark v as visited            // If job 'v' is not assigned to an applicant OR            // previously assigned applicant for job v (which is matchR[v])            // has an alternate job available.            // Since v is marked as visited in the above line, matchR[v]            // in the following recursive call will not get job 'v' again            if (matchR[v] < 0 || bpm(bpGraph, matchR[v], seen, matchR))            {                matchR[v] = u;                return true;            }        }    }    return false;}// Returns maximum number of matching from M to Nint maxBPM(bool bpGraph[M][N]){    // An array to keep track of the applicants assigned to    // jobs. The value of matchR[i] is the applicant number    // assigned to job i, the value -1 indicates nobody is    // assigned.    int matchR[N];    // Initially all jobs are available    memset(matchR, -1, sizeof(matchR));    int result = 0; // Count of jobs assigned to applicants    for (int u = 0; u < M; u++)    {        // Mark all jobs as not seen for next applicant.        bool seen[N];        memset(seen, 0, sizeof(seen));        // Find if the applicant 'u' can get a job        if (bpm(bpGraph, u, seen, matchR))            result++;    }    return result;}// Driver program to test above functionsint main(){    // Let us create a bpGraph shown in the above example    bool bpGraph[M][N] = {  {0, 1, 1, 0, 0, 0},                        {1, 0, 0, 1, 0, 0},                        {0, 0, 1, 0, 0, 0},                        {0, 0, 1, 1, 0, 0},                        {0, 0, 0, 0, 0, 0},                        {0, 0, 0, 0, 0, 1}                      };    cout << "Maximum number of applicants that can get job is "         << maxBPM(bpGraph);    return 0;}

四、对于任务分配问题,还有Hungrian算法,这个后面再讲,此算法的时间复杂度和空间复杂度以及改进也可以探讨


 

0 0
原创粉丝点击