数据结构重读 – 最小生成树(Prim和Kruskal)

假设要在n个城市之间建立通信网络,则连通n个城市只需要n-1条线路。现在想节省光缆,使得路径和最小。

这一问题就是最小代价生成树问题(Minimum Cost Spanning Tree),简称MST。

MST性质:假设N=(V, {E})是一个连通图,U是顶点集合V的一个非空子集,若(u, v)是一条具有最小权值的边,u属于U且v属于V-U。则必存在一棵包含边(u, v)的最小生成树。

因此,可以采用贪心算法解决。

1、Prim(普里姆)算法

我们需要设置两个辅助数组cost和ivex,它们需要联合在一起使用。

i表示一个尚未被选择的顶点的下标(尚属于V-U)。cost[i]为U中所有顶点中到i的最小权值,ivex[i]即为U中的这个顶点。

因此初始情况下,我们使得ivex[*] = v0, cost[i] = arcs[v0][i]。

每次,选择cost最小的一个的i,然后ivex[i]就是新选择边的起始点,i就是终结点。

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

#define MAX_VNUM 100
#define INF_DIST 0xfffffff
#define COST_SELECT -1

typedef struct
{
	int vexs[MAX_VNUM]; // vertex(s)
	int nvexs; // number of vertex(s)
	int arcs[MAX_VNUM][MAX_VNUM]; // arc(s)
	int narcs; // number of arc(s)
} Graph;

int graph_locate_vec(Graph* graph, int val)
{
	int i;
	for(i=0; i<graph->nvexs; i++)
	{
		if(graph->vexs[i]==val)
		{
			return i;
		}
	}
	return -1;
}

void graph_create_graph(Graph* graph)
{
	int i, j;
	// 存储顶点
	printf("Please enter n for vex(s) :\n");
	scanf("%d", &graph->nvexs);
	for(i=0; i<graph->nvexs; i++)
	{
		printf("Please enter a int for vecs(%d) :\n", i);
		scanf("%d", &graph->vexs[i]);
	}
	// 存储弧度
	//memset(graph->arcs, INF_DIST, sizeof(int)*MAX_VNUM*MAX_VNUM);
	for(i=0;i<graph->nvexs;i++)
	{
		for(j=0;j<graph->nvexs;j++)
		{
			graph->arcs[i][j] = INF_DIST;
		}
	}
	printf("Please enter m for arc(s) :\n");
	scanf("%d", &graph->narcs);
	for(i=0; i<graph->narcs; i++)
	{
		int x, y, w;
		printf("Please enter v1, v2, w for vecs(%d) :\n", i);
		scanf("%d %d %d", &x, &y, &w);
		x = graph_locate_vec(graph, x);
		y = graph_locate_vec(graph, y);
		if(x==-1 || y==-1)
		{
			i--;
			printf("Invalid v1 or v2.\n");
			continue;
		} else if (w<=0)
		{
			i--;
			printf("Invalid w.\n");
			continue;
		}
		graph->arcs[x][y] = w;
		graph->arcs[y][x] = w;
	}
}

// Minimum Cost Spanning Tree using Prim
void mst_prim(Graph* g)
{
	int k;
	int i;
	int j;
	int ivex[MAX_VNUM];
	int cost[MAX_VNUM];
	k = 0; // First vec in U
	cost[k] = COST_SELECT;
	// Init cost with u -> i (i is vex in V-U)
	for(i=0; i<g->nvexs; i++)
	{
		if(i!=k)
		{
			ivex[i] = k;
			cost[i] = g->arcs[k][i];
		}
	}
	// Select other nvex-1
	for(i=1; i<g->nvexs; i++)
	{
		// Select min cost[i] and i is not select yet
		int min=INF_DIST, min_vex=0;
		for(j=0; j<g->nvexs; j++)
		{
			if(cost[j]!=COST_SELECT && cost[j]<min)
			{
				min_vex = j;
				min = cost[j];
			}
		}
		k = min_vex;
		cost[k] = COST_SELECT;
		printf("Arc %d -> %d \n", g->vexs[ivex[k]], g->vexs[k]);
		// Update unselect cost
		for(j=0; j<g->nvexs; j++)
		{
			if(cost[j]!=COST_SELECT && g->arcs[k][j]<INF_DIST && g->arcs[k][j]<cost[j])
			{
				ivex[j] = k;
				cost[j] = g->arcs[k][j];
			}
		}
	}
}

int main()
{
	Graph g;
	graph_create_graph(&g);
	mst_prim(&g);
	return 0;
}

测试图如下:

测试数据:

6
1
2
3
4
5
6
10
1 2 6
1 3 1
1 4 5
2 3 5
2 5 3
3 4 5
3 5 6
3 6 4
4 6 2
5 6 6

上面实线的这个Prim算法的时间复杂读是O(n^2),主要耗费在选取min cost上。而这一过程,可以用堆(优先队列)实现,使得复杂度降低为O((V+E)*logV)。

由此,Prim算法适用于稠密图中求最小生成树

2、Kruskal(克鲁斯卡尔)算法

Kruskal适用于稀疏图中求最小生成树

时间复杂度是O(E log E) 或者 O(E log V)

Kruskal算法的流程非常简单明了,但需要借助两个数据结构:

