最小生成树-贪心(Prim算法(普里姆算法))

问题描述

 设G = (V, E)是一个无向连通带权图,即一个网络。E的每条边(v, w)的权为c[v][w]。

 如果G的一个子图G’是一棵包含G的所有顶点的树,则称G’为G的生成树。

 生成树的各边权的总和称为该生成树的耗费

 在G的所有生成树中,耗费最小的生成树称为G的最小生成树MST(minimum spanning tree) 。


算法思想

  • 在保证连通的前提下依次选出权重较小的n – 1条边。
  • G=(V, E)为无向连通带权图,令V={1, 2, …, n}。
  • 设置一个集合S ,初始化S = {1},生成树T = Φ。
  • 贪心策略:如果V–S中的顶点j与S中的某个点i连接, 且(i, j)是E中的权重最小的边,则选择j(将j加入S),并将(i, j) 加入生成树T中。
  • 重复执行贪心策略,直至V–S为空。

Prim算法中的数据结构

在这里插入图片描述

  • 图用连接矩阵C[i][j]给出,即C[i][j]为结点i到结点j的权重。
  • 为了有效地找出V–S中满足与S中的某个点i连接且(i, j) 权重最小的顶点j,对其中的每个顶点j设立两个数组closest[j]和lowcost[j]:
  • 对于每一个j ∈ V–S , 定义closest[j] 数组,closest[j]是j 在S中的邻居邻接顶点,它与j在S中的其它邻居节点k比较,有c[j][closest[j]]c[j][k];定义lowcost[j]数组, lowcost[j]的值就是c[j][closest[j]]。
  • Prim算法执行过程中每一个j ∈ V–S,先找出使得lowcost[j]最小的顶点j,根据数组closest[j]选取边(j, closest[j] ),最后,将j添加到S中。

核心算法

Prim(int n, Type **c) {
	int j = 1; s[j] = true; 
	for (int i = 2; i <= n;  i++) { 
	closest[i] = 1; lowcost[i]=c[1][i]; s[i]=false;}
	for (int i = 1; i < n;  i++) {min= inf; 
	for (int k = 2; k <= n;  k++) {
	    if (lowcost[k]<min&&!s[k])
	       {min = lowcost[k]; j = k} 
	s[j] = true;                         
	for (int k = 2; k <= n;  k++) {
	    if (c[j][k]< lowcost[k]&&!s[k])
	       {lowcost[k] = c[j][k]; closest[k] = j}
      }
} 

例子

 给定一个连通带权图如下:
          在这里插入图片描述

 假如从顶点A出发,顶点 B、C、D 到顶点 A 的权值分别为 2、4、2,所以,对于顶点 A 来说,顶点 B 和顶点 D 到 A 的权值最小,假设先找到的顶点 B:
              在这里插入图片描述
 继续分析顶点 C 和 D,顶点 C 到 B 的权值为 3,到 A 的权值为 4;顶点 D 到 A 的权值为 2,到 B 的权值为无穷大(如果之间没有直接通路,设定权值为无穷大)。所以顶点 D 到 A 的权值最小:

              在这里插入图片描述

 最后,只剩下顶点 C,到 A 的权值为 4,到 B 的权值和到 D 的权值一样大,为 3。所以该连通图有两个最小生成树:
          在这里插入图片描述


完整代码

#include <bits/stdc++.h>
using namespace std;
const int max_ = 0x3f3f3f;
int Graph[110][110];
int visited[110];
int closest[110];
int prepos[110]; //记录到当前节点最小距离的节点代号
int pointnum, edgenum;
int FindMinLen()
{
    int pos, min_ = max_;
    for(int i = 1; i <= pointnum; ++i)
        if(min_ > closest[i] && closest[i])
        {
            min_ = closest[i];
            pos = i;
        }
    return pos;
}
void Prim()
{
    //初始化
    for(int i = 2; i <= pointnum; ++i)
    {
        closest[i] = Graph[1][i];
        prepos[i] = 1;
    }
    closest[1] = 0;
    for(int i = 1; i < pointnum; ++i)
    {
        int pos;
        pos = FindMinLen();
        closest[pos] = 0; //赋0
        //更新表
        for(int j = 2; j <= pointnum; ++j)
        {
            if(closest[j] > Graph[pos][j])
            {
             closest[j] = Graph[pos][j];
             prepos[j] = pos;
            }
        }
        printf("%d -> %d\n", prepos[pos], pos);
    }
}
void InPut()
{
    int pos1, pos2, len;
    scanf("%d %d", &pointnum, &edgenum);
    memset(Graph, max_, sizeof(Graph));
    for(int i = 1; i <= edgenum; ++i)
    {
        scanf("%d %d %d", &pos1, &pos2, &len);
        Graph[pos1][pos2] = len;
        Graph[pos2][pos1] = len;
    }
}
int main()
{
    InPut();
    Prim();
}

测试用例

输入
4 5
1 2 2
1 3 2
1 4 4
2 4 3
3 4 3

输出
1 -> 2
1 -> 3
2 -> 4

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值