首页 > 代码库 > graph | hungary

graph | hungary

匈牙利算法,求二分图最大匹配。

若P是图G中一条连通两个未匹配顶点的路径,并且属于M的边和不属于M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径。(M为一个匹配)

由增广路的定义可以推出下述三个结论:

  • P的路径长度必定为奇数,第一条边和最后一条边都不属于M。所以Line 25-27从first part出发,不从二分图的另一部分出发。Line 12实现了交替出现的逻辑;node->neig匹配,当且仅当neig没有被其他点匹配,或者neig被first中的其他点matches[neig]匹配,并且从matches[neig]能够找到一条增广路径。这里就实现了交替的逻辑了。
  • 将M和P进行异或操作(去同存异)可以得到一个更大的匹配M’。这是因为,属于M的边和不属于M的边交替出现,且第一和最后一条边都不属于M,所以增广路径中,不属于M的边比属于M的边多1,去同存异之后,一定会得到一个更大的匹配(加1了)。Line 13实现的是去同存异的逻辑。如果从node到neig存在一条增广路径,那么中间这些相同的部分直接省略。
  • M为G的最大匹配当且仅当不存在M的增广路径。
 1 #include <iostream> 2 #include <cstdio> 3 #include <vector> 4  5 using namespace std; 6  7 bool augment(vector<vector<int> > &adj, int node,  8         vector<bool> &visited, vector<int> &matches) { 9     for (auto neig : adj[node]) {10         if (!visited[neig]) {11             visited[neig] = true;12             if (matches[neig] == -1 || augment(adj, matches[neig], visited, matches)) {13                 matches[neig] = node;14                 return true;15             }16         }17     }18     return false;19 }20 21 int hungary(vector<vector<int> > &adj, vector<int> &first) {22     vector<bool> visited;23     vector<int> matches(adj.size(), -1); 24     int count = 0;25     for (auto f : first) {26         visited.assign(adj.size(), false);27         if (augment(adj, f, visited, matches)) {28             count++;29         }30     }31     for (int i = 0; i < adj.size(); ++i) {32         cout << i << "<->" << matches[i] << endl;33     }34     return count;35 }36 37 int main(int argc, char** argv) {38     freopen("input.txt", "r", stdin);39     int first, n, m;40     cin >> n >> first >> m;41     vector<vector<int> > adj(n);42     vector<int> left;43     for (int i = 0; i < first; ++i) {44         int l;45         cin >> l;46         left.push_back(l);47     }48     for (int i = 0; i < m; ++i) {49         int n1, n2;50         cin >> n1 >> n2;51         adj[n1].push_back(n2);52         adj[n2].push_back(n1);53     }54 55     cout << hungary(adj, left) << endl;56     return 0;57 }

假设左边结点个数为n, 右边结点个数为m,这里用了visited来确保右边的点不会重复访问,时间复杂度还是是O(nmn),如果把邻接表用list而不是vector的话,访问过的右边结点删去,那么开销可以在O(nm)了。因为右边结点只需访问一次。

空间复杂度是O(m+n)。

graph | hungary