最小生成树算法(普里姆算法和克鲁斯卡尔算法)

什么是生成树?

一个连通图的生成树是一个极小的连通子图,它含有图中全部的n个顶点,但是只有足以构成一棵树的n-1条边。

理解:

  1. 连通图是属于无向图的范畴,有向图的连通子图叫强连通图
  2. 它含有n个全部顶点,只有n-1条,将n个顶点连起来至少要n-1条边
  3. 少于n-1条边连不起来,那么则无法连通。比如10个点直线连起来至少中间要有9条边
  4. 多于n-1条边会形成环,是连通图,但是不是极小的连通子图。且一棵树肯定是没有环的,多于n-1条边那就不是树了
  5. 必须含有n个顶点,且不能多于n-1条边,也不能少于n-1条边
  6. 故一个连通图的生成树是一个极小的连通子图

最小生成树:

  • 最小生成树来自于无向网。
  • 无向图在边上加上权值就成了无向网。
  • 一个无向图可以有多种不同姿态连接的生成树。
  • 最小生成树就是--各边上权值之和最小的生成树。

本文分别介绍两种算法:普里姆算法(prim)和克鲁斯卡尔算法(Kruska)


 

1.普利姆算法(prim)

用邻接矩阵存储无向网:

typedef char Vertextype;
typedef int  EdgeType;
#define MAXVEX 100
#define INFINITY 65535
typedef struct
{
    VertexType vexs[MAXVEX];
    EdgeType arc[MAXVEX][MAXVEX];
    int numVertexs,numEdges;
}MGraph;

设N=(V, {E})为一个无向网,V为顶点集,{E}为边集,U={}为空的顶点集,TE={}空的边集

lowcost[maxvwx]:lowcost[i]表示对于V_{i}\in U-V,对任意U_{i}\in U,从V_{i}U_{i}的边的最小权值; 对于V_{i}\in U,lowcost[i]=0
例如:初始化时将V_{0}加入U,则对于V_{0}\in U,lowcost[0]=0;若对于V_{i}\in U-VU_{j}\in U,若存在权值最小为W_{ij}的边(U_{j}V_{i}),则lowcost[i]=W_{ij}

adjvex[maxvex]:  adjvex[i]表示顶点V_{i}由lowcost[i]权值所代表的边连接到U中顶点的位置

例如:若存在lowcost[i]=W_{ij},则adjvex[i]=j

大致算法:

  1. 初始将V_{0}加入U,使U={V_{0}},TE={},k=0,V_{k}=V_{0}
  2. 对于V_{k}\in U,更新lowcost[i],adjvex[j]
  3. 从lowcost[i]中选择最小权值的边,将lowcost[i]=0,对于V_{i}\in U-V,将V_{i}加入U, adjvex[i]=j,将(U_{j}V_{i})加入到TE
  4. V_{k}=V_{i},重复上述2~4,知道U=V

无向图和相应的邻接矩阵:

最小生成树算法(普里姆算法和克鲁斯卡尔算法)_第1张图片

 手撸算法图解:

最小生成树算法(普里姆算法和克鲁斯卡尔算法)_第2张图片 最小生成树算法(普里姆算法和克鲁斯卡尔算法)_第3张图片

最小生成树算法(普里姆算法和克鲁斯卡尔算法)_第4张图片最小生成树算法(普里姆算法和克鲁斯卡尔算法)_第5张图片

代码:

void MiniSpanTree_Prim(MGraph G)
{
    int adjvex[MAXVEX];
    int lowcost[MAXVEX];
    adjvex[0]=0;
    lowcost[0]=0;//lowcost[i]=0,代表此下标的顶点加入生成树,此处将V0加入生成树,进行初始化
    for(int i=1;i

2.克鲁斯卡尔算法(Kruskal)

大致算法思想:假设连通网N=(V,{E}),则令最小生成树的初始状态为只有n个顶点而没有边的非连通图T=(V,{}),图中每个顶点自成一个连通分量。在{E}中选择权值代价最小的边,若该边依附的顶点落在T中的不同的连通分量中,则将此边加入到T中,否则舍弃此边而选择下一条代价最小的边。直到所有的顶点都在同一个连通分量上。

此算法用图的边集数组结构:

typedef struct
{
    int begin;//边开始的顶点位置
    int end;//边结尾的顶点位置
    int weight;//边上的权值
}Edge;

示例无向网及其边集数组:

最小生成树算法(普里姆算法和克鲁斯卡尔算法)_第6张图片

算法步骤图解:

 

最小生成树算法(普里姆算法和克鲁斯卡尔算法)_第7张图片最小生成树算法(普里姆算法和克鲁斯卡尔算法)_第8张图片

最小生成树算法(普里姆算法和克鲁斯卡尔算法)_第9张图片 最小生成树算法(普里姆算法和克鲁斯卡尔算法)_第10张图片

void MiniSpan_Kruskal(MGraph G)
{
    Edge edges[MAXEDGE];//定义边集数组,这里假设已经将邻接矩阵转换成了边集数组,省略此处代码
    int  Same[MAXVEX];//定义Same数组来判断点与点是否属于同一连通分量
    for(int i=0;i0)
        f=Same[f];
    return f;
}//Find函数是为每一个入口寻找出口
//比如为edges[i].begin和edges[i].end入口寻找出口,得到出口分别为m和n
//如果m=n,则出口相同,说明在同一个连通分量
//不相等,则不在同一个连通分量,存在2个不同的出口,需要将一个连通分量的入口连接到另一个连通分量的出口构成一出口,即构成同一个连通分分量

假设对于一个顶点数为n,边数为e的无向网:

普里姆算法是根据顶点来构造最小生成树,通过将一个个顶点加入一个连通分量构成最小生成树,算法复杂度为O(n^2)

克鲁斯卡尔算法是根据不断选择最小权值的边构造生成树,通过判断该边的2个顶点是否属于不同连通分量,若属于不同连通分量,则合并两个不同的连通分量,若属于同一个分量,则舍弃该边,算法复杂度为O(eloge)

适用范围:

普里姆算法针对顶点展开,主要针对稠密图,因为就算稠密图边再多,顶点也就那几个,不能再多

克鲁斯卡尔算法针对边展开,对于稀疏图有很大优势,边数少时效率会非常高

 

你可能感兴趣的:(数据结构与算法,C语言学习)