目录
题目:
现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。
输入格式
输入数据包括城镇数目正整数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
问题分析
本题的大意是将所有村落连在一起花费最少钱,属于典型的最小生成树问题
最小生成树的特点
- 无回路
- 边数=结点数-1
- 所有结点在一个连通集
- 权重和最小
算法
Prim算法
- 通过正向构造collected集合满足无回路和权重和最小的条件
- 检查条件一(连通集结束时是否所有结点已被包含)和条件二(边数是否满足结点数小一)结束算法
说明:类似Dijkstra算法,按照递增的方式构造集合collected,但是prim算法中dist的值不再是距源点的距离,而是距集合collected的距离(本题dist是指修一条通向集合collected的路,最少花费多少钱)
代码实现
int main()
{
int N,M;
scanf("%d %d",&N,&M);
Graph G=CreateGraph(N);
BuildGraph(G,M);
if(M<N-1){
printf("-1");
}else{
Prim(G);
}
return 0;
}
Prim函数:小树变大的过程中记录fee(费用和)和count(已收集的边数)
void Prim(Graph G)
{
int fee=0;//从结点1开始让树长大,fee记录费用
int count=0;//统计collected集合中已经收录结点个数
int dist[VERTEXMAXNUM];//未收录的任意一点到collected集合的最短路径长度
bool collected[VERTEXMAXNUM];
int i;
{
for(i=1;i<=G->VertexNum ;i++){
dist[i]=Infinite;
collected[i]=false;
}
for(i=1;i<=G->VertexNum ;i++){
if(G->Matrix[1][i] <Infinite){
dist[i]=G->Matrix[1][i];
}
}
}
collected[1]=true;
dist[1]=0;
int minDistVertex=-1;
while(1){
minDistVertex=FindMinDistVertex(dist,collected,G->VertexNum );
if(minDistVertex==-1||count==G->VertexNum-1)break;
collected[minDistVertex]=true;
fee+=dist[minDistVertex];
dist[minDistVertex]=0;
count++;
for(i=1;i<=G->VertexNum;i++){
if(collected[i]==true)continue;
if(G->Matrix[minDistVertex][i]<dist[i]){
dist[i]=G->Matrix[minDistVertex][i];
}
}
}
if(count<G->VertexNum-1){
printf("-1");
}else{
printf("%d",fee);
}
}
FindMinDistVertex函数:寻找距collected集合最近的结点
int FindMinDistVertex(int dist[],bool collected[],int N)
{
int i;
int MinDist=Infinite;
int vertex=-1;
for(i=1;i<=N;i++){
if(MinDist>dist[i]&&(collected[i]==false)){
MinDist=dist[i];
vertex=i;
}
}
return vertex;
}
其他函数:邻接矩阵基础操作
#define Infinite 65535
#define VERTEXMAXNUM 1001
typedef struct GNode* Graph;
struct GNode{
int VertexNum;
int EdgeNum;
int Matrix[VERTEXMAXNUM][VERTEXMAXNUM];
};
typedef struct ENode* Edge;
struct ENode{
int V;
int W;
int Weight;
};
Graph CreateGraph(int N)
{
Graph G=(Graph)malloc(sizeof(struct GNode));
G->VertexNum=N;
int i=0,j=0;
for(i=1;i<=N;i++){
for(j=1;j<=N;j++){
G->Matrix[i][j]=Infinite;
}
}
return G;
}
void InsertEdge(Edge L,Graph G)
{
G->Matrix [L->V ][L->W ]=L->Weight ;
G->Matrix [L->W ][L->V ]=L->Weight;
}
void BuildGraph(Graph G,int M)
{
int i=0;
G->EdgeNum=M;
Edge L=(Edge)malloc(sizeof(struct ENode));
for(i=0;i<M;i++){
scanf("%d %d %d",&(L->V),&(L->W ),&(L->Weight ));
InsertEdge(L,G);
}
free(L);
}
Kruskal算法
- 根据边创建最小堆H
- 在while计数循环中弹出最小边Node,检查边Node的两个结点是否能构成环(即是否两个结点已经在一个集合里,并查集处理),如果不能构成环则记录此边,否则抛弃。
- 当while循环中边计数器count==总结点数-1时,停止(或者最小堆中不能在弹出元素)
代码实现
int main()
{
int N,M;
scanf("%d %d",&N,&M);
Heap H=CreateHeap(M);
BuildMinheap(H);
if(M<N-1){
printf("-1");
}else{
Kruskal(H,N);
}
return 0;
}
Kruskal函数
说明:在while计数循环中弹出最小边Node,检查边Node的两个结点是否能构成环(即是否两个结点已经在一个集合里,并查集处理),如果不能构成环则记录此边,否则抛弃
void Kruskal(MinHeap H,int N)
{
int fee=0;//fee记录费用
int count=0;//统计已经收录边数
int *SetType=(int*)malloc(sizeof(int)*(N+1));//并查集用的集合
int i;
for(i=1;i<=N;i++){
SetType[i]=-1;
}
ElementType Node;//每次从最小堆中弹出的元素
int V,W;
while(count<N-1){
Node=DeleteMinHeap(H);
if(Node.Weight==-1)break;//最小堆中已无元素
V=Node.V;
W=Node.W;
if(!IsSameSet(V,W,SetType)){//检查边的两个结点是否在一个环中
fee+=Node.Weight;
Union(V,W,SetType);
count++;
}
}
if(count<N-1){
printf("-1");
}else{
printf("%d",fee);
}
}
并查集函数
- int Find(int V,int SetType[]):寻找结点V的根节点(采用了路径压缩)
- bool IsSameSet(int V,int W,int SetType[]):判断V,W结点是否在一个集合
- void Union(int V,int W,int SetType[]):将V,W两个结点放在一个集合
int Find(int V,int SetType[])
{
if(SetType[V]<0)return V;
return SetType[V]=Find(SetType[V],SetType);
}
bool IsSameSet(int V,int W,int SetType[])
{
int root1=Find(V,SetType);
int root2=Find(W,SetType);
if(root1==root2){
return true;
}else{
return false;
}
}
void Union(int V,int W,int SetType[])
{
int root1=Find(V,SetType);
int root2=Find(W,SetType);
if(SetType[root2]<SetType[root1]){
SetType[root2]+=SetType[root1];
SetType[root1]=root2;
}else{
SetType[root1]+=SetType[root2];
SetType[root2]=root1;
}
}
最小堆函数
- Heap CreateHeap(int M):建堆
- void BuildMinheap(Heap H);把堆整理为最小堆
- void PercDown(Heap H,int V):从数组下标为V的位置向下过滤(此时V的左子树和右子树都已经是最小堆)
- ElementType DeleteMinHeap(MinHeap H):弹出堆顶元素
#define Infinite 65535
#define VERTEXMAXNUM 3001
typedef struct ENode ElementType;
struct ENode{
int V;
int W;
int Weight;
};
typedef struct HNode* MinHeap;
typedef struct HNode* Heap;
struct HNode{
int Size;
ElementType HeapMatrix[VERTEXMAXNUM];
};
Heap CreateHeap(int M)
{
Heap H=(Heap)malloc(sizeof(struct HNode));
H->Size=M;
H->HeapMatrix[0].Weight=-1;//哨兵
int i=1;
for(i=1;i<=M;i++){
scanf("%d %d %d",&(H->HeapMatrix[i].V),&(H->HeapMatrix[i].W),&(H->HeapMatrix[i].Weight));
}
return H;
}
void PercDown(Heap H,int V)
{
int T=H->HeapMatrix[V].Weight;
ElementType TNode=H->HeapMatrix[V];
int parent,child;
for(parent=V;parent*2<=H->Size;parent=child){
child=2*parent;
if(child!=H->Size&&((H->HeapMatrix[child].Weight)>(H->HeapMatrix[child+1].Weight))){
child++;
}
if(H->HeapMatrix[child].Weight<T){
H->HeapMatrix[parent]=H->HeapMatrix[child];
}else{
break;
}
}
H->HeapMatrix[parent]=TNode;
}
void BuildMinheap(Heap H)
{
int i=H->Size/2;
for(;i>=1;i--){
PercDown(H,i);
}
}
ElementType DeleteMinHeap(MinHeap H)
{
ElementType Node={.Weight=-1};
if(H->Size==0){
return Node;
}
Node=H->HeapMatrix[1];
H->HeapMatrix[1]=H->HeapMatrix[H->Size--];
PercDown(H,1);
return Node;
}