算法学习13-搜索与图论04:最小生成树 && 二分图

算法学习13:最小生成树 && 二分图(待完成)



前言

在这里插入图片描述



在这里插入图片描述



在这里插入图片描述


提示:以下是本篇文章正文内容:

一、最小生成树

1. 普利姆算法(Prim)(朴素版)

在这里插入图片描述



给定一个n个点,m条边的“无向图”,可能存在重边和自环,边权可能是负数。
求最小生成树的“树边权重之和”,如果不存在则输出impossible。
1 <= n <= 500, 1 <= m <= 10^5,图中边权的绝对值不超过10000
(稠密图,邻接矩阵)


// 1 <= n <= 500, 1 <= m <= 10^5,
// (稠密图,邻接矩阵)
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, INF = 0x3f3f3f;

int n, m;
int g[N][N];// 邻接矩阵 
int dist[N];// 这个点到集合的距离 
bool st[N];

int prim()
{
	// 设置到集合的距离全部为 正无穷 
	memset(dist, 0x3f3f3f, sizeof dist);
	
	int res = 0;
	for(int i = 0; i < n; i ++)
	{
		int t = -1; 
		// 找到集合外 距离最近 的点 
		for(int j = 1; j <= n; j ++)
			if(!st[j] && (t == -1 || dist[t] > dist[j]))
				t = j;
	// 如果i不为0,且t到集合的距离为+无穷,那么不存在最小生成树 
		if(i && dist[t] == INF) return INF;
		if(i) res += dist[t];
		
		for(int j = 1; j <= n; j ++) 
			dist[j] = min(dist[j], g[t][j]);
		st[t] = true; 
	}
	return res;	
}

int main()
{
	scanf("%d%d", &n, &m);
	memset(g, 0x3f3f3f, sizeof g);
	
	while(m --) 
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		// 无向图,重边,自环处理 
		g[a][b] = g[b][a] = min(g[a][b], c);
	}
	
	int t = prim(); 
	
	if(t == INF) puts("impossible");
	else printf("%d\n", t);
	
	return 0;
 } 

在这里插入图片描述



2. 克鲁斯卡尔算法(Kruskal)

😊要用到的算法:并查集😊


在这里插入图片描述



在这里插入图片描述



/*
给定一个边带权的无向图G=(V, E),其中V表示图中点的集合,E表示图中边的集合,n=|V|, m=|E|。
由V中的全部n个顶点和E中n-1条边构成的无相连通子图被称为G的一颗生成树,其中边的权值之和最小的生成树被称为无向图G的最小生成树。

输入:
第一行包含2个整数n和m
接下来m行,每行包含3个整数u,v,w,表示点u和点v之间存在一条权值为w的边。

输出:若存在最小生成树,输出它的边权重之和,否则输出impossible。

数据范围:
1<= n <= 10^5,  1<= m <= 2*10^5
图中涉及的边权的绝对值,均不超过1000.
*/
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 200010;

int n, m;
int p[N];// 存储每个元素父结点(最上面的) 

// 并查集:返回x的祖宗结点 + 路径优化(递归) 
int find(int x)
{
	if(p[x] != x) p[x] = find(p[x]);
	return p[x];
}

// 按照权重从小到大排序(升序) 
struct Edge
{
	int a, b, w;
	bool operator< (const Edge &W)const
	{
		return w < W.w;
	}
}edges[N];


int main()
{
	scanf("%d%d", &n, &m);
	for(int i = 0; i < m; i ++)
	{
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w);
		edges[i] = {a, b, w};
	}
	
	// 按边的权重从小到大排序: 
	sort(edges, edges + m);
	
	for(int i = 1; i <= n; i ++) p[i] = i;// 
	
	int res = 0, cnt = 0;// res:边权值之和, cnt:加入最小生成树的边的数量 
	for(int i = 0; i < m; i ++)// 遍历所有边 
	{
		int a = edges[i].a, b = edges[i].b, w = edges[i].w;
		
		a = find(a), b = find(b);
		if(a != b)// 如果a,b不属于同一个结合 
		{
			p[a] = b;// 合并 
			res += w;
			cnt ++;
		}
		 
	}
	
	if(cnt < n - 1) puts("impossible");
	else printf("%d\n", res);
	return 0;
}

在这里插入图片描述



二、二分图:不存在“奇数环”(就是这个环有奇数条边)

1. 染色法:

在这里插入图片描述



在这里插入图片描述



/*
给定一个n个点m条边的无向图,图中可能存在重边和自环
请你判断这个图是不是二分图

输入:
第一行包含2个整数n和m
接下来m行,每行包含2个整数u和v,表示点u和点v之间存在一条边

输出:
如果给定的是二分图,输出“Yes”,否则输出“No”

数据范围:1<= n, m <= 10^5
*/
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 100010, M = 200010;

int n, m;
int h[N], e[M], ne[M], idx;
int color[N]; // 染色数组:0未染色,1黑色,2白色 

// 插入边(邻接表) 
void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

// u:当前点,c:颜色 
bool dfs(int u, int c)
{
	color[u] = c;// 染色:相当于“之前”dfs中的状态数组 
	// 遍历邻接顶点 
	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);// 由于是无向图,所以每条边需要插入2次 
	 } 
	 
	 bool flag = true;// 判断标志 
	 for(int i = 1; i <= n; i ++)
	 	if(!color[i])// 未被染色 
	 	{
	 		if(!dfs(i, 1))// dfs + 染色 
	 		{
	 			// 如果返回结果为false 
	 			flag = false;
	 			break;
			 }
		 }
	  
	if(flag) puts("Yes");
	else puts("No");
	return 0;
}

在这里插入图片描述



2. 匈牙利算法:

在这里插入图片描述



在这里插入图片描述



/*
给定一个二分图,其中左半部分包含n1个点,右半部分包含n2个点,二分图共包含m条边。
数据保证任意一条边的两个端点都不可能在同一部分。
请你求出二分图的最大匹配数。
给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意2条边都不依附于同一个顶点,则称M是一个匹配。(人话就是:左右配对,而且只能配对一次)
多有匹配中包含边数最多的一组匹配是二分图的最大匹配,其边数为最大匹配数。

输入:
第一行包含3个整数n1、n2、m
接下来m行,每行包含2个整数u和v,表示左半部点集中的点u和右半部点集中的点v之间存在一条边

输出:二分图的最大匹配数

数据范围:
1<= n1, n2 <= 500  
1<= u <= n1  1<= v <= n2
1 <= m <= 10^5
*/
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 510, M = 100010;

int n1, n2, m;
int h[N], e[M], ne[M], 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 -1);// 将邻接表头指针置为-1(null) 
	while(m --)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b);
	 } 
	 
	 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;
}

在这里插入图片描述


总结

提示:这里对文章进行总结:

💕💕💕

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lennard-lhz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值