最小生成树:
生成树的定义:给定一个无向图,如果它的某个子图中任意两个顶点都互相连通并且是一棵树,那么这棵树就叫做生成树。(Spanning Tree)
最小生成树的定义:在生成树的基础上,如果边上有权值,那么使得边权和最小的生成树叫做最小生成树。(Minimum Spanning Tree )
解决生成树有两种常用的算法:Kruskal算法和prim算法。
这里我们讲的是prim算法求生成树的解法。
算法思想:
ans = 0;(表示权值和)
1.在无向图的基础上,想象我们有一个点的集合X(初始状态为空)。
2.在集合X中加入一个初始点x,用这个初始点更新其他点离集合X的距离mincost[ ],标记初始点为使用过(使用过:加入集合X)。
3.找一个未使用过(集合X外的点)的离集合X最小距离最小的点,找到了这样一个点,将这个点加入集合X,ans += 这个与集合X的距离,
用这个新加入的点更新其他点离集合X的最小距离mincost[ ],标记新加入的点为使用过,继续执行第3步;找不到这样一个点,则进入第4步。
4.输出ans。
mincost[i] 表示点i离集合X的最小距离。(离集合X中所有点中最近的点的距离)
简单来说就是:
想象一下,有10张面额不同的毛爷爷在你面前,每次只能拿1张,只能拿5次,你肯定会每次都拿这n张(n<=10)中最大的那张,
这样拿5次,让总额最大。这个算法也是同样的道理,不断的加入可触及的最近的点,最后权值一定是最小的。
额,当然10张不同的毛爷爷是不存在的。所以一分都拿不到,还是看代码吧:
代码:
#include <bitsstdc++.h> using namespace std; #define INF 2147483647 #define MAX_V 1000 #define MAX_E 2000 int cost[MAX_V][MAX_V]; // cost[i][j] 表示顶点i到顶点j的权值,不存在时为INF int mincost[MAX_V]; // mincost[i] 表示i点与集合X的最小距离 bool used[MAX_V]; // used[i]表示点i是否在集合中 int V; //顶点数 //表示从点x产生的最小生成树,这么考虑是因为整个图可能不连通 int prim(int x){ //最初X集合为空,每个点到集合X的最小距离都是INF for(int i = 0;i < V; ++i){ mincost[i] = INF; used[i] = false; } //将点x与集合X的距离置为0,第一次集合X会加入点x mincost[x] = 0; int res = 0; while(true){ int v = -1; //找到离集合X最近的点,第一次加入点x for(int u = 0;u < V; ++u){ if(!used[u] && (v == -1 || mincost[u] < mincost[v])) v = u; } //如果所有点可达的点都加入集合X中了,就跳出 if(v == -1) break; used[v] = true; res += mincost[v]; mincost[v] = 0; //把点v加入到集合X中,这一步帮助理解,可写可不写 ,因为cost[v][v] = 0 //用新加入的点v更新其他点离与集合X的最小距离 for(int u = 0;u < V; ++u){ mincost[u] = min(mincost[u] , cost[v][u]); } } return res; } int main(){ }
与Dijkstra算法的比较
Prim算法与Dijkstra算法都是从某个点出发,不断加入最近的点。最终都要把所有可以加的点加完。
Prim算法是求最小生成树,Dijkstra是求单源最短路径。
来个Dijkstra求单源最短路径的代码,与Prim算法比较一下:
#include <bitsstdc++.h> using namespace std; #define INF 2147483647 #define MAX_V 1000 #define MAX_E 2000 //单源最短路径问题(Dijkstra算法) int cost[MAX_V][MAX_V]; //cost[u][v]表示e = (u,v)的权值 int d[MAX_V]; //顶点s出发的最短距离 //不同处1 bool used[MAX_V]; //标记使用过的点 int V; //顶点数 void dijkstra(int s){ fill(d, d+V, INF); fill(used, used + V, INF); d[s] = 0; while(true){ int v = -1; //找到一个距离最近的没有使用过的点 for(int u = 0;u < V; u++){ if(!used[u] && (v == -1 || d[u] < d[v])) v = u; } //如果所有的点都被使用过了,则break if(v == -1) break; //标记当前点被使用过了 used[v] = true; //更新这个找到的距离最小的点所连的点的距离 for(int u = 0;u < V; u++){ d[u] = min(d[u], d[v] + cost[v][u]); //不同处2 } } } int main(){ }
我们可以看到代码基本上是一样的,只有
不同处1:Djikstra中用d[i]表示i点离源点的最短距离,Prim中用mincost[i] 表示i点与集合X的距离。
不同处2:Djikstra中更新d[u] = min( d[u] , d[v] + cost[v][u] ); 用新加入的点更新其他点与源点的最小距离。
Prim中更新mincost[u] = min( mincost[u] , cost[v][u] ); 用新加入的点更新点i与集合X的最小距离。
我认为只用加几行代码,无论是在prim中加,还是在Dijkstra中加,就既可以求单源最短路径,又可以求最小生成树了。