【图论】最小生成树(Prim 和 Kruskal算法)


在学习最小生成树算法之前,要先了解相关知识点!


  • 生成树: 一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。

  • 最小生成树: 在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。

在这里插入图片描述




一、普里姆算法(Prim)

算法思路:

  • 首先就是从图中的一个起点a开始,把a加入U集合。

  • 然后,寻找从与a有关联的边中,权重最小的那条边并且该边的终点b在顶点集合(V-U)中,把 b 也加入到集合U中,并且输出边(a,b)的信息,这样集合U就有 {a,b}。

  • 接着,寻找与 a 关联和 b 关联的边中,权重最小的那条边并且该边的终点在集合(V-U)中,把 c 加入到集合U中,并且输出对应的那条边的信息,这样集合U就有 {a,b,c} 这三个元素了。

  • 依次类推,直到所有顶点都加入到了集合U。


下面我们用 Prim 算法对下面这幅图求其最小生成树:

在这里插入图片描述


  • 假设从顶点 v1 开始,可以发现(v1,v3)边的权重最小,所以第一个输出的边就是:v1—v3=1:

在这里插入图片描述

  • 然后,从 v1 和 v3 作为起点的边中寻找权重最小的边,首先边(v1,v3)已经访问过了,所以从其他边中寻找,发现 (v3,v6) 这条边最小,所以输出边就是:v3—-v6=4

在这里插入图片描述

  • 然后,从 v1、v3、v6 这三个点相关联的边中寻找一条权重最小的边,可以发现边 (v6,v4) 权重最小,所以输出边就是:v6—-v4=2.

在这里插入图片描述

  • 然后,从 v1、v3、v6、v4 这四个顶点相关联的边中寻找权重最小的边,发现边(v3,v2)的权重最小,所以输出边:v3—–v2=5

在这里插入图片描述

  • 然后,从 v1、v3、v6、v4,v2 这五个顶点相关联的边中寻找权重最小的边,发现边(v2,v5)的权重最小,所以输出边:v2—–v5=3

在这里插入图片描述


  • 最后,我们发现六个点都已经加入到集合U了,我们的最小生成树建立完成。


二、克鲁斯卡算法(Kruskal)

算法思路:

  • 将图中的所有边都去掉。

  • 将边按权值从小到大的顺序添加到图中,保证添加的过程中不会形成环

  • 重复上一步直到连接所有顶点,此时就生成了最小生成树。这是一种贪心策略。


下面我们用 Kruskal 算法对下面这幅图求其最小生成树:

在这里插入图片描述


  • 首先从这些边中找出权重最小的那条边,可以发现边(v1,v3)这条边的权重是最小的,所以我们输出边:v1—-v3=1
    在这里插入图片描述

  • 然后,我们需要在剩余的边中,再次寻找一条权重最小的边,可以发现边(v4,v6)这条边的权重最小,所以输出边:v4—v6=2

在这里插入图片描述

  • 然后,我们再次从剩余边中寻找权重最小的边,发现边(v2,v5)的权重最小,所以可以输出边:v2—-v5=3

在这里插入图片描述


  • 然后,我们使用同样的方式找出了权重最小的边:(v3,v6),所以我们输出边:v3—-v6=4

在这里插入图片描述

  • 我们还需要找出最后一条边就可以构造出一颗最小生成树,但是这个时候我们有三个选择:(v1,V4),(v2,v3),(v3,v4),这三条边的权重都是5,首先我们如果选(v1,v4)的话,得到的图如下:

在这里插入图片描述

  • 我们发现,这肯定是不符合我们算法要求的,因为它出现了一个环,所以我们再使用第二个(v2,v3)试试,得到图形如下:

在这里插入图片描述


  • 我们发现,这个图中没有环出现,而且把所有的顶点都加入到了这颗树上了,所以(v2,v3)就是我们所需要的边,所以最后一个输出的边就是:v2—-v3=5


三、例题

原题链接:还是畅通工程

思路: 最小生成树模板题。


Prim 做法:

// 很神奇,用Prime算法Java就可以过

import java.util.Scanner;

public class Main {
    static int N = 105, INF = 0x3f3f3f3f;
    static int n, m;
    static int[] dis = new int[N];    // dis[i]表示i点到生成树集的最短距离
    static boolean[] vis = new boolean[N];
    static int[][] e = new int[N][N];

