最小生成树模板及其dfs总结 (kruskal prim)

本文介绍了最小生成树的克鲁斯卡尔和普利姆算法,并提供了DFS总结。通过CF472D题目实例,阐述了如何利用最小生成树判断图是否为树的条件,包括特判和邻接矩阵对称性的检查。通过DFS实现邻接矩阵的验证,以判断是否存在偏差导致非树结构。

克鲁斯卡尔
利用并查集,将排好序的每条边,如果不存在于并查集中就依次插入
上模板,方便查阅

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 2e5 + 10;
#define inf 0x3f3f3f3f
int fa[N];
int n, m;
struct edge
{
    int u, v, w;
    bool operator < (edge b) { return w < b.w; }
}edges[N];

int find(int x)
{
    if (x == fa[x])
        return x;
    return fa[x] = find(fa[x]);
}
int kruskal()
{
    int ans = 0, sum = 0;
    sort(edges + 1, edges + 1 + m);
    for (int i = 1; i <= m; i++)
    {
        int u = edges[i].u, v = edges[i].v, w = edges[i].w;
        int fx = find(u), fy = find(v);
        if (fx != fy)
        {
            fa[fx] = fy;
            ans += w;
            sum++;
        }
        if (sum == n - 1)return ans;
    }
    return -1;
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)//并查集初始化
        fa[i] = i;
    for (int i = 1; i <= m; i++)
    {
        int u, v, w;
        scanf_s("%d%d%d", &u, &v, &w);
        edges[i] = { u,v,w };
    }
    int ans = kruskal();
    if (ans == -1)
        cout << "impossible" << endl;
    else
        cout << ans << endl;
    return 0;
}

普利姆
从开头不停寻找最短边,更新点集

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
#define N 505
int n,m;
int mp[N][N];
int dis[N],vis[N];
int prim()
{
    memset(dis,inf,sizeof dis);
    int ans=0;
    memset(vis,0,sizeof vis);
    dis[1]=0;
    for(int k=0;k<n;k++)
    {
        int mint=inf,v;
        for(int i=1;i<=n;i++)//寻找下一个最外部接口节点
        {
            if((mint>dis[i]) && !vis[i])
            {
                mint=dis[i];v=i;
            }
        }
        if(mint==inf)
            return inf;
        vis[v]=1;
        ans+=mint;
        for(int i=1;i<=n;i++)//更新每个节点到外部接口节点的距离,为下次插点做准备
            dis[i]=min(dis[i],mp[v][i]);
    }
    return ans;
}
int main()
{
    cin>>n>>m;
    memset(mp,inf,sizeof mp);
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        mp[a][b]=mp[b][a]=min(mp[a][b],c);
    }
    int ans=prim();
    if(ans==inf)
        cout<<"impossible"<<endl;
    else
        cout<<ans<<endl;
    return 0;
}

最小生成树内进行dfs,以克鲁斯卡尔最小生成为例
dfs总结,拿题来说话吧 CF472D
传送门
在这里插入图片描述题意:给一个n*n的邻接矩阵,要求判断原图是不是一棵树

首先拿到题,先做的就是一系列的特判,不满足直接NO
1、对角线必须都为0
2、非对角线必须非0
3、邻接矩阵必须按对角线对称

然后还可能会存在一些不满足的情况,比如样例里的1 1 1会出现环这些等等情况,可以使用最小生成树,用dfs表示出它的邻接数组,与所给数组对比,有偏差则就是NO,剩下便为YES,编码过程要用到dfs,理解了我好一段时间,很神奇的算法

上代码

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#include<fstream>
#include<set>
#include<map>
#include<sstream>
#include<iomanip>
#include<deque>
const int N = 2005;
using namespace std;
int n;
int a[2005][2005];
int dis[2005][2005];
int fa[N];
int tot = 0;
struct bian
{

	int x, y, z;
}b[2000010];
bool operator <(bian a, bian b)
{
	return a.z < b.z;
}
struct edge
{

