最小生成树算法
prim算法求最小生成树c代码
一、概述在图论中,最小生成树是指在一个连通图中生成一棵包含图中所有顶点且边权值之和最小的树。
prim算法是一种常用的求取最小生成树的算法之一,其基本思想是从一个起始顶点开始,逐步选择与当前树相邻的并且权值最小的边,直到包含了图中所有的顶点为止。
本文将介绍prim算法的原理以及给出相应的C代码实现。
二、prim算法原理1. 初始化选择任意一个顶点作为起始顶点,并将其标记为已访问。
设置一个集合V来存放已经访问的顶点,初始化为空集合。
2. 重复以下步骤直到V包含了所有顶点:(1)遍历集合V中的所有顶点,找到与之相邻且未被访问过的顶点中边权值最小的顶点,将其加入集合V中。
(2)将找到的顶点与其相邻的边加入最小生成树中。
3. 输出最小生成树当集合V包含了所有顶点之后,即可输出最小生成树。
三、prim算法C代码实现以下是prim算法的C代码实现:#include <stdio.h>#include <limits.h>#define V 5 // 顶点个数int minKey(int key[], bool mstSet[]) {int min = INT_MAX, min_index;for (int v = 0; v < V; v++) {if (!mstSet[v] key[v] < min) {min = key[v], min_index = v;}}return min_index;}void printMST(int parent[], int graph[V][V]) {printf("Edge \tWeight\n");for (int i = 1; i < V; i++) {printf("d - d \td \n", parent[i], i, graph[i][parent[i]]);}void primMST(int graph[V][V]) {int parent[V]; // 存放最小生成树的结点int key[V]; // 存放与最小生成树相邻的边的权值bool mstSet[V]; // 存放已访问的顶点for (int i = 0; i < V; i++) {key[i] = INT_MAX, mstSet[i] = false;}key[0] = 0;parent[0] = -1;for (int count = 0; count < V - 1; count++) {int u = minKey(key, mstSet);mstSet[u] = true;for (int v = 0; v < V; v++) {if (graph[u][v] !mstSet[v] graph[u][v] < key[v]) { parent[v] = u, key[v] = graph[u][v];}}}printMST(parent, graph); }int m本人n() {int graph[V][V] = {{0, 2, 0, 6, 0},{2, 0, 3, 8, 5},{0, 3, 0, 0, 7},{6, 8, 0, 0, 9},{0, 5, 7, 9, 0}};primMST(graph);return 0;}```四、代码说明1. minKey函数该函数用于在尚未加入最小生成树的结点中找到与最小生成树相邻的边中权值最小的结点。
最小生成树的Prim算法以及Kruskal算法的证明
最⼩⽣成树的Prim算法以及Kruskal算法的证明Prime算法的思路:从任何⼀个顶点开始,将这个顶点作为最⼩⽣成树的⼦树,通过逐步为该⼦树添加边直到所有的顶点都在树中为⽌。
其中添加边的策略是每次选择外界到该⼦树的最短的边添加到树中(前提是⽆回路)。
Prime算法的正确性证明:引理1:对于连通图中的顶点vi,与它相连的所有边中的最短边⼀定是属于最⼩⽣成树的。
引理2:证明:假设最⼩⽣成树已经建成;(vi, vj)是连接到顶点vi的最短边,在最⼩⽣成树中取出vi,断开连接到vi的边,则⽣成树被拆分成1、顶点vi2、顶点vj所在的连通分量(单独⼀个顶点也看作⼀个独⽴的连通分量)3、其余若⼲个连通分量(个数⼤于等于0)三个部分现在要重建⽣成树,就要重新连接之前被断开的各边虽然不知道之前被断开的都是哪⼏条边,但是可以通过这样⼀个简单的策略来重建连接:将vi分别以最⼩的成本逐个连接到这若⼲个互相分离的连通分量;具体来说,就是要分别遍历顶点vi到某个连通分量中的所有顶点的连接,然后选择其中最短的边来连接vi和该连通分量;⽽要将vi连接到vj所在的连通分量,显然通过边(vi, vj)连接的成本最低,所以边(vi, vj)必然属于最⼩⽣成树(如果连接到vi的最短边不⽌⼀条,只要任意挑选其中的⼀条(vi, vj)即可,以上的证明对于这种情况同样适⽤)。
这样我们就为原来只有⼀个顶点vi的⼦树添加了⼀个新的顶点vj及新边(vi, vj);接下来只要将这棵新⼦树作为⼀个连通⼦图,并且⽤这个连通⼦图替换顶点vi重复以上的分析,迭代地为⼦树逐个地添加新顶点和新边即可。
Kruskal算法:通过从⼩到⼤遍历边集,每次尝试为最⼩⽣成树加⼊当前最短的边,加⼊成功的条件是该边不会在当前已构建的图中造成回路,当加⼊的边的数⽬达到n-1,遍历结束。
Kruskal算法的正确性证明:Kruskal算法每次为当前的图添加⼀条不会造成回路的新边,其本质是逐步地连接当前彼此分散的各个连通分量(单个顶点也算作⼀个连通分量),⽽连接的策略是每次只⽤最⼩的成本连接任意两个连通分量。
数学建模-最小生成树-kruskal算法及各种代码
kruskal算法及代码---含伪代码、c代码、matlab、pascal等代码K r u s k a l算法每次选择n- 1条边,所使用的贪婪准则是:从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中。
注意到所选取的边若产生环路则不可能形成一棵生成树。
K r u s k a l算法分e 步,其中e 是网络中边的数目。
按耗费递增的顺序来考虑这e 条边,每次考虑一条边。
当考虑某条边时,若将其加入到已选边的集合中会出现环路,则将其抛弃,否则,将它选入。
目录Kruskal算法Kruskal算法的代码实现Kruskal算法Kruskal算法的代码实现算法定义克鲁斯卡尔算法假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。
之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。
依次类推,直至森林中只有一棵树,也即子图中含有n-1条边为止。
举例描述克鲁斯卡尔算法(Kruskal's algorithm)是两个经典的最小生成树算法的较为简单理解的一个。
这里面充分体现了贪心算法的精髓。
大致的流程可以用一个图来表示。
这里的图的选择借用了Wikipedia上的那个。
非常清晰且直观。
首先第一步,我们有一张图,有若干点和边如下图所示:第一步我们要做的事情就是将所有的边的长度排序,用排序的结果作为我们选择边的依据。
这里再次体现了贪心算法的思想。
资源排序,对局部最优的资源进行选择。
排序完成后,我们率先选择了边AD。
这样我们的图就变成了第二步,在剩下的变中寻找。
② kruskal最小生成树算法的定义和实现
Krskal最小生成树算法是一种用来解决图论中最小生成树问题的算法。
它采用了一种贪心的策略,即每一步都选择权值最小的边,并且保证选择的边不会构成环,直到生成一棵最小生成树为止。
1. 算法的定义kruskal最小生成树算法的定义如下:输入:一个连通无向图G=(V,E),其中V是顶点集合,E是边的集合,每条边都有一个权值。
输出:G的最小生成树算法步骤:① 将图G的所有边按照权值从小到大进行排序。
② 初始化一个空的边集合T,该集合用来存放最小生成树的边。
③ 依次遍历排序后的边集合,对于每一条边e=(u,v),如果将该边添加到T中不会构成环,则将其添加到T中。
④ 重复步骤③,直到T中的边数等于V-1为止,此时T就是G的最小生成树。
2. 算法的实现下面是kruskal最小生成树算法的具体实现过程:```pythonclass UnionFind:def __init__(self, n):self.parent = [i for i in range(n)]def find(self, x):if self.parent[x] != x:self.parent[x] = self.find(self.parent[x]) return self.parent[x]def union(self, x, y):root_x = self.find(x)root_y = self.find(y)if root_x != root_y:self.parent[root_x] = root_ydef kruskal(graph):edges = []for u in range(len(graph)):for v in range(u+1, len(graph)):if graph[u][v] != 0:edges.append((u, v, graph[u][v])) edges.sort(key=lambda x: x[2])n = len(graph)result = []uf = UnionFind(n)for edge in edges:u, v, weight = edgeif uf.find(u) != uf.find(v):uf.union(u, v)result.append((u, v, weight))if len(result) == n - 1:breakreturn result```以上代码实现了kruskal最小生成树算法,其中使用了UnionFind类来实现并查集的功能,以判断是否构成环。
最小生成树---普里姆算法(Prim算法)和克鲁斯卡尔算法(Kruskal算法)
最⼩⽣成树---普⾥姆算法(Prim算法)和克鲁斯卡尔算法(Kruskal算法)最⼩⽣成树的性质:MST性质(假设N=(V,{E})是⼀个连通⽹,U是顶点集V的⼀个⾮空⼦集,如果(u,v)是⼀条具有最⼩权值的边,其中u属于U,v属于V-U,则必定存在⼀颗包含边(u,v)的最⼩⽣成树)普⾥姆算法(Prim算法)思路:以点为⽬标构建最⼩⽣成树1.将初始点顶点u加⼊U中,初始化集合V-U中各顶点到初始顶点u的权值;2.根据最⼩⽣成树的定义:从n个顶点中,找出 n - 1条连线,使得各边权值最⼩。
循环n-1次如下操作:(1)从数组lowcost[k]中找到vk到集合U的最⼩权值边,并从数组arjvex[k] = j中找到该边在集合U中的顶点下标(2)打印此边,并将vk加⼊U中。
(3)通过查找邻接矩阵Vk⾏的各个权值,即vk点到V-U中各顶点的权值,与lowcost的对应值进⾏⽐较,若更⼩则更新lowcost,并将k存⼊arjvex数组中以下图为例#include<bits/stdc++.h>using namespace std;#define MAXVEX 100#define INF 65535typedef char VertexType;typedef int EdgeType;typedef struct {VertexType vexs[MAXVEX];EdgeType arc[MAXVEX][MAXVEX];int numVertexes, numEdges;}MGraph;void CreateMGraph(MGraph *G) {int m, n, w; //vm-vn的权重wscanf("%d %d", &G->numVertexes, &G->numEdges);for(int i = 0; i < G->numVertexes; i++) {getchar();scanf("%c", &G->vexs[i]);}for(int i = 0; i < G->numVertexes; i++) {for(int j = 0; j < G->numVertexes; j++) {if(i == j) G->arc[i][j] = 0;else G->arc[i][j] = INF;}}for(int k = 0; k < G->numEdges; k++) {scanf("%d %d %d", &m, &n, &w);G->arc[m][n] = w;G->arc[n][m] = G->arc[m][n];}}void MiniSpanTree_Prim(MGraph G) {int min, j, k;int arjvex[MAXVEX]; //最⼩边在 U集合中的那个顶点的下标int lowcost[MAXVEX]; // 最⼩边上的权值//初始化,从点 V0开始找最⼩⽣成树Tarjvex[0] = 0; //arjvex[i] = j表⽰ V-U中集合中的 Vi点的最⼩边在U集合中的点为 Vjlowcost[0] = 0; //lowcost[i] = 0表⽰将点Vi纳⼊集合 U ,lowcost[i] = w表⽰ V-U中 Vi点到 U的最⼩权值for(int i = 1; i < G.numVertexes; i++) {lowcost[i] = G.arc[0][i];arjvex[i] = 0;}//根据最⼩⽣成树的定义:从n个顶点中,找出 n - 1条连线,使得各边权值最⼩for(int i = 1; i < G.numVertexes; i++) {min = INF, j = 1, k = 0;//寻找 V-U到 U的最⼩权值minfor(j; j < G.numVertexes; j++) {// lowcost[j] != 0保证顶点在 V-U中,⽤k记录此时的最⼩权值边在 V-U中顶点的下标if(lowcost[j] != 0 && lowcost[j] < min) {min = lowcost[j];k = j;}}}printf("V[%d]-V[%d] weight = %d\n", arjvex[k], k, min);lowcost[k] = 0; //表⽰将Vk纳⼊ U//查找邻接矩阵Vk⾏的各个权值,与lowcost的对应值进⾏⽐较,若更⼩则更新lowcost,并将k存⼊arjvex数组中for(int i = 1; i < G.numVertexes; i++) {if(lowcost[i] != 0 && G.arc[k][i] < lowcost[i]) {lowcost[i] = G.arc[k][i];arjvex[i] = k;}}}int main() {MGraph *G = (MGraph *)malloc(sizeof(MGraph));CreateMGraph(G);MiniSpanTree_Prim(*G);}/*input:4 5abcd0 1 20 2 20 3 71 2 42 3 8output:V[0]-V[1] weight = 2V[0]-V[2] weight = 2V[0]-V[3] weight = 7最⼩总权值: 11*/时间复杂度O(n^2)克鲁斯卡尔算法(Kruskal算法)思路:以边为⽬标进⾏构建最⼩⽣成树在边集中依次寻找最⼩权值边,若构建是不形成环路(利⽤parent数组记录各点的连通分量),则将其添加到最⼩⽣成树中。
km算法bfs写法详解
km算法bfs写法详解在计算机科学中,广度优先搜索(BFS)是一种用于图或树的搜索算法。
这种方法通常用于在图或树中找到最短路径、查找特定节点或解决其他类似问题。
其中,Kruskal算法是一种用于确定加权无向图的最小生成树的BFS算法。
下面,我们将详细介绍Kruskal算法的BFS实现方法。
一、算法概述Kruskal算法通过BFS来寻找加权无向图的最小生成树。
该算法将图中的边按照权重从小到大进行排序,然后逐一选择边并添加到生成树中。
在添加每条边之前,算法会检查所选边是否与已选择的边形成一个环。
如果形成环,则舍弃该边;否则,将该边添加到生成树中。
通过这种方式,Kruskal算法可以避免生成包含环的生成树,从而确保得到正确的结果。
二、BFS实现步骤1.创建并初始化一个空的当前生成树(tree)和一个空的已选择边集合(selected)。
2.从所有边中随机选择一条边(记为edge),将其添加到已选择边集合中,并将其加入当前生成树中。
3.将所选边的两个顶点分别加入当前生成树的左右两个顶点集合中。
4.遍历所选边的所有未访问的顶点邻居(即不在当前生成树中的顶点),将它们加入到已选择边集合中,并检查是否与当前生成树的左右两个顶点集合中的任意一个有边相连。
如果有,则跳过该顶点;否则,将该顶点加入当前生成树的右顶点集合中。
5.如果当前生成树的左右两个顶点集合的大小不相等,说明存在环,需要重新选择边并重新检查顶点。
6.如果遍历完所有边后仍未找到环,则当前生成树即为最小生成树。
7.返回最小生成树。
三、代码实现以下是一个Python实现的Kruskal算法的BFS示例代码:```pythonclassGraph:def__init__(self,vertices):self.V=verticesself.graph=[]#邻接列表表示的图defadd_edge(self,u,v,w):self.graph.append([u,v,w])#BFS的实现defkruskal_bfs(self):result=[]#存放生成树的所有边i,e=0,0#迭代器和边的数量初始化为0pairs=[]#存放边的信息fornodeinrange(self.V):#将所有的边按照权重从小到大排序ifi<len(self.graph)andself.graph[i][2]<e:#如果这条边的权重小于已选择的边的最大权重pairs.append(self.graph[i])#将这条边加入到已选择的边集合中e+=1#更新已选择的边的最大权重i+=1#遍历下一个节点pairs=sorted(pairs,key=lambdaitem:item[2])#对边的权重进行排序foredgeinpairs:#逐一选择边并添加到生成树中ifnot(edge[0]inresultandedge[1]inresult):#检查所选边是否已经存在于生成树中,避免形成环result.append(edge)#将所选边添加到生成树中returnresult#返回生成树的所有边```四、总结Kruskal算法的BFS实现方法通过逐一选择边并检查是否形成环,可以有效地确定加权无向图的最小生成树。
克鲁斯卡尔算法求最小生成树完整代码
克鲁斯卡尔算法是一种用来求解最小生成树(Minimum Spanning Tree)的经典算法,它采用了贪心策略,能够高效地找到图中的最小生成树。
下面将为大家介绍克鲁斯卡尔算法的完整代码,希望对大家有所帮助。
1. 算法思路克鲁斯卡尔算法的基本思路是:首先将图中的所有边按照权值进行排序,然后从小到大依次考虑每条边,如果加入该边不会构成环,则将其加入最小生成树中。
在算法执行过程中,我们需要使用并查集来判断是否会构成环。
2. 代码实现接下来,我们将给出克鲁斯卡尔算法的完整代码,代码使用C++语言编写,具体如下:```cpp#include <iostream>#include <vector>#include <algorithm>using namespace std;// 定义图的边struct Edge {int u, v, weight;Edge(int u, int v, int weight) : u(u), v(v), weight(weight) {} };// 定义并查集class UnionFind {private:vector<int> parent;public:UnionFind(int n) {parent.resize(n);for (int i = 0; i < n; i++) {parent[i] = i;}}int find(int x) {if (parent[x] != x) {parent[x] = find(parent[x]);}return parent[x];}void Union(int x, int y) {int root_x = find(x);int root_y = find(y);if (root_x != root_y) {parent[root_x] = root_y;}}};// 定义比较函数用于排序bool cmp(const Edge a, const Edge b) {return a.weight < b.weight;}// 克鲁斯卡尔算法vector<Edge> kruskal(vector<Edge> edges, int n) { // 先对边进行排序sort(edges.begin(), edges.end(), cmp);// 初始化最小生成树的边集vector<Edge> res;UnionFind uf(n);for (int i = 0; i < edges.size(); i++) {int u = edges[i].u, v = edges[i].v, weight = edges[i].weight; // 判断是否构成环if (uf.find(u) != uf.find(v)) {uf.Union(u, v);res.push_back(edges[i]);}}return res;}// 测试函数int m本人n() {vector<Edge> edges;edges.push_back(Edge(0, 1, 4));edges.push_back(Edge(0, 7, 8));edges.push_back(Edge(1, 2, 8));edges.push_back(Edge(1, 7, 11));edges.push_back(Edge(2, 3, 7));edges.push_back(Edge(2, 5, 4));edges.push_back(Edge(2, 8, 2));edges.push_back(Edge(3, 4, 9));edges.push_back(Edge(3, 5, 14));edges.push_back(Edge(4, 5, 10));edges.push_back(Edge(5, 6, 2));edges.push_back(Edge(6, 7, 1));edges.push_back(Edge(6, 8, 6));edges.push_back(Edge(7, 8, 7));vector<Edge> res = kruskal(edges, 9);for (int i = 0; i < res.size(); i++) {cout << res[i].u << " " << res[i].v << " " << res[i].weight << endl;}return 0;}```3. 算法实例上述代码实现了克鲁斯卡尔算法,并对给定的图进行了最小生成树的求解。
最小生成树问题例题
最小生成树问题例题最小生成树(Minimum Spanning Tree)是图论中的一个经典问题,它是指在一个带权无向图中找到一棵生成树,使得树上所有边的权值之和最小。
最小生成树问题在实际生活中有着广泛的应用,比如电力输送、通信网络等领域。
下面我们以一个具体的例子来说明最小生成树问题的求解过程。
假设有一个无向图,图中包含了6个节点(A、B、C、D、E、F)和9条边。
每条边都有一个权值,表示连接两个节点的成本。
我们的目标是找到一棵最小生成树。
首先,我们可以使用 Prim 算法来求解最小生成树。
Prim 算法的基本思想是从一个起始节点开始,逐步扩展生成树,直到包含所有节点为止。
具体步骤如下:1. 选择一个起始节点,将其标记为已访问。
2. 从已访问的节点中,选择一条连接到未访问节点的最短边。
3. 将这条边加入到最小生成树中,并将连接的节点标记为已访问。
4. 重复步骤2和步骤3,直到所有节点都被访问过。
根据上述算法,我们可以依次选取边 AB、CD、BC、EF、DE 来构建最小生成树。
最终的最小生成树是:A-B、C-D、B-C、E-F 和 D-E 这五条边,它们的权值之和为12。
另外一个常用的求解最小生成树问题的算法是 Kruskal 算法。
Kruskal 算法的基本思想是将图中的边按照权值从小到大进行排序,然后依次选取边,如果这条边连接的两个节点不在同一个连通分量中,就将这条边加入到最小生成树中。
具体步骤如下:1. 对图中的边按照权值进行排序。
2. 从权值最小的边开始,依次选取边。
3. 如果选取的边连接的两个节点不在同一个连通分量中,就将这条边加入到最小生成树中,并将连接的节点合并为一个连通分量。
4. 重复步骤2和步骤3,直到最小生成树中包含了所有的节点。
使用 Kruskal 算法求解上述例子,我们可以依次选取边 AB、BC、CD、DE、EF 来构建最小生成树。
最终的最小生成树是:A-B、B-C、C-D、D-E 和 E-F 这五条边,它们的权值之和也是12。
最小生成树的算法
最小生成树的算法王洁引言:求连通图的最小生成树是数据结构中讨论的一个重要问题.在现实生活中,经常遇到如何得到连通图的最小生成树,求最小生成树不仅是图论的基本问题之一 ,在实际工作中也有很重要的意义,,人们总想寻找最经济的方法将一个终端集合通过某种方式将其连接起来 ,比如将多个城市连为公路网络 ,要设计最短的公路路线;为了解决若干居民点供水问题 ,要设计最短的自来水管路线等.而避开这些问题的实际意义 ,抓住它们的数学本质 ,就表现为最小生成树的构造。
下面将介绍几种最小生成树的算法。
一,用“破圈法”求全部最小生成树的算法1 理论根据1.1 约化原则给定一无向连通图 G =(V ,E )( V 表示顶点,E 表示边),其中 V={ 1v , 2v ,3v …… n v },E= { 1e , 2e , 3e …… n e }对于 G 中的每条边 e ∈ E 都赋予权ω(i e )>0,求生成树 T = (V ,H ),H ⊆ E ,使生成树所有边权最小,此生成树称为最小生成树.(1) 基本回路将属于生成树 T 中的边称为树枝,树枝数为n -1,不属于生成树的边称为连枝.将任一连枝加到生成树上后都会形成一条回路.把这种回路称为基本回路,记为()cf e 。
基本回路是由 T 中的树枝和一条连枝构成的回路.(2) 基本割集设无向图 G 的割集 S (割集是把连通图分成两个分离部分的最少支路集合) ,若 S 中仅包含有T 中的一条树枝,则称此割集为基本割集,记为()S e 。
基本割集是集合中的元素只有一条是树枝,其他的为连枝.(3) 等长变换设T=(V,H),为一棵生成树,e ∈ H, 'e ∈ E, 'e ∉ H,当且仅当'e ∈()cf e ,也就是说e ∈()S e ,则'T =T ⊕{e, 'e }也是一棵生成树。
当()e ω='()e ω时,这棵生成树叫做等长变换。
求无向图的最小生成树算法——Prim与Kruskal
1.算法思想
对于图G=(V,E),用Prim算法求最小生成树T=(S,TE)的流程如下
① 初始化:设S、TE为空集,任选节点K加入S。
② 选取一条权值最小的边(X,Y),其中X∈S,且not (Y∈S) 即,选取一条权值最小的、连接着S中一点与S外一点的边。
以上操作重复|V|-1次结束。由于每次加入S的点i都在当时取到了符合流程②的边min{lowcost},而lowcost[i]=w(i,closest[i]),所以此时的最小生成树的各边就是(i,closest[i]),i∈V且not (i=x)【需要注意的是出发点x的closest[x]还是x,所以应忽略,实际取到x-1条边】。把i从1取到|V|,便得到最小生成树T的每条边。
为了比较快速地选边,我们用两个数组lowcost、closest动态地维护每一个点到S的最短距离。在某一状态下,lowcost[i]表示所有与i相连且另一端点在S中的边中的权值最小值,closest[i]表示在S中且与i相连的点中与i之间距离最小的点。显然,lowcost[i]=w(i,closest[i])。需要注意的是两个数组记录的都是边而不是路径。若i没有边直接连向S,则lowcost[i]=∞。另外,若i已在S中,则lowcost[i]=0。
lowcost[j] = w[k][j];
closest[j] = k;
} //由新加入S中的k点使某些点到S的距离缩短,所以更新各点的lowcost和close= 1; i <= n; i++)
if(i != closest[i]){
设出发点为x。初始时对于任意k∈V,closest[k]=x,lowcost[k]=w(k,x)【w(i,j)表示i、j间的距离。初始化时,若两点间没有边则w(i,j)赋为一个足够大的整数(如maxint),并且所有点到自身的距离赋为0,即w(i,i)=0】
最小生成树(普里姆算法)
最⼩⽣成树(普⾥姆算法):所谓⽣成树,就是n个点之间连成n-1条边的图形。
⽽最⼩⽣成树,就是权值(两点间直线的值)之和的最⼩值。
⾸先,要⽤⼆维数组记录点和权值。
如上图所⽰⽆向图:int map[7][7];map[1][2]=map[2][1]=4;map[1][3]=map[3][1]=2;......然后再求最⼩⽣成树。
具体⽅法是:1.先选取⼀个点作起始点,然后选择它邻近的权值最⼩的点(如果有多个与其相连的相同最⼩权值的点,随便选取⼀个)。
如1作为起点。
visited[1]=1;pos=1;//⽤low[]数组不断刷新最⼩权值,low[i](0<i<=点数)的值为:i点到邻近点(未被标记)的最⼩距离。
low[1]=0; //起始点i到邻近点的最⼩距离为0low[2]=map[pos][2]=4;low[3]=map[pos][3]=2;low[4]==map[pos][4]=3;low[5]=map[pos][5]=MaxInt; //⽆法直达low[6]=map[pos][6]=MaxInt;2.再在伸延的点找与它邻近的两者权值最⼩的点。
//low[]以3作当前位置进⾏更新visited[3]=1;pos=3;low[1]=0; //已标记,不更新low[2]=map[1][2]=4; //⽐5⼩,不更新low[3]=2; //已标记,不更新low[4]=map[1][4]=3; //⽐1⼤,更新后为:low[4]=map[3][4]=1;low[5]=map[1][5]=MaxInt;//⽆法直达,不更新low[6]=map[1][6]=MaxInt;//⽐2⼤,更新后为:low[6]=map[3][6]=2;3.如此类推...当所有点都连同后,结果最⽣成树如上图所⽰。
所有权值相加就是最⼩⽣成树,其值为2+1+2+4+3=12。
⾄于具体代码如何实现,现在结合POJ1258例题解释。
数据结构之最小生成树Prim算法
数据结构之最⼩⽣成树Prim算法普⾥姆算法介绍 普⾥姆(Prim)算法,是⽤来求加权连通图的最⼩⽣成树算法 基本思想:对于图G⽽⾔,V是所有顶点的集合;现在,设置两个新的集合U和T,其中U⽤于存放G的最⼩⽣成树中的顶点,T存放G的最⼩⽣成树中的边。
从所有uЄU,vЄ(V-U) (V-U表⽰出去U的所有顶点)的边中选取权值最⼩的边(u, v),将顶点v加⼊集合U中,将边(u, v)加⼊集合T中,如此不断重复,直到U=V为⽌,最⼩⽣成树构造完毕,这时集合T中包含了最⼩⽣成树中的所有边。
代码实现1. 思想逻辑 (1)以⽆向图的某个顶点(A)出发,计算所有点到该点的权重值,若⽆连接取最⼤权重值#define INF (~(0x1<<31)) (2)找到与该顶点最⼩权重值的顶点(B),再以B为顶点计算所有点到改点的权重值,依次更新之前的权重值,注意权重值为0或⼩于当前权重值的不更新,因为1是⼀当找到最⼩权重值的顶点时,将权重值设为了0,2是会出现⽆连接的情况。
(3)将上述过程⼀次循环,并得到最⼩⽣成树。
2. Prim算法// Prim最⼩⽣成树void Prim(int nStart){int i = 0;int nIndex=0; // prim最⼩树的索引,即prims数组的索引char cPrims[MAX]; // prim最⼩树的结果数组int weights[MAX]; // 顶点间边的权值cPrims[nIndex++] = m_mVexs[nStart].data;// 初始化"顶点的权值数组",// 将每个顶点的权值初始化为"第start个顶点"到"该顶点"的权值。
for (i = 0; i < m_nVexNum; i++){weights[i] = GetWeight(nStart, i);}for (i = 0; i < m_nVexNum; i ++){if (nStart == i){continue;}int min = INF;int nMinWeightIndex = 0;for (int k = 0; k < m_nVexNum; k ++){if (weights[k]!= 0 && weights[k] < min){min = weights[k];nMinWeightIndex = k;}}// 找到下⼀个最⼩权重值索引cPrims[nIndex++] = m_mVexs[nMinWeightIndex].data;// 以找到的顶点更新其他点到该点的权重值weights[nMinWeightIndex]=0;int nNewWeight = 0;for (int ii = 0; ii < m_nVexNum; ii++){nNewWeight = GetWeight(nMinWeightIndex, ii);// 该位置需要特别注意if (0 != weights[ii] && weights[ii] > nNewWeight){weights[ii] = nNewWeight;}}for (i = 1; i < nIndex; i ++){int min = INF;int nVexsIndex = GetVIndex(cPrims[i]);for (int kk = 0; kk < i; kk ++){int nNextVexsIndex = GetVIndex(cPrims[kk]);int nWeight = GetWeight(nVexsIndex, nNextVexsIndex);if (nWeight < min){min = nWeight;}}nSum += min;}// 打印最⼩⽣成树cout << "PRIM(" << m_mVexs[nStart].data <<")=" << nSum << ": ";for (i = 0; i < nIndex; i++)cout << cPrims[i] << "";cout << endl;}3. 全部实现#include "stdio.h"#include <iostream>using namespace std;#define MAX 100#define INF (~(0x1<<31)) // 最⼤值(即0X7FFFFFFF)class EData{public:EData(char start, char end, int weight) : nStart(start), nEnd(end), nWeight(weight){} char nStart;char nEnd;int nWeight;};// 边struct ENode{int nVindex; // 该边所指的顶点的位置int nWeight; // 边的权重ENode *pNext; // 指向下⼀个边的指针};struct VNode{char data; // 顶点信息ENode *pFirstEdge; // 指向第⼀条依附该顶点的边};// ⽆向邻接表class listUDG{public:listUDG(){};listUDG(char *vexs, int vlen, EData **pEData, int elen){m_nVexNum = vlen;m_nEdgNum = elen;// 初始化"邻接表"的顶点for (int i = 0; i < vlen; i ++){m_mVexs[i].data = vexs[i];m_mVexs[i].pFirstEdge = NULL;}char c1,c2;int p1,p2;ENode *node1, *node2;// 初始化"邻接表"的边for (int j = 0; j < elen; j ++){// 读取边的起始顶点和结束顶点p1 = GetVIndex(c1);p2 = GetVIndex(c2);node1 = new ENode();node1->nVindex = p2;node1->nWeight = pEData[j]->nWeight;if (m_mVexs[p1].pFirstEdge == NULL){m_mVexs[p1].pFirstEdge = node1;}else{LinkLast(m_mVexs[p1].pFirstEdge, node1);}node2 = new ENode();node2->nVindex = p1;node2->nWeight = pEData[j]->nWeight;if (m_mVexs[p2].pFirstEdge == NULL){m_mVexs[p2].pFirstEdge = node2;}else{LinkLast(m_mVexs[p2].pFirstEdge, node2);}}}~listUDG(){ENode *pENode = NULL;ENode *pTemp = NULL;for (int i = 0; i < m_nVexNum; i ++){pENode = m_mVexs[i].pFirstEdge;if (pENode != NULL){pTemp = pENode;pENode = pENode->pNext;delete pTemp;}delete pENode;}}void PrintUDG(){ENode *pTempNode = NULL;cout << "邻接⽆向表:" << endl;for (int i = 0; i < m_nVexNum; i ++){cout << "顶点:" << GetVIndex(m_mVexs[i].data)<< "-" << m_mVexs[i].data<< "->"; pTempNode = m_mVexs[i].pFirstEdge;while (pTempNode){cout <<pTempNode->nVindex << "->";pTempNode = pTempNode->pNext;}cout << endl;}}// Prim最⼩⽣成树void Prim(int nStart){int i = 0;int nIndex=0; // prim最⼩树的索引,即prims数组的索引char cPrims[MAX]; // prim最⼩树的结果数组int weights[MAX]; // 顶点间边的权值cPrims[nIndex++] = m_mVexs[nStart].data;// 初始化"顶点的权值数组",// 将每个顶点的权值初始化为"第start个顶点"到"该顶点"的权值。
生成最小生成树的方法
生成最小生成树的方法
生成最小生成树的方法有以下几种:
1. Kruskal算法:该算法首先将图中的边按权值从小到大排序,然后依次考虑每条边,若加入该边不会形成环,则将该边加入最小生成树中,直到最小生成树的边数等于节点数减一为止。
2. Prim算法:该算法从任意一个节点开始,不断选择与当前
最小生成树相连的边中权值最小的边,将其加入最小生成树中,直到所有节点都被加入最小生成树为止。
3. Boruvka算法:该算法首先将图中的每个节点作为一个独立
的连通分量,并初始化一个空的最小生成树。
然后,依次遍历所有连通分量,每次选择与该连通分量相连的最小权值边,并将其加入最小生成树中。
当最小生成树中的边数等于节点数减一时,算法停止。
4. Reverse-Delete算法:该算法从图中的所有边中按权值从大
到小的顺序考虑,然后依次删除每条边,若删除该边后原图仍然是连通的,则继续删除下一条边,直到最小生成树的边数等于节点数减一为止。
这些方法都可以用来生成最小生成树,选择哪种方法取决于具体的应用场景和图的特点。
最小生成树Prim算法和单源最短路径Dijkstra算法
最小生成树Prim算法和单源最短路径Dijkstra算法问题:1. (最小生成树)给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,即求最小生成树。
2. (单源最短路径)给定一个权值都为正数的无向连通图和一个源点,确定它到其它点的最短距离。
之所以将这两个问题放在一起,是因为Prim算法与Dijkstra算法的思路和程序都非常相似,都是有贪心策略。
1.解法(Prim算法):思路:设连通网络N = { V, E },U表示已加入到生成树的顶点集合。
在初始化阶段,任取一个顶点,用其关联边的权值作为初始的V-U顶点集合的数据。
在每一步中,先在V-U顶点集合中选择距离U中任意点“最近”的顶点v,再把v加入到U中,最后看看在新的V-U顶点集合中,是否有哪个顶点距离v比距离U中其它点更近,若有则更新V-U顶点集合中的数据。
U的元素个数为V-1,所以共要进行V-1步。
总的时间复杂度为Time =O(V)*T_EXTRACT-MIN+O(E)*T_DECREASE-KEY若用数组作为顶点权值的数据结构,T_EXTRACT-MIN用时O(V),T_DECREASE-KEY用时O(1),总共用时O(V^2)若用最小堆作为顶点权值的数据结构,T_EXTRACT-MIN用时O(lgV),T_DECREASE-KEY用时O(lgV),总共用时O((V+E)*lgV)若用斐波那契堆作为顶点权值的数据结构,T_EXTRACT-MIN平均用时O(lgV),T_DECREASE-KEY平均用时O(1),总共用时O(E+V*lgV)下面的代码使用数组作为顶点权值的数据结构:[cpp]view plaincopy1.#include <iostream>2.#include <algorithm>ing namespace std;4.5.#define MAXN 10016.#define INF 10000007.8.int lowcost[MAXN]; // 距离U中顶点的最小权值9.bool visited[MAXN]; // 若为true表示已加入到集合U中10.int mst[MAXN]; // 距离U中哪个顶点最短11.int graph[MAXN][MAXN]; // 用矩阵表示图上各边权值12.13.void Prim(int n)14.{15.int i, j;16. memset(visited, 0, n*sizeof(bool));17. visited[0] = true;18. mst[0] = -1;19.for (i=1; i<n; i++)20. {21. lowcost[i] = graph[0][i];22. mst[i] = 0;23. }24.for (i=1; i<=n-1; i++)25. {26.// 取V-U中的最小权值T_EXTRACT-MIN O(V)27.int min=INF, minid;28.for (j=1; j<n; j++)29.// 用visited数组确定V-U30.if (!visited[j] && lowcost[j] < min)31. {32. min = lowcost[j];33. minid = j;34. }35. visited[minid] = true;36.// 减小V-U中的权值T_DECREASE-KEY O(1)37.for (j=1; j<n; j++)38.if (!visited[j] && lowcost[j] > graph[minid][j])39. {40. lowcost[j] = graph[minid][j];41. mst[j] = minid;42. }43. }44.}45.46.int main()47.{48.int n, m, i, j;49. cin >> n >> m;50.for (i=0; i<n; i++)51.for (j=0; j<n; j++)52. graph[i][j] = INF;53.for (i=0; i<m; i++)54. {55.char a[3], b[3];56.int c;57. scanf("%s %s %d",&a, &b, &c);58. graph[a[0]-'A'][b[0]-'A'] = c;59. graph[b[0]-'A'][a[0]-'A'] = c;60. }61. Prim(n);62.int total = 0;63.for (i=1; i<n; i++)64. {65. total += lowcost[i];66. printf("%c-%c: %d\n", i+'A', mst[i]+'A', lowcost[i]);67. }68. printf("%d\n", total);69.}2.解法(Dijkstra算法):思路:设连通网络N = { V, E }和给定源点u,U表示已确定最短路径的顶点集合。
数学建模最小生成树例题
数学建模最小生成树例题例题1:某城市计划建设一条高速公路,需要在若干个村庄之间选择一条最优路径。
已知各个村庄之间的距离,请使用最小生成树算法为高速公路选择最优路径。
参考答案:最小生成树算法可以用于解决此类问题。
常用的最小生成树算法有Kruskal算法和Prim算法。
1. Kruskal算法:按照边的权重从小到大排序,依次将边加入生成树,如果加入的边与已选择的边不构成环,则加入,否则不加入。
2. Prim算法:首先选择权重最小的边加入生成树,然后从剩余的边中选择一条与已选择的边相连且权重最小的边加入生成树,直到所有边都加入生成树。
例题2:一个通信网络由若干个节点和边组成,节点代表城市,边代表通信线路。
已知各个城市之间的距离和通信需求,请使用最小生成树算法为该通信网络设计一个最优的通信线路网。
参考答案:最小生成树算法可以用于解决此类问题。
通过最小生成树算法,我们可以找到一个包含所有节点且边的总权重最小的树形结构,以满足各个城市之间的通信需求。
常用的最小生成树算法有Kruskal算法和Prim算法。
1. Kruskal算法:按照边的权重从小到大排序,依次将边加入生成树,如果加入的边与已选择的边不构成环,则加入,否则不加入。
2. Prim算法:首先选择权重最小的边加入生成树,然后从剩余的边中选择一条与已选择的边相连且权重最小的边加入生成树,直到所有边都加入生成树。
例题3:一个城市的电力网由多个节点和边组成,节点代表发电厂或变电站,边代表输电线路。
已知各个节点之间的电抗和传输功率,请使用最小生成树算法为该城市电力网设计一个最优的输电线路。
参考答案:最小生成树算法可以用于解决此类问题。
通过最小生成树算法,我们可以找到一个包含所有节点且边的总电抗最小的树形结构,以满足各个节点之间的电力传输需求。
常用的最小生成树算法有Kruskal算法和Prim算法。
1. Kruskal算法:按照边的电抗从小到大排序,依次将边加入生成树,如果加入的边与已选择的边不构成环,则加入,否则不加入。
最小生成树的两种算法
最小生成树的两种算法包括:
1. Prim算法:
Prim算法是一种选择点加入树的算法。
首先选择任意一点作为树的第一个节点,然后枚举与它相连的所有点,将两点之间的边权记为这个点到生成树的距离,选择距离最近的点加入生成树,然后枚举与之相邻的节点,用边权更新该节点的距离,使距离等于两个节点之间的边的权重和。
再继续加入当前离生成树最近的点,在更新它相邻的点,以此类推,直到所有点全部加入生成树。
这样就求出了最小生成树。
2. Kruskal算法:
Kruskal算法也称为“加边法”。
首先把图中的所有边按代价从小到大排序,把图中的n个顶点看成独立的n棵树组成的森林,按权值从小到大选择边,所选的边连接的两个顶点应该属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。
重复以上步骤,直到所有顶点都在一颗树内或者有n-1条边为止。
这样就可以得到最小生成树。
以上信息仅供参考,可以咨询计算机专业人士或者查看专业书籍,
以获取更准确更全面的内容。
数据结构(三十三)最小生成树(Prim、Kruskal)
数据结构(三⼗三)最⼩⽣成树(Prim、Kruskal) ⼀、最⼩⽣成树的定义 ⼀个连通图的⽣成树是⼀个极⼩的连通⼦图,它含有图中全部的顶点,但只有⾜以构成⼀棵树的n-1条边。
在⼀个⽹的所有⽣成树中,权值总和最⼩的⽣成树称为最⼩代价⽣成树(Minimum Cost Spanning Tree),简称为最⼩⽣成树。
构造最⼩⽣成树的准则有以下3条:只能使⽤该图中的边构造最⼩⽣成树当且仅当使⽤n-1条边来连接图中的n个顶点不能使⽤产⽣回路的边 对⽐两个算法,Kruskal算法主要是针对边来展开,边数少时效率会⾮常⾼,所以对于稀疏图有很⼤的优势;⽽Prim算法对于稠密图,即边数⾮常多的情况会更好⼀些。
⼆、普⾥姆(Prim)算法 1.Prim算法描述 假设N={V,{E}}是连通⽹,TE是N上最⼩⽣成树中边的集合。
算法从U={u0,u0属于V},TE={}开始。
重复执⾏下⾯的操作:在所有u属于U,v 属于V-U的边(u,v)中找⼀条代价最⼩的边(u0,v0)并加⼊集合TE,同时v0加⼊U,直到U=V为⽌。
此时TE中必有n-1条边,则T=(V,{TE})为N的最⼩⽣成树。
2.Prim算法的C语⾔代码实现/* Prim算法⽣成最⼩⽣成树 */void MiniSpanTree_Prim(MGraph G){int min, i, j, k;int adjvex[MAXVEX]; /* 保存相关顶点下标 */int lowcost[MAXVEX]; /* 保存相关顶点间边的权值 */lowcost[0] = 0;/* 初始化第⼀个权值为0,即v0加⼊⽣成树 *//* lowcost的值为0,在这⾥就是此下标的顶点已经加⼊⽣成树 */adjvex[0] = 0; /* 初始化第⼀个顶点下标为0 */for(i = 1; i < G.numVertexes; i++) /* 循环除下标为0外的全部顶点 */{lowcost[i] = G.arc[0][i]; /* 将v0顶点与之有边的权值存⼊数组 */adjvex[i] = 0; /* 初始化都为v0的下标 */}for(i = 1; i < G.numVertexes; i++){min = INFINITY; /* 初始化最⼩权值为∞, *//* 通常设置为不可能的⼤数字如32767、65535等 */j = 1;k = 0;while(j < G.numVertexes) /* 循环全部顶点 */{if(lowcost[j]!=0 && lowcost[j] < min)/* 如果权值不为0且权值⼩于min */{min = lowcost[j]; /* 则让当前权值成为最⼩值 */k = j; /* 将当前最⼩值的下标存⼊k */}j++;}printf("(%d, %d)\n", adjvex[k], k);/* 打印当前顶点边中权值最⼩的边 */lowcost[k] = 0;/* 将当前顶点的权值设置为0,表⽰此顶点已经完成任务 */for(j = 1; j < G.numVertexes; j++) /* 循环所有顶点 */{if(lowcost[j]!=0 && G.arc[k][j] < lowcost[j]){/* 如果下标为k顶点各边权值⼩于此前这些顶点未被加⼊⽣成树权值 */lowcost[j] = G.arc[k][j];/* 将较⼩的权值存⼊lowcost相应位置 */adjvex[j] = k; /* 将下标为k的顶点存⼊adjvex */}}}}Prim算法 3.Prim算法的Java语⾔代码实现package bigjun.iplab.adjacencyMatrix;/*** 最⼩⽣成树之Prim算法*/public class MiniSpanTree_Prim {int lowCost; // 顶点对应的权值public CloseEdge(Object adjVex, int lowCost) {this.adjVex = adjVex;this.lowCost = lowCost;}}private static int getMinMum(CloseEdge[] closeEdges) {int min = Integer.MAX_VALUE; // 初始化最⼩权值为正⽆穷int v = -1; // 顶点数组下标for (int i = 0; i < closeEdges.length; i++) { // 遍历权值数组,找到最⼩的权值以及对应的顶点数组的下标if (closeEdges[i].lowCost != 0 && closeEdges[i].lowCost < min) {min = closeEdges[i].lowCost;v = i;}}return v;}// Prim算法构造图G的以u为起始点的最⼩⽣成树public static void Prim(AdjacencyMatrixGraphINF G, Object u) throws Exception{// 初始化⼀个⼆维最⼩⽣成树数组minSpanTree,由于最⼩⽣成树的边是n-1,所以数组第⼀个参数是G.getVexNum() - 1,第⼆个参数表⽰边的起点和终点符号,所以是2 Object[][] minSpanTree = new Object[G.getVexNum() - 1][2];int count = 0; // 最⼩⽣成树得到的边的序号// 初始化保存相关顶点和相关顶点间边的权值的数组对象CloseEdge[] closeEdges = new CloseEdge[G.getVexNum()];int k = G.locateVex(u);for (int j = 0; j < G.getVexNum(); j++) {if (j!=k) {closeEdges[j] = new CloseEdge(u, G.getArcs()[k][j]);// 将顶点u到其他各个顶点权值写⼊数组中}}closeEdges[k] = new CloseEdge(u, 0); // 加⼊u到⾃⾝的权值0for (int i = 1; i < G.getVexNum(); i++) { // 注意,这⾥从1开始,k = getMinMum(closeEdges); // 获取u到数组下标为k的顶点的权值最短minSpanTree[count][0] = closeEdges[k].adjVex; // 最⼩⽣成树第⼀个值为uminSpanTree[count][1] = G.getVexs()[k]; // 最⼩⽣成树第⼆个值为k对应的顶点count++;closeEdges[k].lowCost = 0; // 下标为k的顶点不参与最⼩权值的查找了for (int j = 0; j < G.getVexNum(); j++) {if (G.getArcs()[k][j] < closeEdges[j].lowCost) {closeEdges[j] = new CloseEdge(G.getVex(k), G.getArcs()[k][j]);}}}System.out.print("通过Prim算法得到的最⼩⽣成树序列为: {");for (Object[] Tree : minSpanTree) {System.out.print("(" + Tree[0].toString() + "-" + Tree[1].toString() + ")");}System.out.println("}");}} 4.举例说明Prim算法实现过程 以下图为例: 测试类:// ⼿动创建⼀个⽤于测试最⼩⽣成树算法的⽆向⽹public static AdjacencyMatrixGraphINF createUDNByYourHand_ForMiniSpanTree() {Object vexs_UDN[] = {"V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8"};int arcsNum_UDN = 15;int[][] arcs_UDN = new int[vexs_UDN.length][vexs_UDN.length];for (int i = 0; i < vexs_UDN.length; i++) // 构造⽆向图邻接矩阵for (int j = 0; j < vexs_UDN.length; j++)if (i==j) {arcs_UDN[i][j]=0;} else {arcs_UDN[i][j] = arcs_UDN[i][j] = INFINITY;}arcs_UDN[0][5] = 11;arcs_UDN[1][2] = 18;arcs_UDN[1][6] = 16;arcs_UDN[1][8] = 12;arcs_UDN[2][3] = 22;arcs_UDN[2][8] = 8;arcs_UDN[3][4] = 20;arcs_UDN[3][6] = 24;arcs_UDN[3][7] = 16;arcs_UDN[3][8] = 21;arcs_UDN[4][5] = 26;arcs_UDN[4][7] = 7;arcs_UDN[5][6] = 17;arcs_UDN[6][7] = 19;for (int i = 0; i < vexs_UDN.length; i++) // 构造⽆向图邻接矩阵for (int j = i; j < vexs_UDN.length; j++)arcs_UDN[j][i] = arcs_UDN[i][j];return new AdjMatGraph(GraphKind.UDN, vexs_UDN.length, arcsNum_UDN, vexs_UDN, arcs_UDN);}public static void main(String[] args) throws Exception {AdjMatGraph UDN_Graph = (AdjMatGraph) createUDNByYourHand_ForMiniSpanTree();MiniSpanTree_Prim.Prim(UDN_Graph, "V0");} 输出为:通过Prim算法得到的最⼩⽣成树序列为: {(V0-V1)(V0-V5)(V1-V8)(V8-V2)(V1-V6)(V6-V7)(V7-V4)(V7-V3)} 分析算法执⾏过程:从V0开始:-count为0,k为0,closeEdges数组的-lowCost为{0 10 INF INF INF 11 INF INF INF},adjVex数组为{V0,V0,V0,V0,V0,V0,V0,V0,V0}-⽐较lowCost,于是k为1,adjVex[1]为V0,minSpanTree[0]为(V0,V1),lowCost为{0 0 INF INF INF 11 INF INF INF}-k为1,与V1的权值⾏⽐较,得到新的-lowCost为:{0 0 18 INF INF 11 16 INF 12},adjVex数组为{V0,V0,V1,V0,V0,V0,V1,V0,V1}-⽐较lowCost,于是k为5,adjVex[5]为V0,minSpanTree[1]为(V0,V5),lowCost为{0 0 18 INF INF 0 16 INF 12}-k为5,与V5的权值⾏⽐较,得到新的-lowCost为{0 0 18 INF 26 0 16 INF 12},adjVex数组为{V0,V0,V1,V0,V5,V0,V1,V0,V1}-⽐较lowCost,于是k为8,adjVex[8]为V1,minSpanTree[2]为(V1,V8),lowCost为{0 0 18 INF INF 0 16 INF 0}... 三、克鲁斯卡尔(Kruskal)算法 1.Kruskal算法描述 Kruskal算法是根据边的权值递增的⽅式,依次找出权值最⼩的边建⽴的最⼩⽣成树,并且规定每次新增的边,不能造成⽣成树有回路,直到找到n-1条边为⽌。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Kruskal算法完成POJ1258
//by Guo Wei #include <iostream> #include <vector> #include <algorithm> using namespace std; struct Edge { int s,e,w; //起点,终点,权值 Edge(int ss,int ee,int ww):s(ss),e(ee),w(ww) { } Edge() { } bool operator < (const Edge & e1) const { return w < e1.w; } }; vector <Edge> edges; vector <int> parent;
图例
关键问题
每次如何从连接T中和T外顶点的所有边中,找 到一条最短的 1) 如果用邻接矩阵存放图,而且选取最短边的 时候遍历所有点进行选取,则总时间复杂度为 O(V2), V 为顶点个数 2)用邻接表存放图,并使用堆来选取最短边,则 总时间复杂度为O(ElogV) 不加堆的Prim 算法适用于密集图,加堆的适用 于稀疏图
算法:Kruskal 和 Prim
•
Kruskal:将所有边从小到大加入,在此过程中 判断是否构成回路
– – –
使用数据结构:并查集 时间复杂度:O(ElogE) 适用于稀疏图 使用数据结构:堆 时间复杂度:O(ElogV) 或 O(VlogV+E)(斐波那契堆) 适用于密集图 若不用堆则时间复杂度为O(V2)
只需在该图上求最小生成树,d 的最小值即为 第 K 长边! 因为:最小生成树中的最长k-1条长边都去掉 后,正好将原树分成了k 个连通分支,在每 个连通分支上摆一个卫星设备即可
为什么d不可能比第k长边更小?
假设最小生成树T上,第k长边连接的点是a,b,那么 将边<a,b>去掉后,树就分成了两个部分T1 和T2 要使T1和T2能够通讯,必须在T1中找一点p和T2中 的点q相连,若边<p,q>的长度小于<a,b>,则在T 上用<p,q>替换<a,b>就能得到更小的生成树,矛 盾。因此找不到长度小于<a,b>的<p,q>。 对任何比第k长边短的边e,同理也不可能找到替代e 的边。
本文大量内容引自北京大学信息学院程序设计实习(实验班)郑聃崴、 陈国鹏等同学的讲义,在此致谢
图的生成树
在一个连通图G中,如果取它的全部顶点和 一部分边构成一个子图G’,即: V(G’)=V(G);E(G’) ∈E(G) 若边集E(G’)中的边既将图中的所有顶点连 通又不形成回路,则称子图G’是原图G的一 棵生成树。 一棵含有n个点的生成树,必含有n-1条边。
首先从V中任取一个顶点(假定取v1),将它并入 U中,此时U={v1},然后只要U是V的真子集(U∈V), 就从那些一个端点已在T中,另一个端点仍在T外 的所有边中,找一条最短边,设为(vi,vj),其中 vi∈U,vj ∈V-U,并把该边(vi, vj)和顶点vj分别并入T 的边集TE和顶点集U,如此进行下去,每次往生成 树里并入一个顶点和一条边,直到n-1次后得到最 小生成树。
Kruskal算法
假设G=(V,E)是一个具有n个顶点的连通网, T=(U,TE)是G的最小生成树,U=V,TE初值为 空。
将图G中的边按权值从小到大依次选取,若 选取的边使生成树不形成回路,则把它并 入TE中,若形成回路则将其舍弃,直到TE 中包含N-1条边为止,此时T为最小生成树。
关键问题
int main() { int N; while(cin >> N) { for( int i = 0;i < N; ++i) G[i].clear(); for( int i = 0; i < N; ++i) for( int j = 0; j < N; ++j) { int w; cin >> w; G[i].push_back(Edge(j,w)); } cout << HeapPrim(G,N) << endl; } }
因此 d不可能更小了
最小生成树可能不止一棵,为什么第k长边长度一定相同?因为有 以下结论:
一个图的两棵最小生成树,边的权值序列 排序后结果相同
证明:假设某个最小生成树T1的边权从小到大排序后的序列为: a1, a2 …. an 某个最小生成树T2的边权从小到大排序后的序列为: b1,b2…bn 两者若不同,则必然存在一个最小的i,使得 ai > bi 假设T2中有m条边的权为bi,那么,T1中最多只有m-1条边的权和bi相同。 但是对于T2中任何一条不在T1中的权为bi的边,如果将其从T2去掉,则T2被分成A,B两 个部分。那么在T1中连接A,B这两个部分的边,必然权值是等于bi的,否则经过替换, 要么T1的权值可以变得更小,要么T2的权值可以变得更小,这和T1,T2是最小生成树矛 盾。对T2中每个权值为bi的边,都可以在T1中找到一个权值相同且不在T2的边与其对 应,而这些边由于是连接不同部分的,所以不可能相同,因此,在T1中也应该有m条权 值为bi的边,这和T1中最多m-1条权值为bi的边矛盾。因此,不存在i,使得的ai>bi,即两 个边权序列应该相同。
如何判断欲加入的一条边是否与生成树 中边构成回路。 将各顶点划分为所属集合的方法来解决, 每个集合的表示一个无回路的子集。开 始时边集为空,N个顶点分属N个集合, 每个集合只有一个顶点,表示顶点之间 互不连通。 当从边集中按顺序选取一条边时,若它 的两个端点分属于不同的集合,则表明 此边连通了两个不同的部分,因每个部 分连通无回路,故连通后仍不会产生回 路,此边保留,同时把相应两个集合合 并
•
Prim:从任一节点出发,不断扩展
– – – –
例题: POJ 2349 Arctic Network
•
•
•
•
某地区共有n座村庄,每座村庄的坐标用一对整数(x, y)表示,现在要在村庄之间建立通讯网络。 通讯工具有两种,分别是需要铺设的普通线路和无 线通讯的卫星设备。 只能给k个村庄配备卫星设备,拥有卫星设备的村 庄互相间直接通讯。 铺设了线路的村庄之间也可以通讯。但是由于技术 原因,两个村庄之间线路长度最多不能超过 d, 否则 就会由于信号衰减导致通讯不可靠。要想增大 d 值, 则会导致要投入更多的设备(成本)
北京大学暑期课《ACM/ICPC竞赛训练》
北京大学信息学院 郭炜 guo_wei@ /guoweiofpku
课程网页:/summerschool/pku_acm_train.htm
最小生成树(MST)问题
int main() { int N; while(cin >> N) { parent.clear(); edges.clear(); for( int i = 0;i < N; ++i) parent.push_back(i); for( int i = 0; i < N; ++i) for( int j = 0; j < N; ++j) { int w; cin >> w; edges.push_back(Edge(i,j,w)); } sort(edges.begin(),edges.end()); int done = 0; int totalLen = 0; for( int i = 0;i < edges.size(); ++i) { if( GetRoot(edges[i].s) != GetRoot(edges[i].e)) { Merge(edges[i].s,edges[i].e); ++done; totalLen += edges[i].w; } if( done == N – 1) break; } cout << totalLen << endl; } }
while( nDoneNum < n && !pq.empty() ) { do {//每次从队列里面拿离在建生成树最近的点 xDist = pq.top(); pq.pop(); } while( vUsed[xDist.v] == 1 && ! pq.empty()); if( vUsed[xDist.v] == 0 ) { nTotalW += xDist.w; vUsed[xDist.v] = 1; nDoneNum ++; for( i = 0;i < G[xDist.v].size();i ++ ) {//更新新加入点的邻点 int k = G[xDist.v][i].v; if( vUsed[k] == 0) { int w = G[xDist.v][i].w ; if( vDist[k] > w ) { vDist[k] = w; pq.push(Edge(k,w)); } } } } } if( nDoneNum < n ) return -1; //图不连通 return nTotalW; }
POJ 1258 最小生成树模版题
输入图的邻接矩阵,求最小生成树的总权值 (多组数据) 输入样例: 4 0 4 9 21 4 0 8 17 9 8 0 16 21 17 16 0 输出样例: 28
prioirty_queue实现 Prim + 堆 完成POJ1258
//by Guo Wei #include <iostream> #include <vector> #include <algorithm> #include <queue> using namespace std; const int INFINITE = 1 << 30; struct Edge { int v; //边端点,另一端点已知 int w; //边权值,也用来表示v到在建最小生成树的距离 Edge(int v_ = 0, int w_ = INFINITE):v(v_),w(w_) { } bool operator <(const Edge & e) const { return w > e.w; //在队列里,边权值越小越优先 } }; vector< vector <Edge> > G(110); //图的邻接表