二分图问题:染色法、匈牙利算法(用C/C++实现)

二分图:可以将图中所有点划分到两个集合中,边只存在集合之间,集合内无边。

一、如何判别是否是二分图  染色法(O(n + m))

一个图是二分图,当且仅当图中不含奇数环(奇数环:环当中的点为基数)。如果它是一个二分图,那么相连的两个点一定属于两个不同集合,可以看作一个染色问题,由于图中不含奇数环,所以染色过程一定不存在矛盾。

for(i = 1; i <= n; i++)

   if i未染色

          dfs(i, 1); //用深度优先遍历把i所在的连通块都染上

#include<iostream> 
#include<algorithm>
#include<cstring>
/*给定一个n个点m条边的无向图,图中可能存在重边和自环
判断是否是二分图*/
using namespace std;
//无向图,要存两条边,比给定的多一倍 
const int N = 100010, M = 200010; 

int n, m;
int h[N], e[M], ne[M], idx;//用邻接表存储图
int color[N];
void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
 } 
 //把u节点染成c颜色,并染u节点的所有连通块 
 bool dfs(int u, int c)
 {
 	color[u] = c;
 	
 	for(int i = h[u]; i != -1; i = ne[i])
 	{
 		int j = e[i];
 		//如果当前节点没染色,那就染成另一种颜色 
 		if(!color[j])
		{
			if(!dfs(j, 3 - c)) return false;
		} 
		else if (color[j] == c)//染色但染了同样的颜色,即冲突
			return false; 
	}
	return true;
 }
int main()
{
	scanf("%d%d", &n, &m);
	
	memset(h, -1, sizeof h);
	
	while(m --)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		
		add(a, b), add(b,a); 
	}
	bool flag = true;
	for(int i = 1; i <= n; i ++)
	{
		if(!color[i])
		{
			if(!dfs(i, 1))
			{
				flag = false;
				break;
			}
		}
	}
	if(flag) puts("Yes");
	else puts("No");
	return 0; 
} 
 

二、求二分图的最大匹配  匈牙利算法(实际运行时间一般远小于理论值O(mn))

这个算法能得出二分图两边成功匹配的最多的边的个数,成功匹配是指,没有两条边共用一个点。

算法思路:

将二分图的点分进两个集合,分别为集合1和集合2。首先遍历集合1中的点,对于每个点,如果有出边,遍历它的出边所连的点,会有两种情况:

1. 所连的点没有与之配对的点,那么直接将两点配对;

2. 所连的点已经有与之配对的点,这时要去找到与之配对的点,重新遍历那个点的出边,寻找是否可以让它与没有配对的其他点配对(递归)。

#include<iostream>
#include<algorithm>
#include<cstring>
/*给定一个二分图,其中左半部分包含n1个点,右半部分包含n2个点,共m条边
请求最大匹配数*/
using namespace std;

const int N = 510, M = 100010;
int n1, n2, m;
int h[N], e[N], ne[N], idx;
int match[N];
bool st[N];
void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
 } 
bool find(int x)
{
	for(int i = h[x]; i != -1; i = ne[i])
	{
		int j = e[i];
		if(!st[j])
		{
			st[j] = true;
			if(match[j] == 0 || find(match[j]))
			{
				match[j] = x;
				return true;
			}
		}
	}
	return false;
}
int main()
{
	scanf("%d%d%d", &n1, &n2, &m);
	
	memset(h, -1, sizeof h);
	
	while(m --)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b);//遍历集合1的点,只需要存1指向2的即可
		 
	}
	
	int res = 0;
	for(int i = 1; i <= n1; i ++)
	{
		memset(st, false, sizeof st);//初始化每个点为未匹配
		if(find(i)) res++; 
	}
	
	printf("%d\n", res);
	return 0;
}

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
图着色问题是指给定一个无向图,尝试用最少的颜色对每个顶点进行染色,使得任意两个相邻的顶点颜色不同。这是一个经典的NP难问题,没有有效的多项式时间算法,但是可以使用贪心算法来近似解决。 贪心算法的基本思路是:首先按照某种规则选择一个顶点进行染色,然后依次选择其它未染色的顶点,并尝试用最少的颜色进行染色。如果当前顶点的相邻顶点都已经染过色,那么就选择一个未使用的颜色进行染色,否则选择一个不与相邻顶点颜色相同的颜色进行染色。 C++代码实现如下: ```c++ #include <iostream> #include <vector> #include <algorithm> using namespace std; const int MAXN = 100; // 最大顶点数 int n, m; // n 表示顶点数,m 表示边数 vector<int> G[MAXN]; // 存储图的邻接表 int color[MAXN]; // 存储每个顶点的颜色 int greedy_coloring() { int ans = 0; for (int u = 0; u < n; ++u) { bool used[MAXN] = { false }; for (int i = 0; i < G[u].size(); ++i) { int v = G[u][i]; if (color[v] != -1) { used[color[v]] = true; } } for (int i = 0; ; ++i) { if (!used[i]) { color[u] = i; ans = max(ans, i); break; } } } return ans + 1; // 返回使用的颜色数 } int main() { cin >> n >> m; for (int i = 0; i < m; ++i) { int u, v; cin >> u >> v; G[u].push_back(v); G[v].push_back(u); } fill(color, color + n, -1); cout << "使用的颜色数:" << greedy_coloring() << endl; for (int i = 0; i < n; ++i) { cout << "顶点 " << i << " 的颜色是 " << color[i] << endl; } return 0; } ``` 该代码中,我们使用邻接表存储图,并使用一个数组 `color` 存储每个顶点的颜色,初始化为 -1 表示未染色。在 `greedy_coloring` 函数中,我们依次遍历每个顶点,对于每个未染色的顶点,我们枚举可用的颜色,选取第一个未被使用的颜色进行染色。在枚举颜色的过程中,我们需要检查相邻顶点的颜色,标记已经使用的颜色。 该算法的时间复杂度为 $O(n^2)$,因为对于每个顶点,都需要遍历其相邻顶点。当然,使用邻接表存储图可以优化到 $O(m)$ 的时间复杂度。该算法是一种近似算法,不一定能够得到最优解,但是在实际应用中效果很好。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值