最小生成树是这样一棵树,它包含了图中的所有节点,并且使得总的边权值之和最小。显然,一个图存在最小生成树当且仅当该图连通。对于最小生成树的求法,常用的有两种算法。注意:这两种算法对于负边权值的图也成立。
1.Prim算法
该算法和Dijkstra算法很像,采用的思路是广度优先搜索。将节点分成两个集合S和T,S中为已经处理的节点,T中为未处理的节点。每进行一次操作,在最小生成树中添加一条边(u,v),u∈S,v∈T,该边为S中的节点到T中的节点距离最小的那一条边,添加完成后,将节点v加入集合S中。更新与v相邻并且未被访问的节点的距离,重复之前的步骤。与节点v邻接的节点w的权值更新为min{dw,Cvw}。可由反证法证明该算法的正确性。
程序代码如下:
#include
#define x 10000
const int NumVertex = 7;
int map[NumVertex][NumVertex] =
//1 2 3 4 5 6 7
{
x, 2, 4, 1, x, x, x, //1
2, x, x, 3, 10, x, x, //2
4, x, x, 2, x, x, x, //3
1, 3, 2, x, 7, 8, 4, //4
x, 10, x, 7, x, x, 6, //5
x, x, 5, 8, x, x, 1, //6
x, x, x, 4, 6, 1, x //7
};
int distance[NumVertex] = { x, x, x, x, x, x, x };//第i个节点到已知节点的最小权值
int path[NumVertex];//导致第i个节点距离变化的最后一个顶点
int Known[NumVertex];//节点访问情况
void Prim(int Begin, int map[][NumVertex])
{
distance[Begin] = 0;
for (;;)
{
int min = x, position=0;
for (int i = 0; i=distance[i])
{
min = distance[i];
position = i;
}
} //寻找距离最短的节点
Known[position] = 1;
if (min == x)
break;
for (int i = 0; i < NumVertex; i++)
{
if (map[position][i] != x&&Known[i] == 0)
{
if (distance[i]>map[position][i])
{
distance[i] = map[position][i];
path[i] = position+1;
} //更新距离
}
}
}
}
void printarray(int a[])
{
for (int i = 0; i < NumVertex; i++)
{
printf("%d ", a[i]);
}
}
void main()
{
Prim(0, map);
printarray(distance);
printf("\n");
printarray(path);
printf("\n");
}
2.Kruskal算法
该算法的思路比较直观,它从图中的权值最小的边开始,依次选择图中的边,然后判断这条变是否可以接受,当可以接受时将它加入生成树中,当不能接受时选择下一条边。当选定边(u,v)时,如果使得树产生圈,则这条边是不能被接受的,如果不构成圈,那么就能将这条变加到树中。将在同一棵子树中的节点划分到同一集合,将边(u,v)加到树中,可以将其视为集合的合并,所以判断该条边是否能够接受的方法可以采用Find/Union操作,当Find(u)!=Find(v)时,(u,v)能够被接受,执行一次Union(u,v)操作即可。
程序代码如下:
#include
#include
#define x 10000
const int NumVertex = 7;
const int NumEdge = 12;
int map[NumVertex][NumVertex] =
// 0 1 2 3 4 5 6
{
x, 2, 4, 1, x, x, x, //0
2, x, x, 3, 10, x, x, //1
4, x, x, 2, x, 5, x, //2
1, 3, 2, x, 7, 8, 4, //3
x, 10, x, 7, x, x, 6, //4
x, x, 5, 8, x, x, 1, //5
x, x, x, 4, 6, 1, x //6
};
int hash[NumVertex];//节点所属的集合
int Edge[NumEdge];//按照邻接表的排列顺序给每条边编号,该数组表示图中第i条边的长度
int BeginVertex[NumEdge], EndVertex[NumEdge];//图中边的起始节点和图中边的终止节点
int AcceptedEdge[NumEdge];//每条边被接受的情况,接受为1,不接受为0
int Known[NumEdge];
int Find(int Vertex)
{
return hash[Vertex];
}//查找节点所属的集合
void Union(int Vertex1, int Vertex2)
{
for (int i = 0; i < NumVertex; i++)
{
if (hash[i] == hash[Vertex2])
hash[i] = hash[Vertex1];
}
}//合并两个节点
void Kruskal(int map[][NumVertex])
{
int EdgeOrder = 0;
for (int i = 0; i < NumVertex; i++)
{
hash[i] = i + 1;
}//散列函数,返回节点所属的集合
for (int i = 0; i < NumVertex; i++)
{
for (int j = i+1; j < NumVertex; j++)
{
if (map[i][j]Edge[i])
{
min = Edge[i];
position = i;
}
}//搜索距离最小的边
Known[position] = 1;//标记该变已经被访问过
if (Find(BeginVertex[position]) != Find(EndVertex[position]))
{
Union(BeginVertex[position], EndVertex[position]);
AcceptedEdge[position] = 1;
}//如果选取的边加到图中没有构成圈(两节点属于不同集合),则该边可以接受,遂把该边加入图中
}
}
void printarray(int array[])
{
for (int i = 0; i