图的应用——如何构造最小生成树

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算法得到的图的最小生成树

[从今天开始修炼数据结构]图的最小生成树 —— 最清楚易懂的Prim算法和kruskal算法讲解和实现

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

2021.11.14数据结构实验课作业——图的应用(最小生成树和最短路)

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