    static int Prime() {
        for (int i = 1; i <= n; i++) {
            dis[i] = e[1][i];    // 初始为1到其他顶点距离
        }
        dis[1] = 0;
        vis[1] = true;    // 将用过的点标记,避免重复访问
        int ans = 0;

        for (int i = 1; i < n; i++) {    // 进行n-1次操作
            int minn = INF;
            int pos = 1;    // 记录最小权值的顶点
            for (int j = 1; j <= n; j++) {    // 在未加入点中,找到一个最小的权值
                if (!vis[j] && minn > dis[j]) {
                    minn = dis[j];
                    pos = j;
                }
            }
            if (minn == INF)    // 不是连通图
                break;
            vis[pos] = true;    // 将加入点进行标记
            ans += minn;    // 加边权
            for (int j = 1; j <= n; j++) {    // 在未加入点中找到与此点相连的权值最小的边
                if (!vis[j] && dis[j] > e[pos][j])
                    dis[j] = e[pos][j];
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) {
            for (int i = 1; i <= 101; i++)
                vis[i] = false;
            n = sc.nextInt();
            if (n == 0)
                break;
            m = n * (n - 1) / 2;
            for (int i=1;i<=n;i++) {
                for (int j=1;j<=n;j++) {
                    e[i][j] = INF;
                }

            }
            for (int i = 1; i <= m; i++) {
                int u = sc.nextInt(), v = sc.nextInt(), w = sc.nextInt();
                e[u][v] = e[v][u] = w;
            }
            System.out.println(Prime());
        }
    }
}




Kruskal 做法:

//c++做法,能AC

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 105;
int n,m,f[N];// f[i]表示第i点的祖先
struct node{
    int u,v,w;
}e[N*N];    //定义边
bool cmp(node a,node b){
    return a.w < b.w;
}
int find(int u){
    return f[u]==u?u:find(f[u]);
}
int kruskal(){
    for(int i=1;i<=n;i++)
        f[i] = i;   //初始化,开始时每个村庄都是单独的集
    sort(e+1,e+m+1,cmp);   
    int ans = 0;
    for(int i=1;i<=m;i++){
        int fu = find(e[i].u);  //边的前端点所在的集合
        int fv = find(e[i].v);  //边的后端点所在的集合
        if(fu != fv){   //不在同一个集合,代表加入此边不会形成一个环
            f[fu] = fv; //合并
            ans+=e[i].w;
        }
    }
    return ans;
}
int main() {
    while(cin>>n && n) {
        m = n*(n-1)/2;
        for(int i=1;i<=m;i++){
            cin>>e[i].u>>e[i].v>>e[i].w;
        }
        cout<<kruskal()<<endl;
    }
    return 0;
}

//Java做法,会TLE,估计这道题专门卡点了吧

import java.util.Arrays;
import java.util.Scanner;

class node implements Comparable<node> {
	int u, v, w;

	node() {
	};

	public node(int u, int v, int w) {
		this.u = u;
		this.v = v;
		this.w = w;
	}

	public int compareTo(node a) {
		return this.w - a.w;
	}
}

public class Main {

	static int n, m;
	static node[] a = new node[100100];
	static int[] f = new int[100100];

	static int find(int u) {
		return f[u] == u ? u : find(f[u]);
	}

	static int kruskal() {
		for (int i = 1; i <= m; i++) {
			f[i] = i;
		}
		Arrays.sort(a, 0, m);
		int ans = 0;
		for (int i = 1; i <= m; i++) {
			int fu = find(a[i].u);
			int fv = find(a[i].v);
			if (fu != fv) {
				f[fu] = fv;
				ans += a[i].w;
			}
		}
		return ans;
	}

	public static void main(String[] args) {

		Scanner sc = new Scanner(System.in);
		while (sc.hasNext()) {
			n = sc.nextInt();
			if (n == 0)
				break;
			m = n * (n - 1) / 2;
			for (int i = 0; i <= 100010; i++)
				a[i] = new node();
			for (int i = 1; i <= m; i++) {
				a[i] = new node(sc.nextInt(), sc.nextInt(), sc.nextInt());
			}
			System.out.println(kruskal());
		}

	}

}

参考:数据结构–最小生成树详解

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值