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

最小生成树是处理图结构中,简化图的算法;即删除一些边使得图得以简化,但应保证图中任意点都是相连通的。形成的最小生成树应该使得从顶点遍历时走过边的权值和最小。(有n个节点,则最小生成树的边数应为n-1)
如:
在这里插入图片描述
变为最小生成树后:
在这里插入图片描述
处理最小生成树有两种方法:
1.克鲁斯卡尔算法(kruskal):
这种算法是先把所有的边拿出来,按其权值从小到大的顺序排列,然后从最小的边开始还原图,即按该边连接其顶点。从权值值最小的边依次连接,每次连接都要判断本次连接是否形成了环,若是,则改变没有必要还原,当还原到已还原边数为n-1时最小生成树完成。
在这里插入图片描述
在这里插入图片描述
还原步骤:
在这里插入图片描述

若形成环则不还原该边。
c++代码参考代码(用到了并查集这是我对并查集的一些理解):

#include<iostream>
#include<algorithm>
typedef struct//定义边的结构体
{
    int value;
    int node1,node2;
}edge;
#define max_size 20
int parent[20];//用于装节点的根
using namespace std;
int rule(edge a,edge b)//按权值排序边时用
{
    return a.value<b.value;//升序排序
}
int find_root(int v)//找根节点函数
{
    if(parent[v]==-1) return v;
    else
    {
        parent[v]=find_root(parent[v]);//压缩找父节点的路径
        return parent[v];
    }
}
int is_cycle(int v1,int v2)//判断是否成环
{
    int v1_root=find_root(v1);
    int v2_root=find_root(v2);
    if(v1_root==v2_root) return 1;//两个节点的根相同,若在连接两个节点则会形成环
    else
    {
        parent[v1_root]=v2_root;//连接起来,这里最好根据情况 选择把v1或v2的根作为共同的根
        return 0;
    }
}
void kruskal(edge a[],edge b[],int n,int m)
{
    sort(a,a+m,rule);
    for(int i=0,j=0;i<m;i++)
    {
        if(!is_cycle(a[i].node1,a[i].node2))//没有形成环
        {
            b[j].node1=a[i].node1;
            b[j].node2=a[i].node2;
            b[j].value=a[i].value;;
            j++;//统计已还原边数
            if(j==n-1) break;
        }
    }
}
int main()
{
    for(int i=0;i<20;i++)//初始化 节点的父亲
    {
        parent[i]=-1;
    }
    int n,m;
    cin>>n>>m;//输入节点数n,和边数m
    edge arr[m];
    for(int i=0;i<m;i++)//初始化图
    {
        cin>>arr[i].node1>>arr[i].node2>>arr[i].value;
    }
    edge mst[n];//用于装最小生成树
    kruskal(arr,mst,n,m);
    cout<<endl;
    for(int i=0;i<n-1;i++)
    {
        cout<<mst[i].node1<<' '<<mst[i].node2<<' '<<mst[i].value<<endl;
    }
    return 0;
}

运行结果:
在这里插入图片描述

2.普里姆算法(Prim):
普里姆算法是从某一端点出发,形成两个集合:已选节点集合,未选节点集合。从已选集合找到一条到未选集合的最小权值边,并把连接的端点划为已选集合。重复这样的操作,直到所有节点都在已选集合时完成操作。
这里讲一下思路:
在这里插入图片描述
在代码实现时,在选取某个端点后需要更新到未选节点集合的数值,即从已选节点集合到某个未选节点只有一个值。
步骤如下:
(1)从某个端点出发,把该端点划为已选节点(可以用一个数组来标记哪些是已选节点)。
(2)更新从已选端点到相邻节点的权值,小于之前保存的值时更新(到相邻节点的权值最初的初值为无穷大)。
* 例:赋初值dis[1]=…=dis[6]=inf,inf为无穷大,只要保证大于所有权值即可。如选了v1,则可得到,到v2,v3,v4的权值,它们肯定小于inf,所以更新dis[2]=6,dis[3]=1,dis[4]=5.*
(3)找dis数组中最小值的下标,把改下标划为已选集合,更新dis数组;
例:v1更新完dis后,找到最小值为dis[3],把v3划为已选节点,通过v3,索引未选节点,更新到未选节点的距离,索引v2时:5<6,故dis[2]=5;索引v4时:5<5为假,故不必更新dis[4];索引v5时:6<inf,故dis[5]=6;索引v6时:4<inf,故dis[6]=4;更新完成后找到dis中最小权值的下标,把它划为已选节点,更新dis…重复操作,直到所有节点为已选节点
(4)重复(2)、(3)操作,直到所有的节点都为已选节点时结束。

//数组储存未选集合,每次选到未选集合的最短距离都要遍历数组一遍,有瑕疵
#include<iostream>
#include<vector>//使用向量容器,装相邻节点
using namespace std;
#define max_size 20
#define inf 10000 //表示无穷大
typedef struct
{
    int parent;
    int node;//节点序号
    int value;//从已选节点到该节点的距离(权值)
    int flag;//该节点是否被选,1 已选,0 未选
}node;
typedef struct
{
    int node1;
    int node2;
    int value;
}edge;
vector<int> neighbor[max_size];//装相邻节点
vector<int> value[max_size];//装到相邻节点的权值
int j=0;//用于mst数组下标

void Prim(node a[],edge b[],int n,int v)
{
    a[v].flag=1;
    int i;
    for(i=0;i<neighbor[v].size();i++)//更新到相未选点的距离
    {
        if(value[v][i]<a[neighbor[v][i]].value&&a[neighbor[v][i]].flag==0)
        {
            a[neighbor[v][i]].value=value[v][i];
            a[neighbor[v][i]].parent=v;
        }
    }
    int min_value=inf,p=0;
    for(i=1;i<=n;i++)//找小距离,并选取
    {
        if(min_value>a[i].value&&a[i].flag==0)//找到到未选节点的最小距离
        {
            min_value=a[i].value;
            p=i;//记下所选下标
        }
    }
    if(p==0);//没选取到最小距离,说明所有点都为已选集合,即最小生成树已完成
    else
    {
        b[j].node1=a[p].parent;
        b[j].node2=p;
        b[j].value=min_value;
        j++;
        Prim(a,b,n,p);
    }
}
int main()
{
    node arr[max_size];
    int n,m;
    cin>>n>>m;//输入节点数 n,边数 m
    for(int i=1;i<=n;i++)//初始化节点
    {
        arr[i].flag=0;//初始化为未选
        arr[i].value=inf;//初始化 从已选节点到该节点的距离为无穷大
    }
    int a,b,c;
    for(int i=0;i<m;i++)//输入初始图
    {
        cin>>a>>b>>c;//a b为边的节点,c为权值
        neighbor[a].push_back(b);//把b装入a的邻居
        value[a].push_back(c);//把对应权值装入  a的第i个邻居,对应第i个value值
        neighbor[b].push_back(a);
        value[b].push_back(c);
    }
    edge mst[max_size];//用于装最小生成树
    Prim(arr,mst,n,1);//1号节点开始
    for(int i=0;i<n-1;i++)
    {
        cout<<mst[i].node1<<' '<<mst[i].node2<<' '<<mst[i].value<<endl;
    }
    return 0;
}

  • 33
    点赞
  • 228
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值