图的最小生成树算法?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图的最小生成树算法?相关的知识,希望对你有一定的参考价值。

数据结构

图的生成树和最小生成树生成树(SpanningTree):如果一个图的子图是一个包含图所有节点的树,那这个子图就称为生成树. 参考技术A 普利姆(prim)算法:
定义
普利姆(Prim)算法求最小生成树,也就是在包含n个顶点的连通图中,找出只有(n-1)条边包含所有n个顶点的连通子图,也就是所谓的极小连通子图。

普利姆的算法如下:

图的顶点集合为V,设置一个顶点集合new_V,很明显new_V是V的一部分,设另一部分为other_V。

在new_V顶点与other_V顶点形成的所有边中,选择权值最小的一条,将该边的在other_V中的顶点移至new_V中,重复操作,直到new_V集合等于V集合,此时other_V为空,操作结束。
import java.util.*;
class t
public static void main(String[] args)
char[] vertex=new char[]'A','B','C','D','E','F','G'; //顶点数组
int[][] ad_matrix=new int[7][]; //邻接矩阵
final int N=65535;
ad_matrix[0]=new int[]N,5,7,N,N,N,2;
ad_matrix[1]=new int[]5,N,N,9,N,N,3;
ad_matrix[2]=new int[]7,N,N,N,8,N,N;
ad_matrix[3]=new int[]N,9,N,N,N,4,N;
ad_matrix[4]=new int[]N,N,8,N,N,5,4;
ad_matrix[5]=new int[]N,N,N,4,5,N,6;
ad_matrix[6]=new int[]2,3,N,N,4,6,N;
Graph G=new Graph(vertex,ad_matrix); //构建图对象
//上述为测试用例
G.minimal();//最小生成树


class Graph
private char[] vertex; //顶点数组
private int[][] ad_matrix; //邻接矩阵
private Vi_vertex vi;//已访问顶点集合
public Graph(char[] vertex,int[][] ad_matrix)
this.vertex=vertex;
this.ad_matrix=ad_matrix;
this.vi=new Vi_vertex(vertex.length);

public void minimal()//最小生成树
vi.add(0);//选择下标为0的顶点作为出发顶点
while(!vi.done())
prim();


private void prim()//普利姆算法
int min=65535,index_x=0,index_y=0;
for(int i=0;i<vi.length();i++)
index_x=vi.get(i);
for(int j=0;j<ad_matrix[index_x].length;j++)
if(!vi.in(j)&&ad_matrix[index_x][j]<min)//顶点未被访问且边长较小
min=ad_matrix[index_x][j];//这里没有统计最小生成树的权值
index_y=j;//记录最小边位置



vi.add(index_y);
System.out.println(vertex[index_y]);


class Vi_vertex //已访问顶点集合
private int[] visited;//已经存在的顶点集合
private int index=0;//顶点集合容量下标
public Vi_vertex(int length)
visited=new int[length];

public int length()
return index;

public boolean in(int f)//判断顶点是否在已存在集合中
int i=0;
while(i<index)
if(visited[i]==f)
return true;

i++;

return false;

public void add(int f)//添加顶点到集合中
visited[index++]=f;

public boolean done()//判断是否操作结束
return index==visited.length;//集合中保存所有顶点

public int get(int i)
return visited[i];


————————————————
克鲁斯卡尔(kruskal)算法:
基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路。
具体做法:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止。

#include "stdio.h"
#include "stdlib.h"
#define MAX_VERtEX_NUM 20
#define VertexType int
typedef struct edge
VertexType initial;
VertexType end;
VertexType weight;
edge[MAX_VERtEX_NUM];
//定义辅助数组
typedef struct
VertexType value;//顶点数据
int sign;//每个顶点所属的集合
assist[MAX_VERtEX_NUM];

assist assists;

//qsort排序函数中使用,使edges结构体中的边按照权值大小升序排序
int cmp(const void *a,const void*b)
return ((struct edge*)a)->weight-((struct edge*)b)->weight;

//初始化连通网
void CreateUDN(edge *edges,int *vexnum,int *arcnum)
printf("输入连通网的边数:\n");
scanf("%d %d",&(*vexnum),&(*arcnum));
printf("输入连通网的顶点:\n");
for (int i=0; i<(*vexnum); i++)
scanf("%d",&(assists[i].value));
assists[i].sign=i;

printf("输入各边的起始点和终点及权重:\n");
for (int i=0 ; i<(*arcnum); i++)
scanf("%d,%d,%d",&(*edges)[i].initial,&(*edges)[i].end,&(*edges)[i].weight);


//在assists数组中找到顶点point对应的位置下标
int Locatevex(int vexnum,int point)
for (int i=0; i<vexnum; i++)
if (assists[i].value==point)
return i;


return -1;

int main()

