Kruskal算法和Prims算法+例题

首先,这两个算法都是求最小生成树的算法。

首先,生成树是建立在无向图中的,对于有向图,则没有生成树的概念,所以接下来讨论的图均默认为无向图。对于一个有n个点的图,最少需要n-1条边使得这n个点联通,由这n-1条边组成的子图则称为原图的生成树。一般来说,一个图的生成树并不是唯一的(除非原图本身就是一棵树)。

在实际生活中我们常常会遇到这样一些问题:有若干个需要连接的点(不妨假设为一些村庄)和若干条连接着两个点的边(在村庄间修公路),而这些边会有不同的权值(可设为修路所需的费用不同)。现在要连通这些所有的点,并使权值和最小。这类问题在现实生活中很广泛,如修公路、架设电网,等等。
在信息学竞赛中,这种问题有专门的称谓“最小生成树”(Minimum Spanning Tree,简称MST)。

Kruskal算法:

顶点个数:n,边的条数:m

 基本思想:

运用贪心的思想,每次选取权值最小的边,通过判断这条边所连接的两个顶点是否属于同一个集合,来决定这条边是否加入到生成树中去,如果不属于同一个集合,就加入到生成树中去,否则,就不加到生成树中去。直到有n-1条边加入到生成树中去(或者所有的边都访问完成了)就结束算法。

算法执行过程:

一:将边的所有权值按从小到大排序。

二:按照边的权值从小到大遍历所有的边。

三:每次遍历边的时候运用并查集判断边的两个顶点是否属于同一个集合,如果不属于同一个集合,那么就将这条边加入到生成树中去。

四:每次遍历边完成后,判断一下生成树中的边是否等于n-1,如果等于,直接输出结果,结束算法。如果不等于n-1,继续遍历其他的边。

代码如下:

