最小树形图(有向图的最小生成树)

article/2025/9/16 9:41:39

我们知道,无向图的最小生成树的求法有Krusal和prime算法,一个是归点一个是归边,在具体实现上Krusal可以用并查集实现,难度不大。

这里稍微区别一下最短路径和最小生成树(因为我又搞混了23333)
最小生成树能够保证首先是树(对于n个顶点的图只有n-1条边),其次保证任意两个顶点之间都可达,再次保证这棵树的边权值之和为最小,但不能保证任意两点之间是最短路径
最短路径保证从源点S到目地点D的路径最小(有向图中不要求终点能到起点),不保证任意两个顶点都可达;
最小生成树是用最小代价遍历整个图中所有顶点,所有的权值和最小。而最短路径只是保证出发点到终点的路径和最小,不一定要经过所有顶点
最小生成树是到一群点(所有点)的路径代价和最小,是一个n-1条边的树,最短路径是从一个点到另一个点的最短路径;
总之,最小生成树一定保证包含所有结点,而最短路径则不然。若题目要求必须每个点都必须经过,则是MST的问题;若只要求起点终点的最小消费,则是最短路径问题。

那么,对于有向图求最小生成树应该如何求呢?有向图就意味着可能有环,比如下面的图:
在这里插入图片描述
1、2结点形成一个环,应该删除哪一条边呢?如果从3出发,就会删掉2->1的边,如果从4出发,就会删掉1->2的边。那么如果把1、2合成一个点,所以3的权值更新为9-3=6,同理4的权值变为7-4 = 3。相当于变相删除不需要走的边。
有向图的最小生成树(最小树形图)求解步骤如下:

  • 先求出最短弧集合E0;

对于节点1 = min{3,9},结点2 = min{4,7},结点4 = 1

  • 如果E0不存在,则图的最小树形图也不存在;
  • 如果E0存在且不具有环,则E0就是最小树形图;
  • 如果E0存在但是存在有向环,则把这个环收缩成一个点u,形成新的图G1,然后对G1继续求其的最小树形图,直到求到图Gi,如果Gi不具有最小树形图,那么此图不存在最小树形图,如果Gi存在最小树形图,那么逐层展开,就得到了原图的最小树形图。

对于上面那张图,整个过程直观地看就是这样:
收缩环
收缩之后的最小树形图
展开收缩点如下,则权值和为1+3+3+4=11
下面是一个更科学的流程图:
在这里插入图片描述

