假设要在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

