图的梳理——最小生成树

图 的思维导图在这里插入图片描述

图的基本术语

顶点的度、入度和出度 稠密图和稀疏图 子图 回路或环 完全图
端点(邻接点) 路径和路径长度 连通、连通图和连通分量 强连通图和强连通分量

图———最小生成树

最小代价生成树

在一个连通图中的所有生成树中,存在各边代价之和最小的生成树,则称该生成树为该联通网的最小代价生成树。

MST性质

MST 性质是一些构造最小生成树算法使用的原理。
假设 N = (V,E) 是一个连通网,U 是顶点集 V 的一个非空子集。若 (u,v) 是一条具有最小生成树权值(代价)的边,其中 u ∈ U,v ∈ V - U,则必存在一棵包含边 (u,v) 的最小生成树。
需要注解的是,在生成树构造时,图结构中的 n 个顶点分属于 2 个集合:

已落在生成树上的顶点集:U
尚未落在生成树上的顶点集:V - U
那么我们就需要在所连通的 U 中顶点和 V - U 中顶点的边中选取权值最小的边。

证明
反证法:假设网 N 的任何一棵最小生成树都不包含 (u,v)。设 T 是连通网上的一棵最小生成树,当将边 (u,v) 加入到 T 中时,由生成树的定义,T 中必存在一条包含 (u,v) 的回路。另一方面,由于 T 是生成树,则在 T 上必存在另一条边 (u’,v’),其中 u’ ∈ U,v’∈ Y - U,且 u 和 u’ 之间、v 和 v’ 之间均有路径相通。删去边 (u’,v’),便可消除上述回路,同时得到另一棵生成树 T’。因为 (u,v) 的权值不高于 (u’,v’),则 T 的权值亦不高于 T,T’ 是包含 (u’,v’) 的一棵最小生成树。由此和假设矛盾。 ——《数据结构(C语言版)》

Prim 算法

该算法时间复杂度为 O(n2),与图结构的边数无关,适合稠密图

算法流程
假设 N = (V,E) 是连通网,TE 是 N 上最小生成树中边的结合。
U = {u0}(u0 ∈ V),TE = {};
在所有 u ∈ U,v ∈ V - U 的边 (u,v)∈ E 中找到一条权值最小的边 (u0,v0) 并入集合 TE,同时 v0 并入 U;
重复第二步,直至 U = V 为止。
每次选择最小边的时候,可能存在多条同样权值的边可选,不要犹豫任选其一就行。

算法步骤
首先将初始顶点 u 加入 U 中,对其每一个顶点 vj,将 lowcost 数组和 adjvex 数组初始化为到 u 的边信息。
接着循环 n - 1 次,每次循环进行如下操作:

从各个边中选出最小的边 lowcost[k] 选出;
将 k 顶点加入 U 中;
更新剩余每组最小边的信息,对于 V-U 中的边,若存在新的边权值比现有边更小,则更新 lowcost 数组为新的权值。

代码实现

#define INFINITY 65535
void MiniSpanTree_Prim(MGraph G)
{
    int min;
    int i,j,k;
    int adjvex[MAXV];    //保存相关顶点下标,存贮最小边在 U 中的顶点。
    int lowcost[MAXV];    //保存相关顶点间边的权值,存储最小边上的权值;
    
    lowcost[0] = 0;    //v0 加入生成树
    adjvex[0] = 0;    //初始化第一个顶点下标为 0
    for(i = 1; i < G.n; i++)    //初始化,循环除下标 0 外的所有顶点
    {
        lowcost[i] = G.edges[0][i];    //将 v0 顶点存在边的权值存入 lowcost
        adjvex[i] = 0;    //初始化都为 v0 的下标
    }
    for(i = 1; i < G.n; i++)
    {
        min = INFINITY;    //初始化最小权值为 ∞
        j = 1;
        k = 0;
        while(j < G.n)    //循环全部顶点
        {
            if(lowcost[j] != 0 && lowcost[j] < min)
            {                //若权值不为 0 且小于 min
                min = lowcost[j];    //令当前权值为最小值
                k = j;    //当前最小值的下标拷贝给 k
            }
            j++;
        }
        cout << adjvex[k] << k;    //输出当前顶点边中权值最小边
        lowcost[k] = 0;    //将顶点权值设为 0 表示添加入生成树
        for(j = 1; j < G.n; j++)
        {
            if(lowcost[j] != 0 && G.edges[k][j] < lowcost[j])
            {         //若下标为 k 的顶点各边权值小于当前顶点未被加入生成树权值
                lowcost[j] = G.edges[k][j];    //将最小权值存入 lowcost
                adjvex[j] = k;    //将下标为 k 的顶点存入 adjvex
            }
        }
    }
}

Kruskal 算法

假若以“堆”结构来存放边进行堆排序,对于包含 e 条边的网,上述算法排序的时间复杂度为 O(e㏒2e)。只要采取合适的数据结构,算法的时间复杂度为 O(e㏒2e)。与普里姆算法相比,该算法更适合于求稀疏图的最小生成树。

算法流程
假设 N = (V,E) 是连通网,将 N 中的边按权值从小到大的顺序排列。

初始状态只有 n 个顶点而没有边的非连通图 T = (V,{}),图中每个顶点都自成一个连通分量;
在 E 中选择权值最小的边,若该边依附的顶点落在 T 中不同的连通分量上(不形成回路),则将此边加入到 T 中,否则不添加而选择下一条权值最小的边;
重复步骤 2,直至 T 中的所有顶点都在同一连通分量上为止。

算法步骤
首先将边集数组中的元素按权值从小到大排序。
接着依次检查边集数组的所有边,做如下操作:

从边集数组中选择一条边 (U1,U2);
在顶点数组中分别查找 v1 和 v2 所在的连通分量 vs1 和 vs2,进行判断。若 vs1 和 vs2 不相等,表明所选的两个顶点分别属于不同的连通分量,则合并连通分量 vs1 和 vs2。若相等,表明所选的两个顶点属于同一个连通分量属于同一个连通分量,舍弃这个边,选择下一条权值最小的边。

代码实现

void MiniSpanTree_Kruskal(MGraph G)
{
    int i,n,m;
    Edge edges[MAXE];    //定义边集数组
    int parent[MAXV];    //判断回路的并查集
 
    for(i = 0; i < G.n; i++)
    {
        n = Find(parent, edges[i].begin);    //“查”操作
        m = Find(parent, edges[i].end);
        if(m != n)    //若 n 和 m 不相等,说明回路不存在
        {
            parent[n] = m;    //将此边结尾顶点放入下标为起点的 parent 中
            cout << edges[i].begin <<  edges[i].end << edges[i].weight << endl;
        }
    }
}
 
int Find(int *parent, int f)    //“查”操作
{
    while(parent[f] > 0)
        f = parent[f];
    return f;
}

实例:公路村村通
实例:通信网络设计

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值