图的应用——如何构造最小生成树
Posted Rainbowman 0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图的应用——如何构造最小生成树相关的知识,希望对你有一定的参考价值。
1. 图的基本概念
1.什么是图?
图由顶点和边组成,表示为G(V,E),其中G表示一个图,V是图G中顶点的集合,E是图G中边的集合。根据边是否有方向,可分为有向图和无向图。
2. 邻接点
两结点之间通过边相连,则互为邻接点。如在上面的无向图中,(1,3),(2,5)等都为邻接点。
3. 顶点的度
顶点的度指的是与顶点v相连的边的数目。对于无向图来讲,只有度的概念,而对于有向图来讲,可分为入度和出度。入度是指向该顶点的边的条数,出度是从该顶点发出的边的条数。上面的有向图中,顶点1的入度为1,出度为2。
4. 权值
表示从一个顶点到另一个顶点的距离或耗费。
5. 连通性
对于两结点u,v,若通过u可到达v,则u和v是联通的。
在图 G 中,任意的 结点vi,vj∈ V,有 vi,vj 连通,图 G 是连通的。
6. 连通分量
图中的极大联通子图。
2. 什么是最小生成树?
简单来讲,最小生成树就是图中包含全部顶点的极小连通子图。
即:
(1)需要保证包含图中的所有结点;
(2)该图是连通的;
(3)图中的所有边的权值之和最小。
例如
对于该图来讲,加粗的红色的边即为最小生成树。
3. 普利姆算法
思想
(1)最小生成树的一条性质:
设集合V中包含了图中的所有顶点,集合U中包含了图中的部分顶点,现在把集合V分成U和V-U两个集合,u是集合U的一个顶点,v是集合V-U中的一个顶点,如下图所示:
若<u, v>是权值最小的边,则<u, v>边必被包含在最小生成树中。
(2)普利姆算法的思想:
【1】设集合V中包含了图中的所有顶点,集合U一开始为空集,则初始时V-U=V;设集合TE包含了最小生成树的所有边,TE初始时为空;
【2】从V-U集合中随意取出一个顶点放入U中;
【3】从U集合中的顶点(U1,U2,…Un),和V-U集合中的顶点(V1,V2,…Vm)中各选一个,保证边<Ui, Vj>的权值最小;
【4】将边<Ui, Vj>加入集合TE,将顶点Vj从集合V-U加入到集合U;
【5】重复3、4步骤,直到U中包含图中所有结点。
示意图
代码
代码实现时有一点需要注意,我们需要用一个辅助数组CloseEdge来帮助实现“将V-U集合中的顶点加入到U集合中”这一思想。
各部分的解释在注释中有较为详细的说明。
#include <iostream>
#define INFINITY 99999
#define MAX_VEX_NUM 20
using namespace std;
typedef char NumType;
//以下代码为构造图的过程
struct Graph
{
NumType Vex[MAX_VEX_NUM];
int Edge[MAX_VEX_NUM][MAX_VEX_NUM];
int VexNum;
int EdgeNum;
};
int Locate(Graph G,NumType v)
{
int i;
for(i=0;i<G.VexNum;i++)
if(G.Vex[i]==v)return i;
return -1;
}
void CreateGraph(Graph &G)
{
cout<<"请输入结点个数和边的条数:";
cin>>G.VexNum;
cin>>G.EdgeNum;
int i,j,k;
for(i=0;i<G.VexNum;i++)
for(j=0;j<G.VexNum;j++)G.Edge[i][j]=INFINITY;
cout<<"请输入顶点信息:";
for(i=0;i<G.VexNum;i++)cin>>G.Vex[i];
cout<<"请输入边和权值:";
int w;
NumType v1,v2;
for(k=0;k<G.EdgeNum;k++)
{
cin>>v1;cin>>v2;cin>>w;
i=Locate(G,v1);j=Locate(G,v2);
G.Edge[i][j]=w;
G.Edge[j][i]=G.Edge[i][j];
}
}
//以上代码都是构造图的过程(邻接矩阵)
//下面的代码才是最小生成树的算法
struct ClosEdge
{
NumType u;
int w;
};
//CloseEdge的下标存的是V-U集合中的顶点,NumType存的是U集合中的顶点
// w=0意味着该CloseEdge的下标对应的顶点已经在U集合中,否则代表该顶点在V-U集合中
int MiniClosEdge(ClosEdge *closedge,Graph G)//找到从U集合顶点到V-U集合顶点权重最小的边,返回V-U集合中的顶点对应的下标
{
int minn=99999;
int i,j;
for(i=0;i<G.VexNum;i++)
if(minn>closedge[i].w&&closedge[i].w!=0){minn=closedge[i].w;j=i;}
return j;
}
void MiniSpanTree(Graph G)
{
ClosEdge SubArray[MAX_VEX_NUM];
int i;
//一开始U集合中只有下标为零的这一个结点,下两行为初始化步骤
for(i=1;i<G.VexNum;i++)SubArray[i]={G.Vex[0],G.Edge[0][i]};
SubArray[0].w=0;
int k;
for(i=1;i<G.VexNum;i++)//共n-1条边,故i从1开始
{
k=MiniClosEdge(SubArray,G);
cout<<SubArray[k].u<<"-"<<G.Vex[k]<<endl;
SubArray[k].w=0;
int j;
//把与新加入的顶点(从V-U集合加入到U集合)相邻顶点的边的权值加入到辅助向量CloseEdge[]中(更新操作,保证w的值为目前为止从顶点SubArray[k].u到顶点G.Vex[k]最小的)
for(j=0;j<G.VexNum;j++)
if(G.Edge[k][j]<SubArray[j].w)SubArray[j]={G.Vex[k],G.Edge[k][j]};
}
}
int main()
{
Graph G;
CreateGraph(G);
MiniSpanTree(G);
return 0;
}
以上是关于图的应用——如何构造最小生成树的主要内容,如果未能解决你的问题,请参考以下文章
无向带权图的最小生成树算法——Prim及Kruskal算法思路
[从今天开始修炼数据结构]图的最小生成树 —— 最清楚易懂的Prim算法和kruskal算法讲解和实现