(1) 堆(优先队列),按照每条Arc的weight,每次取出最小的weight的Arc。
(2) 并查集,用于判断两个顶点vex i和vex j是否属于同一个结合,并可做合并操作。

算法流程:

  • create a forest F (a set of trees), where each vertex in the graph is a separate tree
  • create a set S containing all the edges in the graph
  • while S is nonemptyand F is not yet spanning
    • remove an edge with minimum weight from S
    • if that edge connects two different trees, then add it to the forest, combining two trees into a single tree
    • otherwise discard that edge.

由于实在不喜欢STL的优先队列,用Java实现吧。

首先是并查集,一定不要实现错哦!

public class UFSet {

	public UFSet(int n) {
		parent = new int[n];
		for (int i = 0; i < n; i++) {
			parent[i] = i;
		}
	}

	public int Find(int i) {
		// check
		if (i < 0 || i >= parent.length) {
			return -1;
		}
		// Find until root and remember it
		if (parent[i] != i) {
			parent[i] = Find(parent[i]);
		}
		return parent[i];
	}

	public void Union(int i, int j) {
		if (i < 0 || j < 0 || i >= parent.length || j >= parent.length) {
			return;
		} else {
			int pi = Find(i);
			int pj = Find(j);
			parent[pi] = pj;
		}
	}

	private int parent[] = null;

	public static void main(String[] args) {
		int n = 10;
		UFSet set = new UFSet(n);
		set.Union(0, 3);
		set.Union(0, 4);
		for (int i = 0; i < n; i++) {
			System.out.println(set.Find(i));
		}
	}
}

然后是Kruskal算法,input是输入参数,kruskal是算法正常执行。

import java.util.Scanner;
import java.util.Comparator;
import java.util.PriorityQueue;

public class Kruskal {

	class Arc {

		public int getX() {
			return x;
		}

		public void setX(int x) {
			this.x = x;
		}

		public int getY() {
			return y;
		}

		public void setY(int y) {
			this.y = y;
		}

		public int getWeight() {
			return weight;
		}

		public void setWeight(int weight) {
			this.weight = weight;
		}

		public Arc(int a, int b, int w) {
			x = a;
			y = b;
			weight = w;
		}

		private int x; // ivex for start
		private int y; // ivex for end
		private int weight;
	};

	int vex2i(int vexs[], int v) {
		for (int i = 0; i < vexs.length; i++) {
			if (v == vexs[i]) {
				return i;
			}
		}
		return -1;
	}

	public void input() {

		// Scanner
		Scanner scan = new Scanner(System.in);

		// Get vexs
		int nvexs = 0;
		System.out.println("Please enter the number of vexs:");
		if (scan.hasNextInt()) {
			nvexs = scan.nextInt();
		}
		vexs = new int[nvexs];
		for (int i = 0; i < nvexs; i++) {
			System.out.println("Please enter a integer for vex[" + i + "]:");
			if (scan.hasNextInt()) {
				vexs[i] = scan.nextInt();
			}
		}

		// PriorityQueue to store arcs
		pq = new PriorityQueue<Arc>(100, new Comparator<Arc>() {
			@Override
			public int compare(Arc a1, Arc a2) {
				return a1.getWeight() - a2.getWeight();
			}
		});

		// Get arcs
		int narcs = 0;
		System.out.println("Please enter the number of arcs:");
		if (scan.hasNextInt()) {
			narcs = scan.nextInt();
		}
		for (int i = 0; i < narcs; i++) {
			System.out.println("Please enter x y w for arc[" + i + "]:");
			int x = -1, y = -1, w = 0;
			if (scan.hasNextInt()) {
				x = scan.nextInt();
				x = vex2i(vexs, x);
			}
			if (scan.hasNextInt()) {
				y = scan.nextInt();
				y = vex2i(vexs, y);
			}
			if (scan.hasNextInt()) {
				w = scan.nextInt();
			}
			if (x == -1 || y == -1) {
				System.out.println("x or y invalid.");
				i--;
				continue;
			}
			if (w <= 0) {
				System.out.println("w invalid.");
				i--;
				continue;
			}
			Arc arc = new Arc(x, y, w);
			pq.add(arc);
		}
	}

	public void kruskal() {
		// Union-Find Set
		UFSet ufset = new UFSet(vexs.length);
		// while not pq.empty, select min arc.w
		double ans = 0.0;
		while (!pq.isEmpty()) {
			Arc arc = pq.poll();
			int x = arc.getX();
			int y = arc.getY();
			int px = ufset.Find(x);
			int py = ufset.Find(y);
			if (px == py) {
				continue;
			} else {
				System.out.println("Arc " + vexs[x] + " ->" + vexs[y] + "");
				ufset.Union(x, y);
				ans += arc.getWeight();
			}
		}
		System.out.println("Final mst: " + ans);
	}

	private int vexs[] = null;
	private PriorityQueue<Arc> pq = null;

	public static void main(String[] args) {
		Kruskal kruskal = new Kruskal();
		// Input vexs and arcs
		kruskal.input();
		// Doing Kruskal
		kruskal.kruskal();
	}

}

输出结果:

Arc 1 ->3
Arc 4 ->6
Arc 2 ->5
Arc 3 ->6
Arc 2 ->3
Final mst: 15.0

 

 

 

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *