最小生成树:Prim和Kruskal算法

在一个无向连通图中,如果存在一个连通子图包含原图中的所有的结点和部分边,且这个子路不存在回路,那么我们称这个子图为原图的一棵生成树。在带权图中,所有的生成树中边权的和最小的那颗(或几棵)被称为最小生成树。——摘自王道

下面将从两种最常见的算法Prim和Kruskal来讲解最小生成树,并进行比较

1.Prim算法

在这里插入图片描述
Prim算法从任意一个顶点开始,每次选择一个与当前顶点集最近的一个顶点,并将两顶点之间的边加入到树中。Prim算法在找当前最近顶点时使用到了贪婪算法。

#include<iostream>
#define INF 10000
using namespace std;
const int N = 6;
bool visit[N];
int dist[N] = { 0, };
int graph[N][N] = { {INF,7,4,INF,INF,INF},  //INF代表两点之间不可达
                    {7,INF,6,2,INF,4},
                    {4,6,INF,INF,9,8},
                    {INF,2,INF,INF,INF,7},
                    {INF,INF,9,INF,INF,1},
                    {INF,4,8,7,1,INF}
                  };
int prim(intcur)
{
    int index = cur;
    int sum = 0;
    int i = 0;
    int j = 0;
    cout << index << " ";
    memset(visit,false, sizeof(visit));
    visit[cur] = true;
    for(i = 0; i < N; i++)
        dist[i] = graph[cur][i];//初始化,每个与a邻接的点的距离存入dist
    for(i = 1; i < N; i++)
    {
        int minor = INF;
        for(j = 0; j < N; j++)
        {
            if(!visit[j] && dist[j] < minor)          //找到未访问的点中,距离当前最小生成树距离最小的点
            {
                minor = dist[j];
                index = j;
            }
        }
        visit[index] = true;
        cout << index << " ";
        sum += minor;
        for(j = 0; j < N; j++)
        {
            if(!visit[j] && dist[j]>graph[index][j])      //执行更新,如果点距离当前点的距离更近,就更新dist
            {
                dist[j] = graph[index][j];
            }
        }
    }
    cout << endl;
    return sum;               //返回最小生成树的总路径值
}
int main()
{
    cout << prim(0) << endl;//从顶点a开始
    return 0;
}

代码参考点击跳转

可以看到,Prim算法主要操作是通过遍历顶点来完成的,每次都要寻找一个最小距离的顶点,因此可以使用来优化。相关资料之后有时间补上。

时间复杂度:O(V^2)
堆优化后时间复杂度:O((V + E) log(V)) = O(E log(V))
Prim算法适用于稠密图


2.Kruskal算法

图片链接
图片源自点击跳转

Kruskal算法首先需要对所有权重边做从小到大排序。将排序好的权重边依次加入到最小生成树中,如果加入时产生回路就跳过这条边,加入下一条边。当所有结点都加入到最小生成树中之后,就找出了最小生成树,其思想本质也是贪婪算法。在实际使用中,可使用并查集来实现操作。

下面摘取王道中的一道基础例题作为入门:

题目描述:
某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。
输入:
测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。
当N为0时,输入结束,该用例不被处理。
输出:
对每个测试用例,在1行里输出最小的公路总长度。
样例输入:
3
1 2 1
1 3 2
2 3 4
4
1 2 1
1 3 4
1 4 1
2 3 3
2 4 2
3 4 5
0
样例输出:
3
5

#include <iostream>
#include<algorithm>
#include<cstdio>
#define N 100
using namespace std;
int Tree[N];
int findRoot(int x){//x值的根节点
    if(Tree[x]==-1)
        return x;
    else{
        int temp=findRoot(Tree[x]);
        Tree[x]=temp;
        return temp;
    }
}
struct edge{
    int a,b;
    int cost;//边的权值
    bool operator < (const edge &A) const{//重载小于号使其可以按照边的权值从小到大排列
        return cost<A.cost;
    }
};

struct edge edges[200];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n*(n-1)/2;i++)
        scanf("%d %d %d",&edges[i].a,&edges[i].b,&edges[i].cost);
    sort(edges,edges+n*(n-1)/2);//按边排序
    for(int i=0;i<N;i++)
        Tree[i]=-1;
    int ans=0;
    for(int i=0;i<n*(n-1)/2;i++){
        int a=findRoot(edges[i].a);//查a的根节点
        int b=findRoot(edges[i].b);//查b的根节点
        if(a!=b){//ab两点不是一棵树上的
            Tree[a]=b;//合并两个集合
            ans+=edges[i].cost;//累加该边权值
        }
    }
    printf("%d\n",ans);
    return 0;
}

时间复杂度:O(ElogE)(快排)
Kruskal算法适用于稀疏图

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值