​
​
#include
#define MAXN 9999999
using namespace std;
int n,m;//n-->顶点个数   m-->边个数
int father[MAXN];//father[i]代表编号为i的顶点的父亲顶点的编号是多少
int sum=0;//加权总和
int n1=0;//最小生成树的边的条数
struct node
{
    int u,v,w;
}a[MAXN];
bool cmp(node x1,node x2)//比较器
{
    return x1.w>n>>m;
    for(int i=0;i>a[i].u>>a[i].v>>a[i].w;
    Kruskal();
}

​


​

代码运行过程:

原图:

Kruskal算法和Prims算法+例题_第1张图片

sum=0

 

第一步:

Kruskal算法和Prims算法+例题_第2张图片

sum=1       m1=1

 

第二步:

Kruskal算法和Prims算法+例题_第3张图片

sum=3      m1=2

 

第三步:

Kruskal算法和Prims算法+例题_第4张图片

sum=6      m1=3

 

第四步:

Kruskal算法和Prims算法+例题_第5张图片

sum=10     m1=4

 

第5步:

Kruskal算法和Prims算法+例题_第6张图片

sum=19       m1=5=n-1    算法结束。所得的最小生成树是上图。

Prims算法:

基本思想:

还是运用贪心的思想,每次往生成树集合中加入离生成树距离最近的点,从一个点慢慢的扩展,最后得到所求的最小生成树。这个算法和求最短路的dijkstra算法很像。

算法执行过程:

n:顶点个数       m:边的条数

一:先选任意一个点加入到生成树集合中去,遍历该点的所有出边点,初始化mincount数组(存放i点到生成树中的点的当前最短距离)。

二:遍历非生成树集合中的所有点,找到离生成树集合中的点距离最近的非生成树集合中点。

三:将该点加入到生成树集合中去,然后遍历该点的所有出边点,看看能否通过该边使得出边点到生成树集合中的点的最短距离变得更短。

四:重复二,三过程( n-1 )次,这样,最小生成树就构造好了(每次遍历都往生成树中加入一个点,进行n-1次遍历后,所有顶点都已经加入到了生成树中去了),算法结束。

代码如下:

#include
#define MAXN 9999
using namespace std;
int n,m;
int u,v,w;
int e[MAXN][MAXN];
int mincount[MAXN];//mincount[i]代表编号为i的顶点离生成树集合的最短距离(可以是生成树集合中的任意一点),和dijkstra算法中的dis数组类似
int visited[MAXN];//标记数组,标记i顶点是否进入了生成树集合
int sum=0;
void prims(int s)//先将s点放到生成树集合中去
{
    memset(visited,0,sizeof(visited));
    for(int i=1;i<=n;i++)//依次遍历所有的点,看一下哪个点和s点连通
        if(e[s][i]>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        e[i][j]=MAXN;
    for(int i=0;i>u>>v>>w;
        e[u][v]=w;
        e[v][u]=w;
    }
    for(int i=1;i<=n;i++)//初始化mincount数组
        mincount[i]=MAXN;
    prims(1);
}

代码执行过程:

原图:

Kruskal算法和Prims算法+例题_第7张图片

sum=0   

 

第一步:

Kruskal算法和Prims算法+例题_第8张图片

sum=1   第一次遍历

 

第二步:

Kruskal算法和Prims算法+例题_第9张图片

sum=3     第二次遍历

 

第三步:

Kruskal算法和Prims算法+例题_第10张图片

sum=12   第三次遍历

 

第四步:

Kruskal算法和Prims算法+例题_第11张图片

sum=15   第四次遍历

 

第五步:

Kruskal算法和Prims算法+例题_第12张图片

sum=19   第5次遍历   n-1等于5   算法结束 。

总结:

通过上面可得,Kruskal算法是一步步将森林中的树进行合并,而Prims算法是每次通过增加一条边来创建一棵树。Kruskal算法是宏观的建立一棵树,而Prims算法是从小开始,一步一步把树建立好。Kruskal算法适用于稀松图(边很少的图),因为Kruskal算法是通过边的权值建立生成树的,所以边的条数越少,算法就越快。而Prims算法适用于稠密图(边很多的图),因为Prims算法是通过顶点到生成树的距离来建树的,与边的条数没有多大的关系,所以适用于稠密图。

例题一:

洛谷 P2820 局域网-----https://www.luogu.org/problemnew/show/P2820

思路:

 求出所有的路径之和,然后用Kruskal算法跑一遍,求出最小生成树的数值,然后用总和减去最小生成树的数值就是答案

代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXN 1000001
#define mod 1000000007
typedef long long ll;
using namespace std;
int n,k,n1,sum=0;
int father[MAXN];
struct node
{
    int u,v,w;
}a[MAXN];
bool cmp(node x1,node x2)//比较器
{
    return x1.w>n>>k;
    int sum1=0;
    for(int i=0;i>a[i].u>>a[i].v>>a[i].w;
        sum1+=a[i].w;
    }
    Kruskal();
    cout<

例题二:

POJ 2421 Constructing Roads------http://poj.org/problem?id=2421

题意:

有一些边已经建好了,要你在此基础上再去求一遍最小生成树

思路:

将已经建好的边的权值设置为0,然后跑一遍Kruskal算法求出最小生成树就是结果

代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXN 101
#define mod 1000000007
typedef long long ll;
using namespace std;
int linjie[MAXN][MAXN];
int father[MAXN];
int n,q,m=0;
int sum=0;
struct node
{
    int u,v,w;
}a[10001];
int z,k;
bool cmp(node x1,node x2)//比较器
{
    return x1.w>linjie[i][j];
        cin>>q;
        for(int i=0;i>z>>k;
            linjie[z][k]=0;//将已经建好的边的权值设为0
            linjie[k][z]=0;
        }
        for(int i=1;i<=n;i++)//将邻接矩阵中的信息传入到结构体数组中去
            for(int j=i+1;j<=n;j++)
            {
                a[m].u=i;
                a[m].v=j;
                a[m++].w=linjie[i][j];
            }
        Kruskal();//正常跑一遍Kruskal算法
        cout<

例题三:

洛谷 P1111 修复公路----https://www.luogu.org/problemnew/show/P1111

思路:

这个题是让你求能够构成最小生成树的边中权值最大的边,如果无法构成最小生成树,我们还需要特判一下。我们知道,如果能够构成最小生成树,那么边的条数一定是顶点的个数减1,我们就是运用这个来判断能否构成最小生成树。

代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXN 1100
#define mod 1000000007
typedef long long ll;
using namespace std;
int n,m;//n-->顶点个数   m-->边个数
int father[MAXN];//father[i]代表编号为i的顶点的父亲顶点的编号是多少
int sum=0;//加权总和
int n1=0;//最小生成树的边的条数
int maxz=-1;
struct node
{
    int u,v,w;
}a[100010];
bool cmp(node x1,node x2)//比较器
{
    return x1.w>n>>m;
    for(int i=0;i>a[i].u>>a[i].v>>a[i].w;
    Kruskal();
    if(n1==n-1)//如果这个条件满足,说明可以构成最小生成树,边的条数是顶点个数减一,否则无法构成最小生成树
        cout<

 

你可能感兴趣的:(算法)