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

    • 一、普里姆算法(Prim)
    • 二、克鲁斯卡算法(Kruskal)
    • 三、例题


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


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

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

【图论】最小生成树(Prim 和 Kruskal算法)_第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 算法对下面这幅图求其最小生成树:

【图论】最小生成树(Prim 和 Kruskal算法)_第2张图片


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

【图论】最小生成树(Prim 和 Kruskal算法)_第3张图片

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

【图论】最小生成树(Prim 和 Kruskal算法)_第4张图片

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

【图论】最小生成树(Prim 和 Kruskal算法)_第5张图片

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

【图论】最小生成树(Prim 和 Kruskal算法)_第6张图片

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

【图论】最小生成树(Prim 和 Kruskal算法)_第7张图片


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


二、克鲁斯卡算法(Kruskal)

算法思路:

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

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

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


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

【图论】最小生成树(Prim 和 Kruskal算法)_第8张图片


  • 首先从这些边中找出权重最小的那条边,可以发现边(v1,v3)这条边的权重是最小的,所以我们输出边:v1—-v3=1
    【图论】最小生成树(Prim 和 Kruskal算法)_第9张图片

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

【图论】最小生成树(Prim 和 Kruskal算法)_第10张图片

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

【图论】最小生成树(Prim 和 Kruskal算法)_第11张图片


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

【图论】最小生成树(Prim 和 Kruskal算法)_第12张图片

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

【图论】最小生成树(Prim 和 Kruskal算法)_第13张图片

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

【图论】最小生成树(Prim 和 Kruskal算法)_第14张图片


  • 我们发现,这个图中没有环出现,而且把所有的顶点都加入到了这颗树上了,所以(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 <= 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 
#include 
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());
		}

	}

}

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

你可能感兴趣的:(Java,ACM,-,图论)