目录
题目
4 Kruskal算法的设计
作者: 冯向阳时间限制: 1S章节: 课程设计
问题描述 :
在使用vector(数组)、图的邻接表ADT以及快速排序的基础上,设计Kruskal算法,用以生成无向网的最小生成树,并以文本形式输出生成树中各条边以及它们的权值。将此算法加入到邻接表ADT中,在邻接表ADT中提供一个公有的成员函数Kyuskal。
提示:
(1)Kruskal算法的基本思想是为使生成树上边的权值之和达到最小,则应使生成树中每一条边的权值尽可能地小。初始时,先构造一个只含n个顶点的子图 T,然后从权值最小的边开始,若它的添加不使T中产生回路,则在T上加上这条边,如此重复,直至加上n-1条边为止。具体来讲,Kruskal算法由以下几个步骤组成:
1)设母图G=(V,E)为一个具有n个顶点的带权的连通网络,其最小生成树的初始状态为有n个顶点但无边的非连通图 T=(V, Φ)。
2)将E中的边按权值的递增顺序排序。
3)选择权值最小的边,若不构成环,则将其加入T中,否则,将其弃舍。
4)循环至有N-1条边。
(1)在排序的过程中,由于根据边的权值大小进行排序,需要同步保持边的权值和边之间的对应关系。
(2)定义vector<pair<TypeOfEdge, int>>,pair->first对应边的权值,pair->second对应边,使用sort进行排序。
(3)实现Kruskal算法的关键是如何判断所选取的边是否与生成树中已保留的边形成回路,这可通过判断边的两个顶点所在的连通分量的方法来解决。为此设置一个辅助数组vest(数组元素下标为0~n-1),它用于判断两个顶点集合(即两个连通分量),此时按其中的一个集合编号重新统一编号(即合并成一个连通分量)。因此,当两个顶点的集合(连通分量)编号不同时,则加入这两个顶点所构成的边到最小生成树中就一定不会形成回路,因为这两个顶点分属于不同的连通分量。
(4)由于我们需要的是最小生成树,所以母图是不用实际生成的,仅仅需要它的原始数据。
注意:处理的图的类型均为无向网(UDN),权值类型为int或double
参考函数原型:
//Kruskal算法
template<class TypeOfVer, class TypeOfEdge>
bool adjlist_graph<TypeOfVer, TypeOfEdge>::Kruskal( int esize, int **edge, TypeOfEdge *weight, TypeOfEdge &cost);//esize:边的数量 **edge:输入的边集二维数组 *weight:初始的权值数组 cost 树的代价
输入说明 :
第一行:权值数据类型(0:int;1:double)//暂不考虑容错处理
第二行:图的类型
第三行:顶点数
第四行:顶点集
第五行:边数
第六行:边集
第七行:权集
输出说明 :
第一行:图的类型
第二行:顶点集
空行
第三行:排序前的边的权值结果(按照输入顺序)
第四行:排序前对应的边的序号关系(按照输入顺序)
空行
第三行:排序后的边的权值结果(按照权值递增排序顺序)
第四行:排序后的对应的边的序号关系(对应权值递增排序顺序)
空行
第五行:最小生成树的各条边以及它们的权值(格式如测试数据所示)
空行
第六行:最小生成树的代价
输入范例
0
UDN
6
1 2 3 4 5 6
10
0 1
0 2
0 3
1 2
1 4
2 3
2 4
2 5
3 5
4 5
6 1 5 5 3 5 6 4 2 6输出范例
UDN
1 2 3 4 5 6
6 1 5 5 3 5 6 4 2 6
0 1 2 3 4 5 6 7 8 91 2 3 4 5 5 5 6 6 6
1 8 4 7 2 3 5 0 6 9(1,3),1
(4,6),2
(2,5),3
(3,6),4
(2,3),515
题目分析
解答
代码
初始代码:运用struct做的,借鉴了网友的做法,然而并不太符合题目中定义vector<pair<TypeOfEdge, int>>的要求,所以后来又编写了一遍
#include "bits/stdc++.h"
using namespace std;
#define Status int
#define MaxInt 32767
#define MVnum 100
char vertex[MVnum];
int vet[MVnum];
struct ArcNodedou{
int ord,start,end;
double weight;
};
bool cmp1(const ArcNodedou a,const ArcNodedou b){
if(a.weight!=b.weight)
return a.weight<b.weight;
else
return a.ord<b.ord;
}
void Kruskal(int flag){
string str;
cin>>str;
cout<<str<<endl;
int verNum,ArcNum;
cin>>verNum;
for(int i=0;i<verNum;i++){
cin>>vertex[i];
vet[i]=i;
}
cin>>ArcNum;
double cost=0;
vector<ArcNodedou> arc;
for (int i = 0; i < ArcNum; i++) {
ArcNodedou temp;
cin >> temp.start >>temp.end ;
temp.ord = i;
arc.push_back(temp);
}
for (int i = 0; i < ArcNum; i++) {
cin >>arc.at(i).weight ;
cout << arc.at(i).weight<< " ";
}
cout << endl;
for (int i = 0; i < ArcNum; i++) {
cout << i << " ";
}
cout << endl << endl;
sort(arc.begin(), arc.end(), cmp1);
for(auto item:arc){
cout<<item.weight<<" ";
}
cout<<endl;
for(auto item:arc){
cout<<item.ord<<" ";
}
cout<<endl<<endl;
int sta,end,VoltageSta,VoltageEnd;
for(auto item:arc){
sta=item.start;
end=item.end;
VoltageSta=vet[sta];
VoltageEnd=vet[end];
if(VoltageSta!=VoltageEnd){
printf("(%c,%c),",vertex[item.start],vertex[item.end]);
cout<<item.weight<<endl;
cost+=item.weight;
for(int i=0;i<verNum;i++){
if(vet[i]==VoltageEnd){
vet[i]=VoltageSta;
}
}
}
}
cout<<endl;
cout<<cost;
};
int main(){
int flag;
cin>>flag;
Kruskal(flag);
return 0;
}
我的新代码
#include "bits/stdc++.h"
using namespace std;
int vertex[100]={0},vernum,vet[100];
template<class TypeOfVer, class TypeOfEdge>
class adjlist_graph{
public:
bool Kruskal(int esize, vector<vector<TypeOfVer> >edge, vector<TypeOfEdge> weight,TypeOfEdge &cost);
};
template<class TypeOfVer, class TypeOfEdge>
bool adjlist_graph<TypeOfVer, TypeOfEdge>::Kruskal( int esize, vector<vector<TypeOfVer> >edge, vector<TypeOfEdge> weight, TypeOfEdge &cost){
vector<pair<TypeOfEdge,int> > Edge,Tree;//建立一个vector,元素为pair,其中first为边的权值,second为边的序号
Edge.clear(),Tree.clear();
for(int i=0;i<esize;i++)
Edge.emplace_back(make_pair(weight[i],i));
sort(Edge.begin(),Edge.end());//让他按照权值从小到大排序,如果权职相同的他会自动按照序号大小排序
//如下两个for循环为输出权值排序后的基本信息
for(int i=0;i<esize;i++)
cout<<Edge.at(i).first<<" ";
cout<<endl;
for(int i=0;i<esize;i++)
cout<<Edge.at(i).second<<" ";
cout<<endl<<endl;
//紧接着是生成树的诞生
for(int i=0;i<esize;i++){
int sta,end,Vsta,Vend;
sta=edge[Edge[i].second][0];
end=edge[Edge[i].second][1];
Vsta=vet[sta];
Vend=vet[end];
//sta与end分别是权值第一小的边的开头顶点与结尾顶点
if(Vsta!=Vend){
printf("(%d,%d),",vertex[sta],vertex[end]);
cout<<Edge[i].first<<endl;
for(int j=0;j<vernum;j++)
if(vet[j]==Vend)
vet[j]=Vsta;
cost+=Edge[i].first;
}
}
cout<<endl;
cout<<cost;
}
int main(){
bool flag;
cin>>flag;
string str;
cin>>str;
cout<<str<<endl;
int esize;
vector<vector<int> > edge;
vector<double> weight;
cin>>vernum;
for(int i=0;i<vernum;i++)
{
cin>>vertex[i];
vet[i]=i;
}
cin>>esize;
for(int i=0;i<esize;i++)
{
vector<int> temp;int tmp;
cin>>tmp;temp.emplace_back(tmp);
cin>>tmp;temp.emplace_back(tmp);
edge.emplace_back(temp);
}
for(int i=0;i<esize;i++)
{
int tmp;
cin>>tmp;
weight.emplace_back(tmp);
}
//如上为输入图的基本信息
//如下一直到if语句都是输出一些图的基本信息(不包括权值排序后的的基本信息)
for(int i=0;i<vernum;i++)
cout<<vertex[i]<<" ";
cout<<endl<<endl;
for(int i=0;i<esize;i++)
cout<<weight[i]<<" ";
cout<<endl;
for(int i=0;i<esize;i++)
cout<<i<<" ";
cout<<endl<<endl;
if(flag)
{
double cost=0;
adjlist_graph<int ,double> adjlistGraph;
adjlistGraph.Kruskal(esize,edge,weight,cost);
}
else {
int cost=0;
vector<int> weight1;
weight1.assign(weight.begin(),weight.end());
adjlist_graph<int,int> adjlistGraph1;
adjlistGraph1.Kruskal(esize,edge,weight1,cost);
}
return 0;
}
代码详解与难点分析(可无)
关键部分:生成树的诞生
for(int i=0;i<esize;i++){
int sta,end,Vsta,Vend;
sta=edge[Edge[i].second][0];
end=edge[Edge[i].second][1];
Vsta=vet[sta];
Vend=vet[end];
//sta与end分别是权值第一小的边的开头顶点与结尾顶点
if(Vsta!=Vend){
printf("(%d,%d),",vertex[sta],vertex[end]);
cout<<Edge[i].first<<endl;
for(int j=0;j<vernum;j++)
if(vet[j]==Vend)
vet[j]=Vsta;
cost+=Edge[i].first;
}
}
cout<<endl;
cout<<cost;
诞生生成树的原理大概为电势平摊法
首先是用vet数组来记录从0到n的下标,其中数组内部元素的值与该元素的下标相等,其内部元素用来代表该点的电势,其中每一个点的电势都不一样,将边按照权值从小到大遍历,并用sta与end来获取该边的起点与终点,Vsta与Vend来获取该边起点与终点的电势,判断是否这两点电势相同,如果这两点电势不相同,说明加入此边不会产生闭环,可以加入,与此同时,将这两点及其原来被同化过的点的电势再次同化成另一个电势,长此以往,实现生成树的诞生。
389

被折叠的 条评论
为什么被折叠?



