【算法复习】图的最小生成树(Prim&Kruskal)
前言
图的最小生成树算法主要有两种,Prim算法和Kruskal算法。实质上,这两种算法的思想都不难理解,但是在实际编程中,很容易将数据结构弄的过于复杂,增加无谓的开销。下面的文章是我这次回过头来整理的时候,看见的构造算是比较巧妙的一篇(主要是kruskal算法的实现部分),虽然有些地方我感觉有点冗余,但总体来说是值得学习的。
原文地址
http://blog.csdn.net/qjzl2008/article/details/8008077
原文内容
所谓生成树就是
如果连通图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[n]=m;
printf("(%d,%d) %d",edges[i].begin,edges[i].end,edges[i].weight);
}
}
}
以上只是粗略的完成两个算法的思想,有很多不严谨的地方,但本文重心只是两种经典最小生成树的原理。上一篇: CentOS 7安装MySQL的详细步骤
下一篇: 分割超大Redis数据库例子
推荐阅读
-
JS使用Prim算法和Kruskal算法实现最小生成树
-
数据结构(C实现)------- 最小生成树之Kruskal算法
-
python最小生成树kruskal与prim算法详解
-
最小生成树两个经典算法(Prime算法、Kruskal算法) - biaobiao88
-
最小生成树的两种方法(Kruskal算法和Prim算法)
-
最小生成树---Kruskal算法
-
【代码超详解】POJ 2031 Building a Space Station(Kruskal 算法求最小生成树)
-
图的最小生成树:普利姆算法、克鲁斯卡尔算法
-
洛谷P3366【模板】最小生成树-克鲁斯卡尔Kruskal算法详解附赠习题
-
图的最小生成树应用——洛谷题单