附上代码:

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
#define INF 0x7f7f7f7f
const int maxn = 105;
const int maxm = 100050;int n, m;
int in[maxn];  //保存每个点的最小入权值
int pre[maxn];  //保存最小权值的父节点
int vis[maxn], id[maxn]; //vis是访问标识,id是重新分配的节点号
struct E{int from,to,dis;
}edge[maxm];int dist_mst(int n,int m,int root){  //节点数、边数、根节点int ans = 0;while(1){for(int i = 0; i < n;++i)in[i] = INF;for(int i = 0; i < m;++i){int u = edge[i].from;int v = edge[i].to;//非根节点选出最小边,并记录父节点if(in[v] > edge[i].dis && u != v){in[v] = edge[i].dis;pre[v] = u;}}//若除了根节点外还有入度为0的结点,即孤立点,则没有最小生成树for(int i = 0; i < n;++i){if(i == root)continue;if(in[i] == INF)return -1;}memset(vis,-1,sizeof(vis));memset(id,-1,sizeof(id));int cnt = 0;in[root] = 0;for(int i = 0; i < n;++i){ans += in[i];int v = i;//每个不断向上搜寻父节点,要么找到根节点,要么找到自己,形成一个环//id[v] != -1意味着当前结点已经被重新分配过节点号了,即已经处理了自环while(vis[v] != i && id[v] == -1 && v != root){vis[v] = i;v = pre[v];}//vis[v] == i 即找到了自环,接下来进行缩点(在一个环内分配同一节点号)if(v != root && id[v] == -1){for(int u = pre[v];u != v;u = pre[u])id[u] = cnt;id[v] = cnt++;}}//没有使用cnt,说明已经没有环,结果已经保存在ans中if(cnt == 0)break;//为不在环中的分配节点号for(int i = 0; i < n;++i){if(id[i] == -1)id[i] = cnt++;}//更新边集节点号for(int i = 0; i < m;++i){int u = edge[i].from;int v = edge[i].to;edge[i].from = id[u];edge[i].to = id[v];if(id[u] != id[v])edge[i].dis -= in[v];//这里id[u] != id[v]说明  edges[i]这条边原来不在有向环中,//如果这条边指向了有向环,那么它的边权就要减少  in[v] 等价于整个环的边权减去in[v]//而如果没有指向有向环,说明它与这个有向环毫无关系,那么在之前的寻找自环缩点过程中已经把这条边的权值加上了,所以这里避免重复计算让这条边的权值减小in[v]变为0}n = cnt;root = id[root];}return ans;}
int main(){int n,m;scanf("%d%d",&n,&m);for(int i=0;i<m;i++){scanf("%d%d%d",&edge[i].from,&edge[i].to,&edge[i].dis);if(edge[i].from==edge[i].to)edge[i].dis=INF;}int res=dist_mst(n,m,0);if(res==-1)printf("No\n");elseprintf("%d\n",res);return 0;
}

一道例题:POJ3164 Command Network
这道题套模板,但是要注意把int改为double。(POJ判题真的很严格orz)
附上AC代码:

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 10001;
const int maxm = 100050;int n, m;
double in[maxn];
int pre[maxn];
int vis[maxn], id[maxn];
struct E{int from,to;double dis;
}edge[maxm];
struct P{double x,y;double getDis(P p){return sqrt((x-p.x)*(x-p.x) + (y-p.y)*(y-p.y));}
}point[maxn];
double dist_mst(int n,int m,int root){double ans = 0;while(1){for(int i = 0; i< n;++i)in[i] = INF;for(int i = 0; i < m;++i){int u = edge[i].from;int v = edge[i].to;if(in[v] > edge[i].dis && u != v){in[v] = edge[i].dis;pre[v] = u;}}for(int i = 0; i < n;++i){if(i == root)continue;if(in[i] == INF)return -1;}memset(vis,-1,sizeof(vis));memset(id,-1,sizeof(id));int cnt = 0;in[root] = 0;for(int i = 0; i < n;++i){ans += in[i];int v = i;while(vis[v] != i && id[v] == -1 && v != root){vis[v] = i;v = pre[v];}if(v != root && id[v] == -1){for(int u = pre[v];u != v;u = pre[u])id[u] = cnt;id[v] = cnt++;}}if(cnt == 0)break;for(int i = 0; i < n;++i){if(id[i] == -1)id[i] = cnt++;}for(int i = 0; i < m;++i){int u = edge[i].from;int v = edge[i].to;edge[i].from = id[u];edge[i].to = id[v];if(id[u] != id[v])edge[i].dis -= in[v];}n = cnt;root = id[root];}return ans;}
int main(){int n,m;while(~scanf("%d%d",&n,&m)){for(int i=0;i<n;i++){scanf("%lf%lf",&point[i].x,&point[i].y);}for(int i = 0; i < m;++i){int x,y;scanf("%d%d",&x,&y);edge[i].from = x-1;edge[i].to = y-1;if(edge[i].from != edge[i].to) edge[i].dis = point[x-1].getDis(point[y-1]);else edge[i].dis = INF;}double res=dist_mst(n,m,0);if(res==-1)printf("poor snoopy\n");elseprintf("%.2lf\n",res);}return 0;
}

http://chatgpt.dhexx.cn/article/WJZELEN3.shtml

相关文章

Kruskal算法(最小生成树)

上篇Prim算法简要的讲解了最小生成树。也提到过Prim算法堆优化&#xff0c;但本蒟蒻并没有贴Prim &#xff08;堆优化的代码&#xff09;。至于为什么没有贴呢&#xff1f;上篇Prim算法blog末尾有说明。 好勒&#xff01;咱们接着讲Kruskal算法。这跟Prim算法有很大的…

最小生成树matlab求解

一、最小生成树 连通所有顶点且总路径最小修建连通7个城市的铁路网&#xff0c;可修建的路线和对应造价如图所示&#xff0c;如何修建使总费用最少&#xff1f; 问题分析&#xff1a; 连通7个城市&#xff1a;生成的图中&#xff0c;从任意顶点起步&#xff0c;沿着边一定可以…

最小生成树——贪心算法

文章目录 1.生成树和最小生成树1.1 问题的定义1.2 MST性质 2.普里姆算法&#xff08;Prim&#xff09;2.1 算法流程2.2 算法正确性证明2.3 算法实现2.4 时间复杂度2.5 测试代码 3.克鲁斯卡尔算法&#xff08;kruskal&#xff09;3.1 算法流程3.2 算法正确性证明3.3 算法实现 参…

C++ 最小生成树

目录&#xff1a; 前置知识 最小生成树 Prim 算法 kruskal 算法 前置知识&#xff1a; 连通图&#xff1a;在无向图中&#xff0c;若任意两个顶点 u 与 v 都有路径相通&#xff0c;则称该无向图为连通图。 强连通图&#xff1a;在有向图中&#xff0c;若任意两个顶点 u 与 …

最小生成树(C语言实现)

求这个网的最小生成树 /** 普里姆算法和克鲁斯卡尔算法求最小生成树* 采用邻接矩阵存储**/ #include<stdio.h>#define MAX_VERTEX_NUM 20 //图的定义 typedef struct {int vertexNum;int edgeNum;char vertex[MAX_VERTEX_NUM];int arc[MAX_VERTEX_NUM][MAX_VERTEX_NUM]…

最小生成树(C语言)

最小生成树问题&#xff08;C语言&#xff09; 所谓一个 带权图 的最小生成树&#xff0c;就是原图中边的权值最小的生成树 &#xff0c;所谓最小是指边的权值之和小于或者等于其它生成树的边的权值之和。 kruskal 克鲁斯卡尔算法&#xff08;Kruskal&#xff09;是一种使用贪…

最小生成树kruskal算法

最小生成树kruskal算法 概述算法分析代码 概述 克鲁斯卡尔 ( K r u s k a l ) (Kruskal) (Kruskal)算法是求连通网的最小生成树的另一种方法。与普里姆 ( P r i m ) (Prim) (Prim)算法不同&#xff0c;它的时间复杂度为 O ( e l o g e ) O(eloge) O(eloge)(e为网中的边数)&…

最小生成树:

定义&#xff1a; 将图中的 n 结点用 n-1 条边连接起来&#xff0c;使其各个边的权值之和最小的生成树。 普里姆算法&#xff1a; 从点的角度出发。 1、start 数组的值表示起始点的下标&#xff0c;start 的下标代表目的顶点&#xff1b;lowcost 数组的下标代表目的顶点&…

图——最小生成树

图——最小生成树 1. 基本概念 在一个连通无向图G(V, E)中&#xff0c;对于其中的每条边(u,v)∈E&#xff0c;赋予其权重w(u, v)&#xff0c;则最小生成树问题就是要在G中找到一个连通图G中所有顶点的无环子集T⊆E&#xff0c;使得这个子集中所有边的权重之和w(T) ∑ ( u , …

最小生成树的Kruskal算法-详解

最小生成树的Kruskal算法 一、 什么是最小生成树 1.1 最小生成树定义&#xff1a; 一个有 n 个结点的连通图的生成树是原图的极小连通子图&#xff0c;且包含原图中的所有 n 个结点&#xff0c;并且有保持图连通的最少的边。最小生成树可以用kruskal&#xff08;克鲁斯卡尔&a…

最小生成树(C语言简单实现)

最小生成树 本文参考自《大话数据结构》 一个连通图的生成树是一个极小的连通子图&#xff0c;它含有图中全部的顶点&#xff0c;但只有足以构成一棵树的n-1条边。我们把构造连通网的最小代价生成树称为最小生成树 。 找连通网的最小生成树&#xff0c;经典的有两种算法&#…

最小生成树怎么画?

介绍最小生成树的图形画法~以下图为例&#xff1a; 1、从1开始&#xff0c;以1为顶点画圈。在红色线经过的部分中&#xff0c;可见权重分别为6、1、5&#xff0c;最小权重为1。如下图。 2、以上图中得到的1、3为顶点的图中&#xff0c;继续画线&#xff0c;图为黄色线部分。经过…

最小生成树算法

最小生成树算法 生成树的概念最小生成树算法Prim算法Kruskal算法 生成树的概念 若图是连通的无向图或强连通的有向图&#xff0c;则从其中任一顶点出发&#xff0c;调用一次 d f s dfs dfs或者 b f s bfs bfs后&#xff0c;可以系统的访问图中所有顶点。若图是有根的有向图 &a…

【图】最小生成树

最小生成树&#xff1a;构造连通网的最小代价生成树。 最小生成树有两种算法&#xff1a;普利姆算法、克鲁斯卡尔算法。 普利姆&#xff08;Prim&#xff09;算法 加点&#xff0c;选择相邻点中边权最小的 需要两个一维数组&#xff0c;一个存权值&#xff0c;另一个存起始点…

最小生成树-Kruskal算法

数据结构:最小生成树-Kruskal算法 什么是最小生成树&#xff0c;最小生成树就是求图中把所有点都访问到的最短路径的长度 Kruskal算法采用的是边贪心思想&#xff0c;我们先大概讲一下它的大概思想&#xff0c;首先我们先假设先隐藏所有的边&#xff0c;这样每个点会成为一个连…

最小生成树(kruskal算法)

一、概述 最小生成树问题顾名思义&#xff0c;概括来说就是路修的最短。 接下来引入几个一看就明白的定义&#xff1a; 最小生成树相关概念&#xff1a; 带权图&#xff1a;边赋以权值的图称为网或带权图&#xff0c;带权图的生成树也是带权的&#xff0c;生成树T各边的权值…

最小生成树、最短路径树

一、最小生成树与最短路径树的区别 最小生成树能够保证整个拓扑图的所有路径之和最小&#xff0c;但不能保证任意两点之间是最短路径。 应用如网络部线&#xff0c;把所有的电脑(服务器&#xff1f;&#xff09;都连起来用的网线(光纤&#xff1f;&#xff09;最少&#xff0c…

【图解算法】最小生成树

今天我们介绍图论中的另一部分&#xff0c;最小生成树。 对图的最短路感兴趣的同学可以看&#xff1a; 【图解算法】一次解决最短路径问题 目录 1. 最小生成树简介2. Prim算法2.1 模板题2.2 思路模板2.3 代码实现2.3 prim 算法的优化 3. Kruskal算法3.1 模板题3.2 思路模板3.3…

数据结构—最小生成树

目录 一、生成树 二、最小生成树&#xff08;代价最小树&#xff09; 三、求最小生成树 1、Prim算法&#xff08;普里姆&#xff09; 2.Kruskal 算法&#xff08;克鲁斯卡尔&#xff09; 3.Prim算法和Kruskal算法对比 一、生成树 连通图的生成树是包含图中全部顶点的一个…

最小生成树详解

目录 最小生成树简介 什么是树 什么是生成树 什么是最小生成树 最小生成树的做法 Kruscal&#xff08;克鲁斯卡尔&#xff09;算法 思路 代码 其他算法 最小生成树简介 什么是树 树&#xff08;tree&#xff09;是一种特殊的图&#xff0c;一个图要成为树要满足三个条…