按照不同的遍历算法,将得到不同的生成树;从不同的顶点出发,得到的生成树夜有所不同。对于一个带权图而言,不同的生成树对应的总权值夜不尽相同。所以,如何找到权值最小的生成树呢? 典型的方法有两种:Kruskal算法和Prim算法
任意给定一个有n个节点的连通网络 N={ V , E },首先构造一个由这n个顶点组成的、不含任何边的图 T = { V, ∅ } ,其中每个顶点自成一个连通分量。不断从E中取出权值最细小的一条边(若有多条,任取其一),若改变的两个端点来自不同的连通分量,则将刺鳊加入到T中。如此重复,知道所有顶点在同一连通分量上为止。 利用最小堆和并查集来实现Kruskal算法。首先利用最小堆来存放E中所有的边。堆中每个节点的格式为: tail(边的顶点位置);head(边的顶点位置);cost(边的权值) 在构造最小生成树的过程中,尚未处理的边存放在最小堆中。通过并查集的Find运算,可以很快地判断热议一条边的两个端点是否来自同一个连通分量。若是,则舍弃这条边;否则,将此边加入到T中,并通过并查集的Union运算,将两个端点各自所处的两个连通分量合二为一。随着T中边的不断增多,连通分量的逐步合并明知道最后主题构成一个连通分量为止。
先给出最小生成树的类定义
const float maxValue=FLOAT_MAX; //机器可表示的、问题中不可能出现的大数 template<class T,class E> struct MSTEdgeNode //最小生成树边节点的类声明 { int tail,head;E key; //端点的位置和边的权值 MSTEdgeNode() :tail(-1),head(-1),key(0){}//构造函数 //操作符,自行补充 bool operator <= (MSTEdgeNode<T,E>&R){return key<=R.key; }; template<class T,class E> class MinSpanTree //最小生成树的类定义 { protected: MSTEdgeNode<T,E> *edgevalue; //用边值数组表示树 int maxSize, n //数组的最大元素个数和当前元素个数 public: MibnSpanTree(int sz=DefaultSize-1):MaxSize(Sz),n(0) {edgevalue=new MSTEdgeNode<T,E>[sz];} int Insert(MSTEdgeNode& item); };在求解最小生成树的时候,可以使用邻接矩阵存储图,也可以用邻接表存储。算法中使用图的抽象基类操作,无需考录图及其操作的具体实现。
#include"heap.h" #include"UFSets.h" template<class T,class E> void Kruskal(Graph<T,E>& G,MinSpanTree<T,E> & MST) { MSTEdgeNode<T,E> ed;int u,v,count; //边节点辅助单元 int n=G.NumberOfVertices(); //顶点数 int m=G.NumberOfEdges(); //边数 MinHeap<MSTEdgeNode<T,E>> H(m); //最小堆,关键码类型为E UFSets F(n); //并查集 for(u=0;u<n;u++) { for(v=u+1;v<n;v++) { ed.tail=u;ed.head=v; //插入堆 ed.key=G.getWeight(u,v); H.Insert(ed); } } count=1; //最小生成树加入边数计数 while(count<n) //反复执行,去n-1条边 { //从最小堆中退出具有最小权值的边ed H.RemoveMib(ed); u=F.Find(ed.tail); //取两顶点所在集合的根u和v v=F.Find(ed.head); if(u!=v) //不是同一个集合,说明不连通 { F.Union(u,v); //合并,连通 MST.Insert(ed); //该边存入最小生成树 count++; } } }如果基于邻接矩阵实现图的存储,则建立在最小堆是需要检测图的邻接矩阵,这需要O(n2)的时间,此外,需要将e条边组成初始的最小堆。如果从空堆开始,依次插入各边,需要O(e l o g 2 e log_2{e} log2e)时间。在构造最小生成树的过程中,需要进行O(e)次出堆的操作RemoveMin( )、2次并查集的Find( )操作以及n-1次Union( )操作。所以计算的总时间为O(e l o g 2 e log_2{e} log2e+e l o g 2 n log_2{n} log2n+n2+n)。对连通图而言就是O(n2+e l o g 2 e log_2{e} log2e) 如果采用邻接表实现图的存储,则在建立最小堆的时候需要检测图的邻接表,需要O(n+e)的时间。为建成初始的最小堆。需要O(e l o g 2 e log_2{e} log2e)时间。在构造对象生成树的过程中,需要进行O(e)次出堆操作RemoveMin( )、2e次并查集的Find( )操作和n-1次Union( )操作,计算总时间为O(e l o g 2 e log_2{e} log2e+ l o g 2 e log_2{e} log2e+n2+n)。对连通图而言就是O(n+e l o g 2 e log_2{e} log2e)。
与Kruskal算法类似,需要一个最小堆存储图的边,每次选出的一个端点在生成树中,另外一个端点不在生成树的权值最小的边,它正好在最小堆的顶端,将其从堆中退出,加入到生成树中。然后将新出现的所有端点在生成树中,一个端点不在生成树的边都插入最小堆中。下一次更迭中,下一条满足要求的权值最小的又上升到最小堆的顶端。如此重复n-1次。最有建立起最小生成树。
此算法的迭代次数为O(n),每次迭代将平均2e/n条边插入最小堆中,e条边从典狱长删除,堆得插入和删除时间复杂性均为O( l o g 2 e log_2{e} log2e),则总的计算时间为O(e l o g 2 e log_2{e} log2e)。
[1]殷人昆.《数据结构(用面向对象方法与c++语言描述)》.北京:清华大学,2007.6:371~375页.
