最小生产树Prim和Kruskal

撸了今年阿里、头条和美团的面试,我有一个重要发现.......>>> hot3.png

前言

春节将至,提前祝大家新春快乐,万事如意。今天介绍无向图最小生产树。

无向图最小生成树问题描述

一个无向图G的最小生成树就是由该图的那些链接G的所有顶点的边构成的树,其总价值最低。 最小生成树存在当且仅当图是连通的。为了简便考虑, 下面的算法都是假设图是连通的。 无向图最小生成树有两个典型的算法Prim和Kruskal,下面分别介绍。

Prim算法

算法核心思想

以贪婪策略,一步一步将关联顶点增加到树上。

算法介绍

算法的每一阶段都是通过:

  1. 选择一条边(u,v)使得(u,v)的值是所有u在树上但v不在树上的边的值中的最小者。并把相应的顶点v添加到这颗树上。
  2. 继续上述步骤,直到所有顶点都在树上。

图解示例

 

核心代码

public void prim(Vertex start){
	//初始声明所有顶点均不在树上
	for(Vertex v:graph){
		v.isInTree=false;
		v.dist=Integer.MAX_VALUE;
	}
	start.dist = 0;// 声明起点的距离为0
	//以顶点的最短距离构建堆
	PriorityQueue<Vertex> priorityQueue = new PriorityQueue<Vertex>();
	priorityQueue.add(start);
	while(!priorityQueue.isEmpty()){
		Vertex v=priorityQueue.poll();
		if(v!=null){
	          if(!v.isInTree){//取出的顶点不在树上
			//1. 声明顶点在树上
			v.isInTree=true;
			if(v.adj!=null&&!v.adj.isEmpty()){
				for(AdjVertex adjw:v.adj){
				//更新临接表中 最短距离有变化的顶点,并将该顶点加入到优先队列
					if(adjw.cvw<adjw.w.dist){
					    adjw.w.setDist(adjw.cvw);
					    adjw.w.setPath(v);
					    priorityQueue.add(adjw.w);
						}
					}
				}
			}
		}
	}
}

Kruskal算法

算法核心思想

以贪婪策略,连续按照最小的权选择备选边,并且当所选的边不会产生圈时选定该边。

算法介绍

分析

Kruskal类似处理一个森林。初始时,有存在|V|颗单节点树,每添加一条边即将两棵树合并,当算法终止时就只有一颗树。

数据结构选择

  1. 经过上述分析,Kruskal所需要的数据结构需要很好的支持find(即找到节点所属的当前树)和union操作(即合并两颗树)。目前良好的支持find/union操作的数据结构就是不相交集合
  2. 每次选择最小权的边。以边的权构建堆,每次执行deletemin操作。

算法核心

在算法的任意时刻,两个顶点属于同一个集合当且仅当它们在当前的生成森林中连通。

图解示例

 

核心代码

public List<Edge> kruskal() {
	List<Edge> result = new ArrayList<Edge>();
	int vertexSize = graph.values().size();
	int acceptedEdge = 0;
	//以点的数量构建不相交集合
	DisjSets disjSets = new DisjSets(vertexSize);
	//以边的权构建堆
	PriorityQueue<Edge> priorityQueue = new PriorityQueue<Edge>(getEdges());
	while (acceptedEdge < vertexSize - 1&&!priorityQueue.isEmpty()) {
		Edge e = priorityQueue.poll();
		int disv = disjSets.find(e.vnum);
		int disw = disjSets.find(e.wnum);
		if (disv != disw) {
			//两个顶点不在一颗树上,合并两个顶点
				acceptedEdge++;
				disjSets.union(disv, disw);
				result.add(e);
			}
	}
	return result;
}

完整代码地址

github
Prim

Kruskal

码云

Prim

Kruskal

Prim算法和Kruskal算法都是用于求解最小生成树的经典算法。 Prim算法的基本思想是从一个点开始,每选择一个与当前生成距离最近的点加入生成中,直到所有点都被加入生成为止。具体实现时,可以使用一个优先队列来维护当前生成与未加入生成的点之间的距离,每选择距离最小的点加入生成中。 Kruskal算法的基本思想是从边开始,每选择一条权值最小且不会形成环的边加入生成中,直到生成中包含所有点为止。具体实现时,可以使用并查集来判断是否形成环。 下面是Prim算法和Kruskal算法的C语言代码实现: Prim算法: ```c #include <stdio.h> #include <stdlib.h> #include <limits.h> #define MAX_VERTICES 1000 int graph[MAX_VERTICES][MAX_VERTICES]; int visited[MAX_VERTICES]; int dist[MAX_VERTICES]; int prim(int n) { int i, j, u, min_dist, min_index, sum = 0; for (i = 0; i < n; i++) { visited[i] = 0; dist[i] = INT_MAX; } dist[0] = 0; for (i = 0; i < n; i++) { min_dist = INT_MAX; for (j = 0; j < n; j++) { if (!visited[j] && dist[j] < min_dist) { min_dist = dist[j]; min_index = j; } } u = min_index; visited[u] = 1; sum += dist[u]; for (j = 0; j < n; j++) { if (!visited[j] && graph[u][j] < dist[j]) { dist[j] = graph[u][j]; } } } return sum; } int main() { int n, m, i, j, u, v, w; scanf("%d%d", &n, &m); for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { graph[i][j] = INT_MAX; } } for (i = 0; i < m; i++) { scanf("%d%d%d", &u, &v, &w); graph[u][v] = graph[v][u] = w; } printf("%d\n", prim(n)); return 0; } ``` Kruskal算法: ```c #include <stdio.h> #include <stdlib.h> #include <limits.h> #define MAX_VERTICES 1000 #define MAX_EDGES 1000000 struct edge { int u, v, w; }; int parent[MAX_VERTICES]; struct edge edges[MAX_EDGES]; int cmp(const void *a, const void *b) { return ((struct edge *)a)->w - ((struct edge *)b)->w; } int find(int x) { if (parent[x] == x) { return x; } return parent[x] = find(parent[x]); } void union_set(int x, int y) { parent[find(x)] = find(y); } int kruskal(int n, int m) { int i, sum = 0; for (i = 0; i < n; i++) { parent[i] = i; } qsort(edges, m, sizeof(struct edge), cmp); for (i = 0; i < m; i++) { if (find(edges[i].u) != find(edges[i].v)) { union_set(edges[i].u, edges[i].v); sum += edges[i].w; } } return sum; } int main() { int n, m, i; scanf("%d%d", &n, &m); for (i = 0; i < m; i++) { scanf("%d%d%d", &edges[i].u, &edges[i].v, &edges[i].w); } printf("%d\n", kruskal(n, m)); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值