一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。(from 百度百科)
此算法采用贪心算法的思想,运行时间为O( n^2 )。
基本步骤:
设R是有n个定点的对称连通关系。
首先,要用二维数组记录点和权值。如上图所示无向图:
int map[7][7];
map[1][2]=map[2][1]=4;
map[1][3]=map[3][1]=2;
……
然后再求最小生成树。具体方法是:
1.先选取一个点作起始点,然后选择它邻近的权值最小的点(如果有多个与其相连的相同最小权值的点,随便选取一个)。如1作为起点。
visited[1]=1;
pos=1;
//用low[]数组不断刷新最小权值,lowi的值为:i点到邻近点(未被标记)的最小距离。
low[1]=0; //起始点i到邻近点的最小距离为0
low[2]=map[pos][2]=4;
low[3]=map[pos][3]=2;
low[4]=map[pos][4]=3;
low[5]=map[pos][5]=MaxInt; //无法直达
low[6]=map[pos][6]=MaxInt;
2.再在伸延的点找与它邻近的两者权值最小的点。
//low[]以3作当前位置进行更新
visited[3]=1;
pos=3;
low[1]=0; //已标记,不更新
low[2]=map[1][2]=4; //比5小,不更新
low[3]=2; //已标记,不更新
low[4]=map[1][4]=3; //比1大,更新后为:low[4]=map[3][4]=1;
low[5]=map[1][5]=MaxInt;//无法直达,不更新
low[6]=map[1][6]=MaxInt;//比2大,更新后为:low[6]=map[1][6]=2;
//
// main.cpp
// prim
//
// Created by 颜泽鑫 on 6/12/16.
// Copyright © 2016 颜泽鑫. All rights reserved.
//
#include
#include
#define MaxInt numeric_limits<int>::max()
#define MaxEle 120
using namespace std;
int map[MaxEle][MaxEle];
int visited[MaxEle];
int distance_low[MaxEle];
/**
* compute the minimal spanning tree
*
* @param n the size of matrix
*
* @return the minimal value of MST
*/
int prim(int n) {
int pos, min, MST = 0;
// The array 'visited' represent which
// vertix has been visited already to avoid forming circle.
visited[1] = 1;
pos = 1;
// The array 'distance_distance_low' represent that
// arriving vertix i will cost distance_distance_low[i] steps now.
for (int i = 2; i <= n; i++) {
if (map[pos][i] == 0) {
distance_low[i] = MaxInt;
} else {
distance_low[i] = map[pos][i];
}
}
// manipulate n-1 times at all.
for (int i = 1; i <= n - 1; i++) {
min = MaxInt;
// Find the minimal value of distance
// and corresponding position.
for (int j = 1; j <= n; j++) {
if (visited[j] == 0 && min > distance_low[j]) {
min = distance_low[j];
pos = j;
}
}
visited[pos] = 1;
MST += min;
// Because of add a new vertix, it is neccessary to
// reload the information of 'distance_low'.
for (int j = 1; j <= n; j++) {
if (visited[j] == 0 && distance_low[j] > map[pos][j]) {
if (map[pos][j] != 0) {
distance_low[j] = map[pos][j];
}
}
}
}
return MST;
}
int main() {
int n;
/**
* input the size of matrix
*/
while (scanf("%d", &n) != EOF) {
// Initialise the map;
memset(map, MaxInt, sizeof(map));
/**
* input matrix information in ordered.
*/
for (int i = 1; i != n + 1; i++) {
for (int j = 1; j != n + 1; j++) {
int ele;
cin >> ele;
map[i][j] = map[j][i] = ele;
}
}
int ans = prim(n);
cout << ans << endl;
}
return 0;
}
Kruskal算法也是采用贪心算法的思想,运行时间为O(nlogn)。
设R是有n个顶点的对称连通关系。S={e1,e2,e3…..ek}是R的加权边集合。
1、利用优先级队列将权值小的边放到队列最前,优先出对,保证了每次选择的都是权值最小的边。
2、利用并查集的查找及结合把同处同一连通分量中的顶点连到同一父节点下。这样,每次判断是否构成回路,只要判断父节点是否相同的即可。
我们可以把每个连通分量看成一个集合,该集合包含了连通分量的所有点。而具体的连通方式无关紧要,好比集合中的元素没有先后顺序之分,只有“属于”与“不属于”的区别。图的所有连通分量可以用若干个不相交集合来表示。
而并查集的精妙之处在于用数来表示集合。如果把x的父结点保存在p[x]中(如果没有父亲,p[x]=x),则不难写出结点x所在树的递归程序:
find(int x) {return p[x]==x?x:p[x]=find(p[x]);}
意思是,如果p[x]=x,说明x本身就是树根,因此返回x;否则返回x的父亲p[x]所在树的根结点。
既然每棵树表示的只是一个集合,因此树的形态是无关紧要的,并不需要在“查找”操作之后保持树的形态不变,只要顺便把遍历过的结点都改成树根的儿子,下次查找就会快很多了。如下图所示:
#include
#include
using namespace std;
#define max 200
int MST; // 最小生成树
int Nodenum;
int father[max];
struct edge {
int from;
int to;
int cost;
friend bool operator < (const edge& a, const edge& b) {
return a.cost > b.cost;
}
};
// to find the minimal distance of edge.
priority_queue SQ;
/**
* to find the final father of the node.
*
* @param x node index.
*
* @return father index.
*/
int find(int x) {
return x == father[x] ? x : find(father[x]);
}
int Kruskal() {
MST = 0;
for (int i = 1; i <= Nodenum; i++) {
father[i] = i;
}
int num = 0;
while (!SQ.empty() && num != Nodenum - 1) {
edge temp = SQ.top();
SQ.pop();
int father_x = find(temp.from);
int father_y = find(temp.to);
if (father_x != father_y) {
father[father_y] = find(father[father_x]);
MST += temp.cost;
num++;
}
}
/**
* Let every index has their final father.
*/
for (int i = 1; i <= Nodenum; i++) {
father[i] = find(father[i]);
}
return MST;
}
bool judge() {
int flag = father[1];
for (int i = 2; i != Nodenum + 1; i++) {
if (flag != find(father[i])) {
return false;
}
}
return true;
}
int main() {
int edgeNum; // 边总数
cin >> Nodenum;
cin >> edgeNum;
while (!SQ.empty()) {
SQ.pop();
}
while (edgeNum--) {
int from; int to; int cost;
cin >> from >> to >> cost;
edge a;
a.from = from;
a.to = to;
a.cost = cost;
SQ.push(a);
}
int MST = Kruskal();
if (judge()) {
cout << MST << endl;
} else {
cout << "Don't exist!" << endl;
}
return 0;
}
/*
It daesn't matter the order of edge.
standard sample:
8
11
2 1 3
3 1 2
3 2 4
2 4 2
3 4 3
4 5 5
3 5 2
5 6 4
7 6 5
8 5 5
7 8 4
*/