最小生成树是连接图中所有顶点代价最小的树,通用算法是对于最小生成树的一个顶点子集A,设全体顶点集合为V,则跨越集合{A,V-A}中最小的的边为安全边,可以加入到最小生成树中。
Kruskal算法采用了不相交的集合森林,把每一个顶点初始化为一个单元素的集合,我再定义了一个边的结构,用于连接两个不同的集合。先用最小堆,以权重非递减的顺序处理边,若边的两个顶点不在同一个集合中,则将他们合并到同一个集合中,如此循环,知道所有的集合变为1个集合为止。
// disjoint_set_forest.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include<iostream>
#include<time.h>
#include<queue>
using namespace std;
typedef int ElementType;
#define numOfVertex 10 //图中顶点的个数
typedef int vertex;
//不相交的集合森林//////////////////////////////////
typedef struct TreeNode *Node;
struct TreeNode
{
ElementType key;
int rank;
Node parent;
};
//生成只有一个元素的新集合/////////////////////////////////////////
Node makeSet(ElementType x)
{
Node node = (Node)malloc(sizeof(TreeNode));
node->key = x;
node->rank = 0;
node->parent = node;
return node;
}
//这里传递的参数不能是ElementType类型,因为一开始所有的点都被初始化为一个集合,所以用node类型
Node findSet(Node node)
{
if (node != node->parent)
node->parent = findSet(node->parent);
return node->parent;
}
//连接两个集合的函数//////////////////////////////////////////////////
Node Link(Node node1, Node node2)
{
if (node1->rank > node2->rank)
{
node2->parent = node1;
return node1;
}
else
{
node1->parent = node2;
if (node1->rank == node2->rank)
node2->rank++;
return node2;
}
}
Node Union(Node x, Node y)
{
return Link(findSet(x), findSet(y));
}
///////////////////////////////////////////////////////////////////////////
//边的结构,重定义了操作符//////////////////////////////////////////////
struct Edge
{
Node node1, node2; //边的两个顶点
int weight; //边的权重
friend bool operator< (Edge edge1, Edge edge2) //重定义运算符
{
return edge1.weight > edge2.weight; //因为优先队列默认是<,因此若要形成最小堆,用>来重定义<
}
};
void Mst_Kruskal(int graph[numOfVertex][numOfVertex])
{
//先把每一个顶点生成一个单独的集合,以0为起点
Node node[numOfVertex];
for (int i = 0; i < numOfVertex; i++)
node[i] = makeSet(i);
//初始化边的结构
Edge edgeTemp;
priority_queue<Edge> edgess; //最小堆
for (int i = 0; i < numOfVertex; i++)
for (int j = i + 1; j < numOfVertex; j++)
{
if (graph[i][j] != 0)
{
edgeTemp.weight = graph[i][j];
edgeTemp.node1 = node[i];
edgeTemp.node2 = node[j];
edgess.push(edgeTemp);
}
}
//用队列来存放选中的边
queue<Edge> result;
while (!edgess.empty())
{
if (findSet(edgess.top().node1) != findSet(edgess.top().node2))
{
result.push(edgess.top());
Union((edgess.top()).node1, (edgess.top()).node2);
}
edgess.pop();
}
//打印选中的边
while (!result.empty())
{
edgeTemp = result.front();
result.pop();
cout << edgeTemp.node1->key << "----" << edgeTemp.node2->key << endl;;
}
}
int main()
{
//用初始化一个邻接矩阵的权重,注意只用到了上三角矩阵
srand((int)time(0)); //产生随机数种子
int graph[numOfVertex][numOfVertex];
for(int i=0;i<numOfVertex;i++)
for (int j = i+1; j < numOfVertex; j++)
graph[i][j]= rand() % 20;
//打印初始化后的邻接矩阵
for (int i = 0; i < numOfVertex; i++) {
for (int k = 0; k < i + 1; k++) cout << ' ' << '\t';
for (int j = i + 1; j < numOfVertex; j++) {
cout << graph[i][j] << '\t';
}
cout << endl;
}
//调用最小生成树的算法
Mst_Kruskal(graph);
while (1);
return 0;
}