最小生成树
定义:无向图连通图G中,由所有顶点构成且总价值(连接权值和)最低的一棵树(拓扑结构)称为一个最小生成树。
最小生成树顶点为|V|个,边数为|V|-1,没有圈。
对任一生成树T,如果将图中一条不属于T的边添加至T,则会产生一个圈,从该圈中除去任意一条边,则会又恢复成生成树。
下图为一示例:
对于上面的无向有权连通图,其最小生成树为:
解决最小生成树问题有两种算法Prim算法和Kruskal算法,二者都是基于贪心算法的策略。
Prim算法
由于最小生成树包含图中所有顶点。因此可以从顶点以及顶点间的连接权值考虑解决问题。
Prim算法考虑顶点以及邻接点,每一步基于贪心的策略,选取未知的且与已知顶点具有最小连接权值的顶点。
Prim算法和求带权最短路径Dijkstra算法一样,借助一个状态信息表InfoTable来标记每一步每个顶点的状态。
InfoTable中,每个顶点有三个状态:Known(标记顶点是否被声明已知),dist(与开始顶点的距离),Path(上一个被标记已知的顶点)。
与Dijkstra不同的是,这里的状态信息表更新规则为:在每个顶点v被选取以后,对于每个未知的v邻接点, d i s t w = m i n ( d i s t w , w e i g h t v , w ) d i s t w = m i n ( d i s t w , w e i g h t v , w ) 。另外Prim算法运行在无向图上,注意每条边出现在两个邻接表中。
Prim算法运行时间复杂度为 O ( | V | 2 ) O ( | V | 2 ) ,使用二叉堆时,运行时间为 O ( | E | log | V | ) O ( | E | log | V | ) 。
template <class T>
void Graph<T>::Prim(int index)
{
int MinIndex;
while (1 )
{
MinIndex = UnknownMinDistVertex(index);
if (MinIndex == -1 )
break ;
VSet[index].table[MinIndex].Known = true ;
for (vector <Vertex<T> >::iterator iter = VSet[MinIndex].adj_list.begin(); iter != VSet[MinIndex].adj_list.end();iter++)
{
if (!VSet[index].table[iter->value].Known)
if (iter->weight < VSet[index].table[iter->value].dist)
{
VSet[index].table[iter->value].dist = iter->weight;
VSet[index].table[iter->value].Path = VSet[MinIndex].value;
}
}
}
}
Kruskal算法
Kruskal算法同样采取贪心的策略来解决最小生成树问题。和Prim不同的是,Kruskal算法考虑图中的每条边。
Kruskal每次选择具有最小连接权值的边,当每次选择的边与已被选择的边不构成圈时,就选定该条边。
当选定的边数到达一定数目|V|-1时,算法终止,所有边会形成一个树即最小生成树。
判断是否形成圈:首先所有顶点各自构成一个集合,当它们之间的边被选定时,合并成一个大集合。算法进行到某一刻时,两个顶点u,v属于同一个集合当且仅当它们连通,如果下一步再出现一条边的两个顶点已经在集合中,则会形成一个圈,则不选择这条边。
Kruskal算法每次要寻找一条最小权值边,时间复杂度为