最小生成树
程序目录结构
DenseGraph.h是邻接矩阵的类
Edge.h是边的类,成员有两个端点与边的权重
Kruskal是实现kruskal的类
lazyPrimMST是惰性prim算法的类
PrimMST是prim算法的类
MinHeap是最小堆的类,用于实现获取最小边
IndexMinHeap是最小索引堆的类,用于实现获取最小边,并且淘汰不需要的边
UnionFind5是并查集的类,用于判环
Prim算法
思想:每次选出与选中点的集合相连的最短的边,并将其所连的点加入选中点的集合
lazyprim
核心代码
#ifndef INC_03_LAZY_PRIM_LAZYPRIMMST_H
#define INC_03_LAZY_PRIM_LAZYPRIMMST_H
#include <iostream>
#include <vector>
#include <cassert>
#include "Edge.h"
#include "MinHeap.h"
using namespace std;
// 使用Prim算法求图的最小生成树
template<typename Graph, typename Weight>
class LazyPrimMST
{
private:
Graph &G;
MinHeap<Edge<Weight> > pq;
bool *marked;
vector<Edge<Weight> > mst;//存放最小生成树的边
Weight mstWeight;
void visit(int v)
{
assert(!marked[v]);
marked[v] = true;
typename Graph::adjIterator adj(G,v);
//遍历与v相连的所有边
for(Edge<Weight>* e=adj.begin(); !adj.end(); e=adj.next())
{
//若这条边的另一端未访问则放入最小堆
if(!marked[e->other(v)])
pq.insert(*e);
}
}
public:
LazyPrimMST(Graph &graph):G(graph),pq(MinHeap<Edge<Weight> >(graph.E()))
{
marked = new bool[G.V()];
for(int i = 0; i < G.V(); i++)
marked[i]=false;
mst.clear();
visit(0);
//LazyPrim的时间复杂度为O(ElogE)级别
while( !pq.isEmpty())
{
Edge<Weight> e = pq.extractMin();
if(marked[e.v()] == marked[e.w()])
{
continue;
}
mst.push_back(e);
if(!marked[e.v()])
{
visit(e.v());
}
else
{
visit(e.w());
}
}
mstWeight = mst[0].wt();
for(int i=1; i<mst.size(); i++)
mstWeight+=mst[i].wt();
}
~LazyPrimMST()
{
delete[] marked;
}
vector<Edge<Weight> > mstEdges()
{
return mst;
}
Weight result()
{
return mstWeight;
}
};
#endif
lazyprim的问题
当一个未选中的点与已选中的点的集合有多条边相连时,会将所有的边都加入最小堆中,虽然当每次取出时取出最小的边,并跳过两端都被选中的边,这样可以保证结果正确,但与此同时也使每次的堆排操作产生不必要的耗时,复杂度为O(ELOGE).
E为边数
优化Prim算法
思路:利用最小索引堆,在插入最小堆时只是存储未选点与已选点集合之间的最短边,将复杂度降为O(ELOGV),V为点数
核心代码
#ifndef INC_05_IMPLEMENTATION_OF_OPTIMIZED_PRIM_ALGORITHM_PRIMMST_H
#define INC_05_IMPLEMENTATION_OF_OPTIMIZED_PRIM_ALGORITHM_PRIMMST_H
#include <iostream>
#include <vector>
#include <cassert>
#include "Edge.h"
#include "IndexMinHeap.h"
using namespace std;
// 使用优化的Prim算法求图的最小生成树
template<typename Graph, typename Weight>
class PrimMST
{
private:
Graph &G; //图的引用
IndexMinHeap<Weight> ipq; //最小索引堆,算法辅助数据结构
vector<Edge<Weight>* > edgeTo; //访问的点所对应的边
bool* marked; //标记数组
vector<Edge<Weight> > mst; //最小生成树所包含的所有边
Weight mstWeight; //最小生成树的权值
//访问节点v
void visit(int v)
{
assert(!marked[v]);
marked[v] = true;
typename Graph::adjIterator adj(G,v);
for(Edge<Weight>* e = adj.begin(); !adj.end(); e=adj.next())
{
int w = e->other(v);
if(!marked[w])
{
if(!edgeTo[w])
{
ipq.insert(w,e->wt());
edgeTo[w] = e;
}
else if(e->wt() < edgeTo[w]->wt())
{
edgeTo[w] = e;
ipq.change(w,e->wt());
}
}
}
}
public:
PrimMST(Graph &graph):G(graph),ipq(IndexMinHeap<double>(graph.V()))
{
marked = new bool[G.V()];
for(int i=0; i<G.V(); i++)
{
marked[i] = false;
edgeTo.push_back(NULL);
}
mst.clear();
//prim
visit(0);
while(!ipq.isEmpty())
{
int v = ipq.extractMinIndex();
assert(edgeTo[v]);
mst.push_back(*edgeTo[v]);
visit(v);
}
mstWeight = mst[0].wt();
for(int i = 1; i<mst.size(); i++)
mstWeight += mst[i].wt();
}
~PrimMST()
{
delete[] marked;
}
vector<Edge<Weight> > mstEdges()
{
return mst;
}
Weight result()
{
return mstWeight;
}
};
#endif // INC_05_IMPLEMENTATION_OF_OPTIMIZED_PRIM_ALGORITHM_PRIMMST_H
KRUSKAL算法
思路:每次找出图中最短且不成环的边
核心代码
#ifndef INC_03_KRUSKAL_H
#define INC_03_KRUSKAL_H
#include <iostream>
#include <vector>
#include <cassert>
#include "Edge.h"
#include "MinHeap.h"
#include "UnionFind5.h"
using namespace std;
// Kruskal算法
template <typename Graph, typename Weight>
class KruskalMST{
private:
vector<Edge<Weight> > mst; // 最小生成树所包含的所有边
Weight mstWeight; // 最小生成树的权值
public:
// 构造函数, 使用Kruskal算法计算graph的最小生成树
KruskalMST(Graph &graph){
// 将图中的所有边存放到一个最小堆中
MinHeap<Edge<Weight> > pq( graph.E() );
for( int i = 0 ; i < graph.V() ; i ++ ){
typename Graph::adjIterator adj(graph,i);
for( Edge<Weight> *e = adj.begin() ; !adj.end() ; e = adj.next() )
if( e->v() < e->w() )
pq.insert(*e);
}
// 创建一个并查集, 来查看已经访问的节点的联通情况
UnionFind uf = UnionFind(graph.V());
while(!pq.isEmpty()&&mst.size()<graph.V()-1){
Edge<Weight> e = pq.extractMin();
//利用判断根节点是否相同来判断是否成环
if(uf.isConnected(e.v(),e.w()))
continue;
mst.push_back(e);
//连接两个点
uf.unionElements(e.v(),e.w());
}
mstWeight = mst[0].wt();
for( int i = 1 ; i < mst.size() ; i ++ )
mstWeight += mst[i].wt();
}
~KruskalMST(){ }
// 返回最小生成树的所有边
vector<Edge<Weight> > mstEdges(){
return mst;
};
// 返回最小生成树的权值
Weight result(){
return mstWeight;
};
};
#endif
测试
主程序
循环生成节点数为100到2000的全通图
#include <iostream>
#include <iomanip>
#include<fstream>
#include "DenseGraph.h"
#include "ReaderGraph.h"
#include "LazyPrimMST.h"
#include "PrimMST.h"
#include "Kruskal.h"
#include<stdio.h>
using namespace std;
// 测试有权图和有权图的读取
int main()
{
string filename = "testG1.txt";
double starttime,endtime;
double time1 = 0.0;
// ofstream out("data.txt",ios::app);
ofstream out("data1.txt",ios::app);
// ofstream out("data2.txt",ios::app);
for(int V = 100; V<=2000; V+=100)
{
DenseGraph<double> g1 = DenseGraph<double>(V, false,true);
//
// // Test Lazy Prim MST
cout<<"Test Lazy Prim MST——V:"<<V<<endl;
starttime=clock();
LazyPrimMST<DenseGraph<double>, double> lazyPrimMST1(g1);
endtime=clock();
time1=double(endtime - starttime)/CLOCKS_PER_SEC;
cout<<time1<<'s'<<endl;
// out<<V<<' '<<time1<<endl;
// cout<<V<<' '<<time1<<endl;
cout<<"The MST weight is: "<<lazyPrimMST1.result()<<endl;
cout<<"Test Prim MST——V:"<<V<<endl;
starttime=clock();
PrimMST<DenseGraph<double>, double> primMST(g1);
endtime=clock();
time1=double(endtime - starttime)/CLOCKS_PER_SEC;
// out<<V<<' '<<time1<<endl;
cout<<time1<<'s'<<endl;
cout<<"The MST weight is: "<<primMST.result()<<endl;
cout<<"Test Kruskal MST——V:"<<V<<endl;
starttime=clock();
KruskalMST<DenseGraph<double>, double> kruskalMST(g1);
endtime=clock();
time1=double(endtime - starttime)/CLOCKS_PER_SEC;
// out<<time1<<endl;
cout<<time1<<'s'<<endl;
cout<<"The MST weight is: "<<kruskalMST.result()<<endl;
}
// out.close();
cout<<endl;
return 0;
}
结果
总结
无论是prim算法还是kruskal算法都是将寻找最小生成树的方法差分为每次寻找最短边的方法,获得局部最优解,最后构成最小树。