int arcnum,vexnum;
edge edges;
CreateUDN(&edges,&vexnum,&arcnum);
//对连通网中的所有边进行升序排序,结果仍保存在edges数组中
qsort(edges, arcnum, sizeof(edges[0]), cmp);
//创建一个空的结构体数组,用于存放最小生成树
edge minTree;
//设置一个用于记录最小生成树中边的数量的常量
int num=0;
//遍历所有的边
for (int i=0; i<arcnum; i++)
//找到边的起始顶点和结束顶点在数组assists中的位置
int initial=Locatevex(vexnum, edges[i].initial);
int end=Locatevex(vexnum, edges[i].end);
//如果顶点位置存在且顶点的标记不同,说明不在一个集合中,不会产生回路
if (initial!=-1&& end!=-1&&assists[initial].sign!=assists[end].sign)
//记录该边,作为最小生成树的组成部分
minTree[num]=edges[i];
//计数+1
num++;
//将新加入生成树的顶点标记全不更改为一样的
for (int k=0; k<vexnum; k++)
if (assists[k].sign==assists[end].sign)
assists[k].sign=assists[initial].sign;


//如果选择的边的数量和顶点数相差1,证明最小生成树已经形成,退出循环
if (num==vexnum-1)
break;



//输出语句
for (int i=0; i<vexnum-1; i++)
printf("%d,%d\n",minTree[i].initial,minTree[i].end);

return 0;
参考技术B 求图的最小生成树的算法有两种:
1.Kruskal算法(相对最好)
此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。
1. 把图中的所有边按代价从小到大排序;
2. 把图中的n个顶点看成独立的n棵树组成的森林;
3. 按权值从小到大选择边,所选的边连接的两个顶点ui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。
4. 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。

2.Prim算法(基于贪心)
此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。
图的所有顶点集合为V;初始令集合u=s,v=V−u;在两个集合u,v能够组成的边中,选择一条代价最小的边(u0,v0),加入到最小生成树中,并把v0并入到集合u中。
重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。
由于不断向集合u中加点,所以最小代价边必须同步更新;需要建立一个辅助数组closedge,用来维护集合v中每个顶点与集合u中最小代价边信息。

图的应用(最小生成树,拓扑排序)

介绍

应用图解决现实问题是我们使用图这种数据结构的原因所在。
最小生成树是图的应用中很常见的一个概念,一个图的最小生成树不是唯一的,但最小生成树的边的权值之和纵使唯一的。最小生成树的算法主要有Prim算法和Kruskal算法。这两种算法都是基于贪心算法策略(只考虑眼前的最佳利益,而不考虑整体的效率)。
拓扑排序是指由一个有向无环图的顶点组成的序列,此序列满足以下条件:

  1. 每个顶点出现且仅出现一次
  2. 若顶点A在序列中排在顶点B之前,则图中不存在顶点B到顶点A的路径。

最小生成树

Prim算法

Prim算法非常类似与寻找图的最短路径的Dijkstra算法。
算法思路:

  1. 首先将图的任一节点加如树中
  2. 之后选择一个与当前顶点最近的节点接入树中。
  3. 循环 2直到所有节点均被接入树中。

Prim算法的时间复杂度是O(V*V),不依赖于E,因此他适合边稠密的图的最小生成树。

Kruskal算法

克鲁斯卡尔算法是一种按权值的递增次序选择合适的边来构造最小生成树的方法。
算法思路:

  1. 初始时只有n个顶点而无边的非连通图,每个顶点自成一个连通分量
  2. 按照边的权值由小到大,不断选取当前未被选取过且权值最小的边
  3. 若该边依附的顶点落在图的不同的连通分量,则将该边加入树中,否则舍去,直到所有顶点都在一个连通分量。

Kruskal的时间复杂度为O(Elog2E),因此此算法适合构造边稀疏而顶点稠密的图的最小生成树。

拓扑排序

对一个AOV网进行拓扑排序的算法有很多,下面介绍一种。

  1. 从AOV网中选择一个没有前驱的节点进行输出
  2. 从网中删除该顶点和所有以它为起点的有向边
  3. 重复1和2,直到当前的AOV网为空,或者当前网中不存在无前驱的顶点为止。后面一种情况说明有向图中必然存在环。

由于输出每个顶点的同时还要删除以它为起点的边,故采用邻接表存储拓扑排序的时间复杂度为O(V+E)。采用邻接矩阵存储拓扑排序的时间复杂度是O(V*V)。
注;若一个顶点有多个直接后继,则拓扑排序的结果通常不唯一。

后续

公众号:物联网知识

以上是关于图的最小生成树算法?的主要内容,如果未能解决你的问题,请参考以下文章

图的相关算法(二):最小生成树算法

图的最小生成树(普利姆prim算法)

最小生成树-kruskal算法

图的最小生成树——Prim算法

图的应用——最小生成树

无向带权图的最小生成树算法——Prim及Kruskal算法思路