最小生成树——Prim算法和Kruscal算法

1、最小生成树的定义

        一个连通图的生成树是该连通图的一个极小连通子图,它含有图中全部顶点,但只构成一棵树的(n-1)条边。对于一个带权连通无向图G的不同生成树,各棵树的边上的权值之和可能不同,边上的权值之和最小的树称为该图的最小生成树。

2、最小生成树的应用

        比如要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。

3、最小生成树的构造算法

        图的邻接矩阵类型定义如下:

#define N 100

typedef char ElemType;

//adjacency matrix graph
typedef struct MGraph
{
	ElemType vertexes[N];
	int edges[N][N];
	int n;
}MGraph;

(1)普里姆(Prim)算法

        Prim算法的核心思想是,从一个顶点开始,加入顶点集合,将顶点集合中所有顶点到其他顶点的边作为候选边,每次从候选边中挑选最小权值的边作为生成树的边,然后更新候选边,直到图的所有顶点都被加入为止。其步骤如下:

        ①初始化顶点集合为传入的那个顶点,初始化候选边为该顶点到其他顶点的所有边

        ②重复步骤③,直到图中所有顶点都被加入为止

        ③从候选边中挑选最小权值的边作为生成树的边,此时因为加入了新的顶点,所以需要更新候选边

        代码实现:

void Prim(MGraph& g, int k)
{
	int* w = new int[g.n]; //the least edge weight
	int* v = new int[g.n]; //vertexes of the least weight
	for (int i = 0; i < g.n; i++)
	{
		w[i] = g.edges[k][i];
		v[i] = k;
	}
	w[k] = 0;
	for (int cnt = 1; cnt < g.n; cnt++)
	{
		//find the min val of w
		int imin = -1;
		for (int i = 0; i < g.n; i++)
		{
			if (w[i] != 0 && (imin == -1 || w[i] < w[imin]))
				imin = i;
		}
		//print
		cout << "(" << g.vertexes[v[imin]] << "," << g.vertexes[imin] << ")";
		//update the least weight
		w[imin] = 0; 
		for (int i = 0; i < g.n; i++)
		{
			if (g.edges[imin][i] != 0 && g.edges[imin][i] < w[i])
			{
				w[i] = g.edges[imin][i];
				v[i] = imin;
			}
		}
	}
	delete[] w, v;
}

        Prim算法包含两重for循环,其时间复杂度为O(n²)。

(2)克鲁斯卡尔(Kruscal)算法

        Kruscal算法的基本思想是,将图的所有边按权值递增的顺序进行排序,然后每次依次从小到大选择一条边加入到生成树中,但加入的前提是加入这条边后不会构成回路。其步骤如下:

        ①将图的所有边按权值递增的顺序进行排序

        ②重复步骤③,直到图中所有顶点都被加入为止

        ③按照从小到大的顺序选择一条边,如果这条边未使生成树形成回路,就把这条边加入到生成树中

        代码实现如下:

void Kruscal(MGraph& g)
{
	//define the Edge
	typedef struct Edge
	{
		int u, v;
		int w;
		Edge(int _u, int _v, int _w) :u(_u), v(_v), w(_w) {}
	}Edge;
	//init the edges
	vector v;
	for (int i = 0; i < g.n; i++)
	{
		for (int j = 0; j < g.n; j++)
		{
			auto w = g.edges[i][j];
			if (w != INT16_MAX && w != 0)
				v.push_back(Edge(i, j, w));
		}
	}
	//sort by weight increment
	sort(v.begin(), v.end(), [](Edge u, Edge v)->int {
		return u.w < v.w;
	});
	//add all edges if it does not constitute a loop
	int* set = new int[g.n];
	for (int i = 0; i < g.n; i++)
		set[i] = i;
	vector::iterator iter = v.begin();
	for (int cnt = 0; cnt < g.n - 1; )
	{
		if (set[iter->u] != set[iter->v])
		{
			cout << "(" << g.vertexes[iter->u] << "," << g.vertexes[iter->v] << ")";
			cnt++;
			for (int i = 0; i < g.n; i++)
			{
				if (set[i] == set[iter->v])
					set[i] = set[iter->u];
			}
		}
		iter++;
	}
	delete[] set;
}

        Kruscal算法同样包含两重for循环,其时间复杂度为O(n²),但可以对Kruscal算法做两方面的优化,一是将边集排序改为堆排序,二是用并查集来判断新加入边是否构成回路,优化后Kruscal算法的时间复杂度为O(elog₂e),e为图的所有边。

4、测试

求出给定无向带权图的最小生成树。图的定点为字符型,权值为不超过100的整形。

输入

第一行为图的顶点个数n
第二行为图的边的条数e
接着e行为依附于一条边的两个顶点和边上的权值

输出

最小生成树中的边。

样例输入

6
10
ABCDEF
A B 6
A C 1
A D 5
B C 5
C D 5
B E 3
E C 6
C F 4
F D 2
E F 6

样例输出

(A,C)(C,F)(F,D)(C,B)(B,E)
(A,C)(D,F)(B,E)(C,F)(B,C)

代码如下:

#include 
#include 
#include 
#include 
using namespace std;

#define N 100

typedef char ElemType;

//adjacency matrix graph
typedef struct MGraph
{
	ElemType vertexes[N];
	int edges[N][N];
	int n;
}MGraph;

void Prim(MGraph& g, int k)
{
	int* w = new int[g.n]; //the least edge weight
	int* v = new int[g.n]; //vertexes of the least weight
	for (int i = 0; i < g.n; i++)
	{
		w[i] = g.edges[k][i];
		v[i] = k;
	}
	w[k] = 0;
	for (int cnt = 1; cnt < g.n; cnt++)
	{
		//find the min val of w
		int imin = -1;
		for (int i = 0; i < g.n; i++)
		{
			if (w[i] != 0 && (imin == -1 || w[i] < w[imin]))
				imin = i;
		}
		//print
		cout << "(" << g.vertexes[v[imin]] << "," << g.vertexes[imin] << ")";
		//update the least weight
		w[imin] = 0; 
		for (int i = 0; i < g.n; i++)
		{
			if (g.edges[imin][i] != 0 && g.edges[imin][i] < w[i])
			{
				w[i] = g.edges[imin][i];
				v[i] = imin;
			}
		}
	}
	delete[] w, v;
}

void Kruscal(MGraph& g)
{
	//define the Edge
	typedef struct Edge
	{
		int u, v;
		int w;
		Edge(int _u, int _v, int _w) :u(_u), v(_v), w(_w) {}
	}Edge;
	//init the edges
	vector v;
	for (int i = 0; i < g.n; i++)
	{
		for (int j = 0; j < g.n; j++)
		{
			auto w = g.edges[i][j];
			if (w != INT16_MAX && w != 0)
				v.push_back(Edge(i, j, w));
		}
	}
	//sort by weight increment
	sort(v.begin(), v.end(), [](Edge u, Edge v)->int {
		return u.w < v.w;
	});
	//add all edges if it does not constitute a loop
	int* set = new int[g.n];
	for (int i = 0; i < g.n; i++)
		set[i] = i;
	vector::iterator iter = v.begin();
	for (int cnt = 0; cnt < g.n - 1; )
	{
		if (set[iter->u] != set[iter->v])
		{
			cout << "(" << g.vertexes[iter->u] << "," << g.vertexes[iter->v] << ")";
			cnt++;
			for (int i = 0; i < g.n; i++)
			{
				if (set[i] == set[iter->v])
					set[i] = set[iter->u];
			}
		}
		iter++;
	}
	delete[] set;
}

int main()
{
	MGraph g;
	while(cin >> g.n)
	{
		//input
		int e;
		cin >> e;
		map m;
		for (int i = 0; i < g.n; i++)
		{
			cin >> g.vertexes[i];
			m[g.vertexes[i]] = i;
		}
		for (int i = 0; i < g.n; i++)
			for (int j = 0; j < g.n; j++)
				g.edges[i][j] = INT16_MAX;
		
		char u, v;
		int w;
		for (int cnt = 0; cnt < e; cnt++)
		{
			cin >> u >> v >> w;
			g.edges[m[u]][m[v]] = g.edges[m[v]][m[u]] = w;
		}
		//excute
		Prim(g, 0); cout << endl; //Prim algorithm
		Kruscal(g); cout << endl; //Kruscal algorithm
	}
	return 0;
}

参考文献

[1] 李春葆.数据结构教程.清华大学出版社,2013.

[2] 最小生成树.百度百科[引用日期2018-04-29].

你可能感兴趣的:(数据结构和算法)