数据结构——最小生成树Kruskal算法

                       最小生成树Kruskal算法

       上一篇博客讲了最小生成树的Prim算法。而另一种常见的最小生成树算法就是克鲁斯卡尔算法。
       Kruskal算法跟Prim算法的思路不一样,Prim算法还是一种树由根到叶子节点的生成方法,也就是生成树慢慢长大,每次都往生成树中添加新的节点,最后让生成树中包含图中的所有节点。而Kruskal算法则是初始时把所有的节点都当成一棵树,通过树的合并来最终生成一棵包含所有节点的树,也就是图的最小生成树。两棵树如何合并呢?非常简单,就是在两棵树中分别选一个节点出来,把这两个节点用一根线连起来,两棵树就合并成一棵树了,那么要求合并后的权值最小,那么就要求这条新生成的边权值最小。再反过来想,如何取得最小边呢?那就是从图的边集中选择最小权值的边,看这条边的两个节点是不是分别在两棵树中,如果不是,那么说明这边条已经在生成了的树中了,舍去,再从图的边集中取另外一条权值最小的边,直到取到了一条边,它的两个节点分别在两棵树中为止。这时,就让这条边的两个节点所在的两棵树合并成一棵,树集中的两棵树就变为了一棵。这样一直进行下去,当树集中只剩一棵树时,就说明最终的最小生成树完成了。
       如何在边集中选择满足条件且权值最小的一条边呢?这里,可以把图的所有的边放入一个集合中,对这个集合进行排序,因为算法过程中不会对边的权值进行修改,所以不会破坏边集的有序性,整个Kruskal算法过程只需要对边集进行一次排序。排序时按增序列排序。给每条边一个标记,当标记为true是表示这边表已经被用过了,为false表示还没有被用过。这样从边集中选择满足条件且权值最小的边就只需要从左到右遍历边集,选择第一个未被使用过的边就可以了。
       算法策略还是很简单的,下面是基于C++的代码实现
#include "stdafx.h"
#include 
using namespace std;

const int MAX = 1e5;

/**
* 返回cv节点的第一个邻接节点
*/
int FirstAdjVex(int cv,int *vs, int **weights, int size) 
{
	if (cv < 0 || cv >= size) 
	{
		return -1;
	}
	for (int v = 0;v < size;v++) 
	{
		if (weights[cv][v] < MAX) 
		{
			return v;
		}
	}
	return -1;
}

/**
* 返回cv相对ca的下一个邻接节点
*/
int NextAdjVex(int cv,int ca,int *vs,int **weights,int size)
{
	if(cv < 0 || cv >= size || ca < 0 || ca >= size -1)//最后一个bool分式表示如果当前ca是size-1了,就不存在后一个邻接节点了
	{
		return -1;
	}
	for (int v = ca + 1;v < size;v++) 
	{
		if(weights[cv][v] < MAX)
		{
			return v;
		}
	}
	return -1;
}
/**
* 定义图中的边的结构体
*/
struct Line
{
	int w;//边的权值
	int v1, v2;//边的两个节点
	bool used;
};
void SortLines(Line **lines,int size)
{
	Line *temp;
	//冒泡排序
	for (int i = 0;i < size;i++)
	{
		for (int j = i + 1;j < size;j++)
		{
			if(lines[i]->w>lines[j]->w)
			{
				temp = lines[i];
				lines[i] = lines[j];
				lines[j] = temp;
			}
		}
	}
}
/**
* 在边集中查找权值最小,且两个节点不在同一棵树下的边
*/
int UnusedMinLine(Line **lines,int edgeNum,int *treeRoots,int size)
{
	Line *line;
	for (int i = 0;i < edgeNum;i++)
	{
		line = lines[i];
		if (!line->used && treeRoots[line->v1] != treeRoots[line->v2]) 
		{
			return i;
		}
	}
	return -1;
}
/**
* 将v1和v2所在的两棵树合并成一棵,并且树的根节点是v1所在树的根节点
*/
void TreeUnion(int *treeRoots, int size,int v1,int v2) 
{
	int treeRoot1 = treeRoots[v1];
	int treeRoot2 = treeRoots[v2];
	for (int i = 0;i < size;i++)
	{
		if(treeRoots[i] == treeRoot2)//当前节点所在树的根节点是v2所在树的根节点,则把当前节点的根节点改成v1所在树的根节点
		{
			treeRoots[i] = treeRoot1;
		}
	}
}
void Kruskal(int *nodes, int **weights, int size,int edgeNum) 
{
	int *treeRoots = new int[size];//树的根节点
	for (int i = 0;i < size;i++)
	{
		treeRoots[i] = i;
	}
	//构造边集
	Line **lines = new Line*[edgeNum];
	int index = 0;
	for (int i = 0;i < size;i++)
	{
		for (int j = i + 1;j < size;j++)//由于无向图的邻接矩阵是一个对称矩阵,所以只需要遍历一半,这里是遍历右上部分
		{
			if (weights[i][j] < MAX)
			{
				Line *line = new Line;
				line->v1 = i;
				line->v2 = j;
				line->w = weights[i][j];
				line->used = false;
				lines[index++] = line;
			}
		}
	}
	SortLines(lines,edgeNum);
	Line *line;
	while (true)
	{
		index = UnusedMinLine(lines, edgeNum,treeRoots,size);
		if(index>=0)//找到了满足条件的最小边
		{
			line = lines[index];
			cout << "(" << line->v1 << "," << line->v2 << ") --> " << line->w << endl;
			line->used = true;
			TreeUnion(treeRoots, size, line->v1, line->v2);
		}else
		{
			break;
		}
	}
}
int main()
{
	int size = 6;
	int u0 = 0;
	int *nodes = new int[size];
	for (int i = 0;i < size;i++)
	{
		nodes[i] = i;
	}
	int **weights = new int*[size];
	for (int i = 0;i < size;i++)
	{
		weights[i] = new int[size];
		for (int j = 0;j < size;j++)
		{
			weights[i][j] = MAX;
		}
	}
	weights[0][1] = 8;
	weights[0][2] = 1;
	weights[0][3] = 6;
	weights[1][0] = 8;
	weights[1][2] = 6;
	weights[1][4] = 3;
	weights[2][0] = 1;
	weights[2][1] = 6;
	weights[2][3] = 6;
	weights[2][4] = 8;
	weights[2][5] = 5;
 	weights[3][0] = 6;
	weights[3][2] = 6;
	weights[3][5] = 2;
	weights[4][1] = 3;
	weights[4][2] = 8;
	weights[4][5] = 8;
	weights[5][2] = 5;
	weights[5][3] = 2;
	weights[5][4] = 8;
	Kruskal(nodes, weights, size, 10);
	system("pause");
	return 0;
}
执行这段代码,结果如下
数据结构——最小生成树Kruskal算法_第1张图片
       这样可以生成一棵最小生成树。上面的代码中用treeRoots保存了每个节点所在的树的根节点,这样可以用来选择出边的两个节点不在同一棵树、没被用过且边的权值最小的边。用得还是比较巧妙的。

你可能感兴趣的:(学习,数据结构)