	int to, next, v;
}e[10 * N]; int head[N];
int find(int x)
{
	if (fa[x] != x)return fa[x] = find(fa[x]);
	else return x;
}
int num = 0;
void ins(int u, int v, int w)
{
	e[++num].to = v;
	e[num].v = w;
	e[num].next = head[u];
	head[u] = num;
}
void insert(int u, int v, int w)
{
	ins(u, v, w);
	ins(v, u, w);
}
bool vis[N];
int top, zhan[N];
void kruskal()
{
	for (int i = 1; i <= n; i++)
	{

		fa[i] = i;
	}
	for (int i = 1; i <= n; i++)
	{

		for (int j = 1; j <= n; j++)
		{

			if (i < j)
			{

				b[++tot].x = i;
				b[tot].y = j;
				b[tot].z = a[i][j];
			}
		}
	}
	sort(b + 1, b + 1 + tot);
	int cnt = 0;
	for (int i = 1; i <= tot; i++)
	{

		int fx = find(b[i].x);
		int fy = find(b[i].y);
		if (fx == fy)continue;
		else fa[fx] = fy;
		cnt++;
		insert(b[i].x, b[i].y, b[i].z);
		if (cnt == n - 1)return;
	}
}
void dfs(int cur)
{
	for (int i = head[cur]; i; i = e[i].next)
	{

		if (vis[e[i].to])continue;
		for (int j = 1; j <= top; j++)
		{

			dis[e[i].to][zhan[j]] = dis[zhan[j]][e[i].to] = dis[zhan[j]][cur] + e[i].v;
		}
		zhan[++top] = e[i].to;
		vis[e[i].to] = 1;
		dfs(e[i].to);
	}
}
int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{

		for (int j = 1; j <= n; j++)
		{

			scanf("%d", &a[i][j]);

		}
	}
	for (int i = 1; i <= n; i++)
	{

		for (int j = 1; j <= n; j++)
		{

			if (i == j)
			{
				if (a[i][j] != 0)
				{

					puts("NO");
					return 0;
				}

			}
			else
			{

				if (a[i][j] == 0)
				{

					puts("NO");
					return 0;
				}
			}
		}
	}
	int x = (n % 2 == 1 ? (n / 2 + 1) : (n / 2));
	for (int i = 1; i <= x; i++)
	{

		for (int j = x + 1; j <= n; j++)
		{

			if (a[i][j] != a[j][i])
			{

				puts("NO");
				return 0;
			}
		}
	}
	kruskal();
	zhan[1] = 1;
	top = 1;
	vis[1] = 1;
	dfs(1);
	for (int i = 1; i <= n; i++)
	{

		for (int j = 1; j <= n; j++)
		{

			if (dis[i][j] != a[i][j])
			{

				puts("NO");
				return 0;
			}
		}
	}
	puts("YES");
}
### DFS算法与连通图的最小生成树 深度优先搜索(DFS)是一种用于遍历或搜索树或图的算法。它通过递归或栈数据结构实现,从一个起点开始,尽可能深地访问每个节[^2]。然而,DFS本身并不直接适用于寻找连通图的最小生成树。 #### 最小生成树的定义 最小生成树是指在一个带权连通图中找到一棵子树,使得该子树包含所有顶,并且边的总权重最小。常用的最小生成树算法包括Prim算法和Kruskal算法[^4]。 #### DFS是否可以用于最小生成树 DFS的主要目标是遍历图中的所有节,而不是优化边的权重总和。因此,直接使用DFS无法保证生成的树是最小生成树。尽管如此,DFS可以用于某些特殊情况下的生成树构建,例如基于破圈法的最小生成树求解[^3]。在这种方法中,DFS用于检测和移除图中的环路,从而逐步构造生成树。 #### 使用DFS构建生成树 虽然DFS不能直接生成最小生成树,但它可以生成一棵生成树。以下是一个基于DFS的生成树构建示例代码: ```python def dfs(graph, start): visited = set() stack = [start] tree_edges = [] while stack: vertex = stack.pop() if vertex not in visited: visited.add(vertex) for neighbor in graph[vertex]: if neighbor not in visited: tree_edges.append((vertex, neighbor)) stack.append(neighbor) return tree_edges # 示例图 graph = { 'A': ['B', 'C'], 'B': ['A', 'D', 'E'], 'C': ['A', 'F'], 'D': ['B'], 'E': ['B', 'F'], 'F': ['C', 'E'] } tree = dfs(graph, 'A') print(tree) ``` 此代码生成了一棵以`A`为根的生成树,但不保证是最小生成树。 #### 结论 DFS可以用于生成连通图的一棵生成树,但不能保证该树是最小生成树。对于最小生成树问题,应使用专门的算法如PrimKruskal
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值