八、图(下):公路村村通
目录
- 题目描述
- 代码
- 解题思路和出现的问题
题目描述 现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。
输入格式:
输入数据包括城镇数目正整数N(≤1000)和候选道路数目M(≤3N);随后的M行对应M条道路,每行给出3个正整数,分别是该条道路直接连通的两个城镇的编号以及该道路改建的预算成本。为简单起见,城镇从1到N编号。
输出格式:
输出村村通需要的最低成本。如果输入数据不足以保证畅通,则输出?1,表示需要建设更多公路。
输入样例:
6 15
1 2 5
1 3 3
1 4 7
1 5 4
1 6 2
2 3 4
2 4 6
2 5 2
2 6 6
3 4 6
3 5 1
3 6 1
4 5 10
4 6 8
5 6 3
输出样例:
12
代码
#include
#include
#include
#define MaxVerteNum 1000
typedef int Vertex;
typedef int WeightType;
typedef struct ENode *PtrToENode;
struct ENode{
WeightType Weight;
Vertex V1;
Vertex V2;
};
typedef PtrToENode Edge;
typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode{
PtrToAdjVNode Next;
Vertex AdjV;
WeightType Weight;
/* 存储边的权重 */
};
typedef struct HNode{
PtrToAdjVNode FirstEdge;
}AdjList[MaxVerteNum];
typedef struct LNode *LGraph;
struct LNode{
int Nv;
int Ne;
AdjList G;
};
LGraph Create( int VertexNum )
{
Vertex V;
LGraph Graph;
Graph = (LGraph)malloc(sizeof(struct LNode));
Graph->Nv = VertexNum;
Graph->Ne = 0;
for( V=0;
V!=VertexNum;
++V )
Graph->G[V].FirstEdge = NULL;
return Graph;
}void InsertEdge( LGraph Graph, PtrToENode E )
{
PtrToAdjVNode NewNode;
NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
NewNode->AdjV = E->V2;
NewNode->Weight = E->Weight;
NewNode->Next = Graph->G[E->V1].FirstEdge;
Graph->G[E->V1].FirstEdge = NewNode;
NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
NewNode->AdjV = E->V1;
NewNode->Weight = E->Weight;
NewNode->Next = Graph->G[E->V2].FirstEdge;
Graph->G[E->V2].FirstEdge = NewNode;
}LGraph BuildGraph()
{
LGraph Graph;
PtrToENode E;
Vertex V;
int Nv, i;
scanf("%d",&Nv);
Graph = Create(Nv);
scanf("%d",&(Graph->Ne));
if(Graph->Ne){
E = (PtrToENode)malloc(sizeof(struct ENode));
for(i=0;
iNe;
++i){
scanf("\n%d %d %d",&E->V1,&E->V2,&E->Weight);
--E->V1;
--E->V2;
InsertEdge( Graph, E );
}
}
return Graph;
}/* -----------顶点并查集定义----------- */
typedef Vertex ElementType;
/* 元素类型 */
typedef Vertex SetName;
/* 根结点下标作为集合名称 */
typedef ElementType SetType[MaxVerteNum];
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[Root1] < S[Root2] ){
S[Root2] += S[Root1];
/* 集合1并入集合2 */
S[Root1] = Root2;
}
else {
S[Root1] += S[Root2];
S[Root2] = Root1;
}
}SetName Find( SetType S, ElementType X )
{ /* 集合元素已经全部初始化为-1 */
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 );
/* 得到V1所属的连通集名称 */
Root2 = Find( VSet, V2 );
/* 得到V2所属的连通集名称 */
if ( Root1 == Root2 )
return false;
else {
Union( VSet, Root1, Root2 );
return true;
}
}
/*-----------并查集定义结束--------------*//*-----------边的最小堆定义--------------*/
void PerDown( Edge ESet, int p, int N )
{/* 假设含有N个元素的边数组中,以ESet[p]为根结点的左右子堆都是最小堆 */
/* 将ESet[p]为根结点的堆调整为最小堆 */
int Parent, Child;
struct ENode X;
X = ESet[p];
for( Parent = p;
(Parent*2+1)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 )
{ /* 将图的边存入数组ESet,并且初始化为最小堆 */
Vertex V;
PtrToAdjVNode W;
int ECount;
/* 将图的边存入数组ESet */
ECount = 0;
for( V=0;
VNv;
++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-1;
ECount>=0;
--ECount )
PerDown(ESet, ECount, Graph->Ne);
} void Swap( PtrToENode E1, PtrToENode E2 )
{
struct ENode ETemp;
ETemp = *E2;
*E2 = *E1;
*E1 = ETemp;
}int GetEdge(Edge ESet, int CurrentSize)
{ /* 给定当前堆的大小CurrentSize,将当前最小边位置弹出并调整堆 */
Swap( &ESet[0],&ESet[CurrentSize-1] );
PerDown( ESet, 0, CurrentSize-1 );
return CurrentSize - 1;
}
/* ------------边的最小堆定义结束--------------- */int Kruskal( 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 = Create(Graph->Nv);
TotalWeight = 0;
ECount = 0;
NextEdge = Graph->Ne;
/* 原始的边的集合的规模,也就是最小堆的规模 */
while ( ECount < Graph->Nv-1 ){/* 构成树的边数应该恰好等于Graph->Nv-1 */
NextEdge = GetEdge(ESet, NextEdge);
/* 获得Weight最小的边的位置,并且这个位置也作为新的最小堆的规模 */
if( NextEdge == 0 ) /* 边集中没有边了 */
break;
/* 如果该边的加入不构成回路,即两端结点不属于同一连通集 */
if ( CheckCycle( VSet, ESet[NextEdge].V1, ESet[NextEdge].V2 )){
/* 将该边插入MST */
InsertEdge( MST, ESet+NextEdge );
TotalWeight += ESet[NextEdge].Weight;
/* 累计权重 */
++ECount;
}
}
if( ECount < Graph->Nv-1 ) /* 边集中没有边了,从while循环中跳出 */
TotalWeight = -1;
/* 设置错误标记,表示生成树不存在 */
return TotalWeight;
}int main()
{
LGraph Graph = BuildGraph();
LGraph MST;
int TotalWeight = Kruskal( Graph, MST );
printf("%d", TotalWeight);
return 0;
}
解题思路和出现的问题 1.建立图,把边和顶点都储存在图中。
2.所有边储存在最小堆中,这样是方便每次取出Weight最小的边。
3.取出Weight最小的边,重新整理最小堆。看如果这条边插入生成树中会不会有回路(方法是利用并查集)
【八、图(下):公路村村通】出现的问题主要是在最小堆里面,最小堆储存的是边不是顶点,所以调用下滤函数
PerDown(ESet, ECount, Graph->Ne);
这里不能写成Graph->Nv
。以后每次写完一个函数都要检查一下变量是不是对的,否则出错了要从头到尾检查太慢了。推荐阅读
- 宽容谁
- 一个人的旅行,三亚
- 第6.2章(设置属性)
- 布丽吉特,人生绝对的赢家
- 家乡的那条小河
- 讲述,美丽聪明的海欧!
- 一个人的碎碎念
- 野营记-第五章|野营记-第五章 讨伐梦魇兽
- 夜游宫|夜游宫 心语
- 增长黑客的海盗法则