对于任何一个连通无向图来说,其都存在一棵最小生成树,该树能够连接图中的所有顶点,并且具有最小的权重和。有两种算法能够求解该问题,一种是prim算法,另外一种是kruskal算法。
prim算法
在prim算法的任一时刻,我们都可以看到一个已经被发现的顶点集,这些顶点集能够形成一棵树。此时,算法在每一个阶段找出一个新的顶点v,使之到已被发现的顶点集具有最少权重,并且把该新发现的顶点v添加到已经发现的顶点集中。当算法终止时,最小生成树形成。代码如下:
pair<vector<int>,vector<double>> prim_mst(const vector<list<int>>& graph,const vector<vector<double>>& edge_weights)
{
const int source=0; //认为最小生成树的源顶点是标号为0的顶点;
vector<bool> mstv(graph.size(),false); //标记结点是否被访问了
const int NIL=-1; //NIL表示不存在的顶点
vector<int> pred(graph.size(),NIL); //记录在最小生成树中每个结点的前向结点。
vector<double> weight(graph.size(),DBL_MAX);//weight[v]记录的是未发现顶点v到已发现的所有顶点中最短的距离,
//假设未发现顶点v到已发现顶点u的距离最少,则pred[v]=u,weight[v]为边(u,v)的权重;
mstv[source]=true;
for(auto iter=graph[source].begin();iter!=graph[source].end();++iter)
{
int tmp=*iter;
pred[tmp]=source;
weight[tmp]=edge_weights[source][tmp];
}
for(int i=0;i!=graph.size()-1;++i)
{
double min_weight=DBL_MAX;
int v=-1;
//找出连接树中结点所有边中具有最少权重边的顶点v.
for(int j=0;j!=graph.size();++j)
if(!mstv[j]&&weight[j]<min_weight){
min_weight=weight[j];
v=j;
}
if(v==-1)
throw runtime_error("the undirected graph is not connected");
mstv[v]=true;
for(auto iter=graph[v].begin();iter!=graph[v].end();++iter)
{
int tmp=*iter;
if(!mstv[tmp]&&edge_weights[v][tmp]<weight[tmp]){
weight[tmp]=edge_weights[v][tmp];
pred[tmp]=v;
}
}
}
return make_pair(pred,weight);
}
void prim_print_tree(const vector<list<int>>& graph, const vector<vector<double>>& edge_weights)
{
auto mst=prim_mst(graph,edge_weights);
const vector<int>& pred=mst.first;
const vector<double>& weight=mst.second;
double tree_weight=0;
cout<<"tree edge\tcorresponding weight"<<endl;
const int NIL=-1; //NIL表示不存在的顶点
for(int v=0;v!=graph.size();++v)
if(pred[v]!=NIL){
tree_weight+=weight[v];
cout<<"( "<<pred[v]<<" to "<<v<<" )\t\t"<<weight[v]<<endl;
}
cout<<"minimum spanning tree weight: "<<tree_weight<<endl;
}
kruskal算法:
kruskal算法是连续地按照最小的权重选择边,并且当所选的边不产生回路时就把它作为取定的边。形式上,kruskal算法是在处理树的集合,也就是森林。开始的时候,存在|V|棵单结点树,而添加一边则将两棵树合并成一棵树,当算法终止时,就只剩下一棵树了,这棵树就是最小生成树。这与prim算法不同,prim算法是本来就有一棵树,这棵树慢慢长大最后变成了最小生成树,而kruskal算法是有不同树的集合,通过不断合并,最终变成一棵树。代码如下:
struct edge{
edge(int s=0,int e=0,double w=0):start(s),end(e),weight(w){};
bool operator<(const edge& e) const { return weight<e.weight;}
int start,end;
double weight;
};
vector<edge> kruskal_mst(const vector<list<int>>& graph,vector<edge>& graph_edge)
{
DisjSets dj(graph.size());
//将图中所有边按照权重从小到大排序;
sort(graph_edge.begin(),graph_edge.end());
vector<edge> mst_edge; //存储最小生成树中的边
for(const auto& e:graph_edge)
{
int u=e.start;
int v=e.end;
int uset=dj.findSet(u);
int vset=dj.findSet(v);
if(uset!=vset){
mst_edge.push_back(e);
dj.unionSets(uset,vset);
}
}
return mst_edge;
}
void kruskal_print_mst(const vector<list<int>>& graph,vector<edge>& graph_edge)
{
auto mst_edge=kruskal_mst(graph,graph_edge);
cout<<"edges\tcorresponding weight"<<endl;
double tree_weight=0;
for(const auto& e:mst_edge)
{
tree_weight+=e.weight;
cout<<"( "<<e.start<<" to "<<e.end<<" )\t\t"<<e.weight<<endl;
}
cout<<"minimum spanning tree weight: "<<tree_weight<<endl;
}