8.1 最小生成树
8.1.1 什么是最小生成树
解决最小生成树有很多算法,但是归结起来都是贪心算法。
贪心算法:
什么是“贪”:每一步都要最好的什么是“好”:权重最小的边但是因为是最小生成树,所以这个贪心算法还需要约束:
只能用图里有的边只能正好用掉 | V | - 1 条边不能有回路
贪心算法由两个著名的算法:Prim 算法 和 Kruskal 算法。
8.1.2 Prim 算法
Prim 算法–选一个结点,让一棵小树“长大” (和之前 单源最短路径中的 Dijkstra算法很像)(针对的是点)
和 Dijkstra算法 进行比较:
C 语言实现
Vertex FindMinDist( MGraph Graph, WeightType dist[] )
{
Vertex MinV, V;
WeightType MinDist = INFINITY;
for (V=
0; V<Graph->Nv; V++) {
if ( dist[V]!=
0 && dist[V]<MinDist) {
MinDist = dist[V];
MinV = V;
}
}
if (MinDist < INFINITY)
return MinV;
else return ERROR;
}
int Prim( MGraph Graph, LGraph MST )
{
WeightType dist[MaxVertexNum], TotalWeight;
Vertex
parent[MaxVertexNum], V, W;
int VCount;
Edge E;
for (V=
0; V<Graph->Nv; V++) {
dist[V] = Graph->G[
0][V];
parent[V] =
0;
}
TotalWeight =
0;
VCount =
0;
MST = CreateGraph(Graph->Nv);
E = (Edge)malloc( sizeof(struct ENode) );
dist[
0] =
0;
VCount ++;
parent[
0] = -
1;
while (
1) {
V = FindMinDist( Graph, dist );
if ( V==ERROR )
break;
E->V1 =
parent[V];
E->V2 = V;
E->Weight = dist[V];
InsertEdge( MST, E );
TotalWeight += dist[V];
dist[V] =
0;
VCount++;
for( W=
0; W<Graph->Nv; W++ )
if ( dist[W]!=
0 && Graph->G[V][W]<INFINITY ) {
if ( Graph->G[V][W] < dist[W] ) {
dist[W] = Graph->G[V][W];
parent[W] = V;
}
}
}
if ( VCount < Graph->Nv )
TotalWeight = ERROR;
return TotalWeight;
}
8.1.3 Kruskal 算法
如果图是比较稀疏的,也就是说有很多结点,但是边的数量很少,那么Kruskal 算法 就更合适。
Kruskal 算法 的思想是:将森林合并成树 (针对的是边)
伪代码:
C 语言实现
typedef Vertex ElementType;
typedef Vertex SetName;
typedef ElementType SetType[MaxVertexNum];
void InitializeVSet( SetType S,
int N )
{
ElementType X;
for ( X=
0; X<N; X++ ) S[X] = -
1;
}
void Union( SetType S, SetName Root1, SetName Root2 )
{
if ( S[Root2] < S[Root1] ) {
S[Root2] += S[Root1];
S[Root1] = Root2;
}
else {
S[Root1] += S[Root2];
S[Root2] = Root1;
}
}
SetName Find( SetType S, ElementType X )
{
if ( S[X] <
0 )
return X;
else
return S[X] = Find( S, S[X] );
}
bool CheckCycle( SetType VSet, Vertex V1, Vertex V2 )
{
Vertex Root1, Root2;
Root1 = Find( VSet, V1 );
Root2 = Find( VSet, V2 );
if( Root1==Root2 )
return false;
else {
Union( VSet, Root1, Root2 );
return true;
}
}
void PercDown( Edge ESet,
int p,
int N )
{
int Parent, Child;
struct ENode X;
X = ESet[p];
for( Parent=p; (Parent*
2+
1)<N; Parent=Child ) {
Child = Parent *
2 +
1;
if( (Child!=N-
1) && (ESet[Child].Weight>ESet[Child+
1].Weight) )
Child++;
if( X.Weight <= ESet[Child].Weight )
break;
else
ESet[Parent] = ESet[Child];
}
ESet[Parent] = X;
}
void InitializeESet( LGraph Graph, Edge ESet )
{
Vertex V;
PtrToAdjVNode W;
int ECount;
ECount =
0;
for ( V=
0; V<Graph->Nv; V++ )
for ( W=Graph->G[V].FirstEdge; W; W=W->Next )
if ( V < W->AdjV ) {
ESet[ECount].V1 = V;
ESet[ECount].V2 = W->AdjV;
ESet[ECount++].Weight = W->Weight;
}
for ( ECount=Graph->Ne/
2; ECount>=
0; ECount-- )
PercDown( ESet, ECount, Graph->Ne );
}
int GetEdge( Edge ESet,
int CurrentSize )
{
Swap( &ESet[
0], &ESet[CurrentSize-
1]);
PercDown( ESet,
0, CurrentSize-
1 );
return CurrentSize-
1;
}
int ( LGraph Graph, LGraph MST )
{
WeightType TotalWeight;
int ECount, NextEdge;
SetType VSet;
Edge ESet;
InitializeVSet( VSet, Graph->Nv );
ESet = (Edge)
malloc(
sizeof(
struct ENode)*Graph->Ne );
InitializeESet( Graph, ESet );
MST = CreateGraph(Graph->Nv);
TotalWeight =
0;
ECount =
0;
NextEdge = Graph->Ne;
while ( ECount < Graph->Nv-
1 ) {
NextEdge = GetEdge( ESet, NextEdge );
if (NextEdge <
0)
break;
if ( CheckCycle( VSet, ESet[NextEdge].V1, ESet[NextEdge].V2 )==
true ) {
InsertEdge( MST, ESet+NextEdge );
TotalWeight += ESet[NextEdge].Weight;
ECount++;
}
}
if ( ECount < Graph->Nv-
1 )
TotalWeight = -
1;
return TotalWeight;
}
8.2 拓扑排序
例:计算机专业排课。每一门课都有一个预修课程。
如果计算机的课多了,肯定要写一个程序解决问题。我们可以用图的结构来解决,把每一门课当做一个顶点,边是预修课程。
这种关系依赖图,称作AOV(Activity On Vertex) 网络。
在一个有限图中,所有的真实活动是表现在顶点上的,而顶点和顶点之间的有向边表示了两个活动之间的先后顺序。整个问题抽象一下就成了一个拓扑排序的问题。(引出拓扑排序)
8.2.1 拓扑排序定义
拓扑序:如果图中从V 到 W有一条有向路径,则V 一定排在 W之前。满足此条件的顶点是序列称为一个拓扑序。
获得一个拓扑序的过程就是 拓扑排序。
AOV 如果有合理的拓扑序,则必定是有向无环图。
C语言实现:拓扑排序
bool TopSort( LGraph Graph, Vertex TopOrder[] )
{
int Indegree[MaxVertexNum], cnt;
Vertex V;
PtrToAdjVNode W;
Queue Q = CreateQueue( Graph->Nv );
for (V=
0; V<Graph->Nv; V++)
Indegree[V] =
0;
for (V=
0; V<Graph->Nv; V++)
for (W=Graph->G[V].FirstEdge; W; W=W->Next)
Indegree[W->AdjV]++;
for (V=
0; V<Graph->Nv; V++)
if ( Indegree[V]==
0 )
AddQ(Q, V);
cnt =
0;
while( !IsEmpty(Q) ){
V = DeleteQ(Q);
TopOrder[cnt++] = V;
for ( W=Graph->G[V].FirstEdge; W; W=W->Next )
if ( --Indegree[W->AdjV] ==
0 )
AddQ(Q, W->AdjV);
}
if ( cnt != Graph->Nv )
return false;
else
return true;
}
8.2.2 关键路径
对拓扑排序的重要应用就是:关键路径
AOE (Activity On Edge)网络:
AOE 网络与 AOV 网络相反,AOE 网络所有的活动表现在边上,而顶点表示活动几到达这个顶点时结束。
例子: