【算法复习】图的最小生成树(Prim&Kruskal)
2012-10-31 23:25
501 查看
所谓生成树就是
如果连通图G的一个子图是一棵包含G的所有顶点的树,则该子图称为G的生成树。
生成树是连通图的包含图中的所有顶点的极小连通子图。
(图的生成树不惟一。从不同的顶点出发进行遍历,可以得到不同的生成树)
而权值最小的树就是最小生成树。
关于生成树最经典的应用模型就是沟通零散点最小造价的问题,
比如网络G表示n各城市之间的通信线路网线路(其中顶点表示城市,边表示两个城市之间的通信线路,边上的权值表示线路的长度或造价)
通过求该网络的最小生成树达到求解通信线路或总代价最小的最佳方案
求图的最小生成树主要有两种经典算法:
1.普里姆算法
时间复杂度为O(n2).适合于求边稠密的最小生成树。
2.克鲁斯卡尔算法
时间复杂度为O(eloge)(e为网中边数),适合于求稀疏的网的最小生成树。
普里姆算法
通过邻接矩阵图表示的简易实现中,找到所有最小权边共需O(V2)的运行时间。使用简单的二叉堆与邻接表来表示的话,普里姆算法的运行时间则可缩减为O(E log V),其中E为连通图的边数,V为顶点数。如果使用较为复杂的斐波那契堆,则可将运行时间进一步缩短为O(E + V log V),这在连通图足够密集时(当E满足Ω(V log V)条件时),可较显著地提高运行速度。
算法思想:取图中任意一个顶点V作为生成树的根,之后若要往生成树上添加顶点W,则在顶点V和W之间必定存在一条边。并且该边的权值在所有连通顶点V和W之间的边中取值最小。
以下代码采用的数据存储方式为邻接矩阵
克鲁斯克尔算法
按权值递增次序选择合适的边来构造最小生成树。先把所有的顶点包括在生成树中,将图中的边按权值递增的顺序依次选取,要保证选取的边不使生成树中产生回路,将选取的边加到生成树中,直至有n-1条边为止。
算法思想:设图G=(V,E)是一个具有n个顶点的连通网,T=(U,TE)是图G的最小生成树,初始状态:U=V,TE为空。将图中所有边按权值从小到大次序排列,顺次逐条检测这些边,若将此边加入到生成树中形成回路,则舍弃,否则将此边加入到TE中。直到选中n-1条边。
主要步骤就是根据边来添加点,添加时需判断是否会形成环,所以主要就是两个问题需要解决,怎么方便通过边来添加,还有就是如何判断是否会产生环。
所以可以用边集结构来解决,遍历图,计算出边集数组,索引值小的为起点,大的为终点,再根据权值来排序,
判断是否会产生环则利用下面的find函数比较方便
以上只是粗略的完成两个算法的思想,有很多不严谨的地方,但本文重心只是两种经典最小生成树的原理。
如果连通图G的一个子图是一棵包含G的所有顶点的树,则该子图称为G的生成树。
生成树是连通图的包含图中的所有顶点的极小连通子图。
(图的生成树不惟一。从不同的顶点出发进行遍历,可以得到不同的生成树)
而权值最小的树就是最小生成树。
关于生成树最经典的应用模型就是沟通零散点最小造价的问题,
比如网络G表示n各城市之间的通信线路网线路(其中顶点表示城市,边表示两个城市之间的通信线路,边上的权值表示线路的长度或造价)
通过求该网络的最小生成树达到求解通信线路或总代价最小的最佳方案
求图的最小生成树主要有两种经典算法:
1.普里姆算法
时间复杂度为O(n2).适合于求边稠密的最小生成树。
2.克鲁斯卡尔算法
时间复杂度为O(eloge)(e为网中边数),适合于求稀疏的网的最小生成树。
普里姆算法
通过邻接矩阵图表示的简易实现中,找到所有最小权边共需O(V2)的运行时间。使用简单的二叉堆与邻接表来表示的话,普里姆算法的运行时间则可缩减为O(E log V),其中E为连通图的边数,V为顶点数。如果使用较为复杂的斐波那契堆,则可将运行时间进一步缩短为O(E + V log V),这在连通图足够密集时(当E满足Ω(V log V)条件时),可较显著地提高运行速度。
算法思想:取图中任意一个顶点V作为生成树的根,之后若要往生成树上添加顶点W,则在顶点V和W之间必定存在一条边。并且该边的权值在所有连通顶点V和W之间的边中取值最小。
以下代码采用的数据存储方式为邻接矩阵
#include <iostream> #define MAX 65535 #define MAXVEX 20 using namespace std; typedef struct { int arcs[MAXVEX][MAXVEX]; int vexnum,arcnum; }MGraph; void minTree_Prim(MGraph &G,int* minCost,int* addVertex) { int lowCost[MAXVEX]; //lowCost数组始终存放索引对应顶点到其他边的最小权值,随时修正权值情况 addVertex[0] = 0; //初始化第一个顶点 addVertext数组索引对应顶点存放边的另一个顶点 lowCost[0] = 0; minCost[0] = 0; for(int i=1; i<G.vexnum;i++) { addVertex[i]=0; lowCost[i] = G.arcs[0][i]; } int min = MAX; for(int i=1; i<G.vexnum;i++) { min = MAX; int k =0; for(int j=1;j<G.vexnum;j++) { if(lowCost[j]&& min>lowCost[j]) //lowCost为0时索引值对应顶点已添加到生成树中 { min = lowCost[j]; k = j; } } cout<<"("<<addVertex[k]<<","<<k<<")"<<endl; //找出了最小的点 minCost[k] = lowCost[k]; lowCost[k] =0; for(int j=1;j<G.vexnum;j++) { if(lowCost[j] && lowCost[j]>G.arcs[k][j]) //修正lowCost数组 { lowCost[j] = G.arcs[k][j]; addVertex[j] = k; } } } } int main() { MGraph myGraph; int arcs[MAXVEX][MAXVEX] ={0}; arcs[0][1] = 6; arcs[0][2] = 1; arcs[0][3] = 5; arcs[1][0] = 6; arcs[1][2] = 5; arcs[1][4] = 3; arcs[2][0] = 1; arcs[2][1] = 5; arcs[2][3] = 5; arcs[2][4] = 6; arcs[2][5] = 4; arcs[3][0] = 5; arcs[3][2] = 5; arcs[3][5] = 2; arcs[4][1] = 3; arcs[4][2] = 6; arcs[4][5] = 6; arcs[5][2] = 4; arcs[5][3] = 2; arcs[5][4] = 6; for(int i =0;i<6;i++) { for(int j =0;j<6;j++) { if(!arcs[i][j]) arcs[i][j] = MAX; myGraph.arcs[i][j] = arcs[i][j]; cout<<myGraph.arcs[i][j]<<" "; } cout<<endl; } myGraph.vexnum = 6; int* minCost = (int*)malloc(sizeof(int)*MAX); int* vertexTree = (int*)malloc(sizeof(int)*MAX); minTree_Prim(myGraph,minCost,vertexTree); for(int i = 0;i<6;i++) cout<<minCost[i]<<endl; return 0; }
克鲁斯克尔算法
按权值递增次序选择合适的边来构造最小生成树。先把所有的顶点包括在生成树中,将图中的边按权值递增的顺序依次选取,要保证选取的边不使生成树中产生回路,将选取的边加到生成树中,直至有n-1条边为止。
算法思想:设图G=(V,E)是一个具有n个顶点的连通网,T=(U,TE)是图G的最小生成树,初始状态:U=V,TE为空。将图中所有边按权值从小到大次序排列,顺次逐条检测这些边,若将此边加入到生成树中形成回路,则舍弃,否则将此边加入到TE中。直到选中n-1条边。
主要步骤就是根据边来添加点,添加时需判断是否会形成环,所以主要就是两个问题需要解决,怎么方便通过边来添加,还有就是如何判断是否会产生环。
所以可以用边集结构来解决,遍历图,计算出边集数组,索引值小的为起点,大的为终点,再根据权值来排序,
判断是否会产生环则利用下面的find函数比较方便
typedef struct { int begin; int end; int weight; }Edge; //定义边集结构 void sort_edges(Edge& edge,Edge& edge2) //交换大小,为后面排序所用 { Edge edge_temp; edge_temp.begin = edge.begin; edge_temp.end = edge.end; edge_temp.weight = edge.weight; edge.begin = edge2.begin; edge.end = edge2.end; edge.weight = edge2.weight; edge2.begin = edge_temp.begin; edge2.end = edge_temp.end; edge2.weight = edge_temp.weight; } void turn_to_edges(MGraph &G,Edge* edges) //讲邻接矩阵转换成边集数组 { int k=0; for(int i=0;i<G.vexnum;++i) { for(int j=0;j<G.vexnum;++j) { if( (G.arcs[i][j] != MAX) && (i<j)) { edges[k].begin = i; edges[k].end=j; edges[k].weight=G.arcs[i][j]; k++; } } } k=G.edgenum-1; for(int i=0;i<G.edgenum;++i) //讲转换的边集数组按从小到大的顺序排列 { int weight=0,index=0; for(int j= 0;j<k;++j) { if(edges[j].weight>weight) { weight = edges[j].weight; index = j; } } sort_edges(edges[index],edges[k]); --k; } } int find(int* parent,int num) //查找已添加进树中的顶点最后尾部索引 { while(parent[num]) num = parent[num]; return num; } void minTree_kruskal(MGraph &G) { int *parent = (int*)malloc(sizeof(int)*G.vexnum); Edge *edges = (Edge*)malloc(sizeof(Edge)*G.edgenum); turn_to_edges(G,edges); for(int i=0;i<G.vexnum;++i) parent[i]=0; for(int i=0;i<G.edgenum;++i) { int n = find(parent,edges[i].begin); int m = find(parent,edges[i].end); if(m!=n) //若相等,则添加此边会形成环 { parent =m; printf("(%d,%d) %d",edges[i].begin,edges[i].end,edges[i].weight); } } }
以上只是粗略的完成两个算法的思想,有很多不严谨的地方,但本文重心只是两种经典最小生成树的原理。
相关文章推荐
- 算法复习 - 最小生成树算法 Prim、Kruskal(普里姆算法、 克鲁斯卡尔算法)
- 最小生成树算法(prim&kruskal)
- 算法记录---最小生成树【kruskal&&prim】
- zoj 1203 Swordfish 【最小生成树 kruskal && prim】
- hdoj 1875 畅通工程再续【最小生成树 kruskal && prim】
- zoj 1586 QS Network【最小生成树 kruskal && prim】
- 最小生成树算法介绍(转载)Prim+Kruskal
- 数据结构之最小生成树的Prim和Kruskal`s 算法
- POJ 1789 Truck History 最小生成树 kruskal && Prim
- 最小生成树之 prim & kruskal
- 最小生成树 Prim(普里姆)算法和Kruskal(克鲁斯特尔)算法
- hdu 1863 畅通工程 最小生成树模板入门题 prim+kruskal两种算法AC。
- hdoj 1162 Eddy's picture【最小生成树 kruskal && prim】
- kruskal与prim最小生成树算法
- hdoj 1875 畅通project再续【最小生成树 kruskal && prim】
- POJ 1258 Agri-Net 最小生成树 prim && kruskal
- 最小生成树算法(Prim和Kruskal)
- 最小生成树两个重要的算法:Prim 和 Kruskal
- 最小生成树模板【kruskal & prim】
- hdu 1162 Eddy's picture 最小生成树入门题 Prim+Kruskal两种算法AC