贯通C++图之最小生成树的应用

     贯通C++图之最小生成树的应用分享

       正如图的基本元素是顶点和边,从这两个方向出发,就能得到两个算法——Kruskal算法(从边出发)、Prim算法(从顶点出发)。据说还有别的方法,恕我参考转子泵资料有限,不能详查。

  最小生成树的储存

  显然用常用的树的储存方法来储存没有必要,虽然名曰“树”,实际上,这里谁是谁的“祖先”、“子孙”并不重要。因此,转子泵如下的MSTedge结构数组来储存就可以了。

  • template <class dist>   
  • class MSTedge   
  • {   
  • public:   
  • MSTedge() {}   
  • MSTedge(int v1, int v2, dist cost) : v1(v1), v2(v2), cost(cost) {}   
  • int v1, v2;   
  • dist cost;   
  • bool operator > (const MSTedge& v2) { return (cost > v2.cost); }   
  • bool operator < (const MSTedge& v2) { return (cost < v2.cost); }   
  • bool operator == (const MSTedge& v2) { return (cost == v2.cost); }   
  • };

  Kruskal算法

  最小生成树直白的讲就是,挑选N-1条不产生回路最短的边。Kruskal算法算是最直接的表达了这个思想——在剩余边中挑选一条最短的边,看是否产生回路,是放弃,不是选定然后重复这个步骤。说起来倒是很简单,做起来就不那么容易了——判断是否产生回路需要并查集,在剩余边中找一条最短的边需要最小堆(并不需要对所有边排序,所以堆是最佳选择)。

  Kruskal算法的复杂度是O(eloge),当e接近N^2时,可以看到这个算法不如O(N^2)的Prim算法,因此,他适合于稀疏图。而作为转子泵稀疏图,通常用邻接表来储存比较好。另外,对于邻接矩阵储存的图,Kruskal算法比Prim算法占不到什么便宜(初始还要扫描N^2条“边”)。因此,最好把Kruskal算法放在Link类里面。

  • template <class name, class dist> int Link::MinSpanTree(MSTedge* a)   
  • {   
  • MinHeap > E; int i, j, k, l = 0;   
  • MFSets V(vNum); list::iterator iter;   
  • for (i = 0; i < vNum; i++)   
  • for (iter = vertices[i].e->begin(); iter != vertices[i].e->end(); iter++)   
  • E.insert(MSTedge(i, iter->vID, iter->cost));//建立边的堆   
  • for (i = 0; i < eNum && l < vNum; i++)//Kruskal Start   
  • {   
  • j = V.find(E.top().v1); k = V.find(E.top().v2);   
  • if (j != k) { V.merge(j, k); a[l] = E.top(); l++; }   
  • E.pop();   
  • }   
  • return l;   
  • }

  下面是堆和并查集的实现

  • #ifndef Heap_H   
  • #define Heap_H   
  • #include    
  • using namespace std;   
  • #define minchild(i) (heap[i*2+1] 
  • template <class T>   
  • class MinHeap   
  • {   
  • public:   
  • void insert(const T& x) { heap.push_back(x); FilterUp(heap.size()-1); }   
  • const T& top() { return heap[0]; }   
  • void pop() { heap[0] = heap.back(); heap.pop_back(); FilterDown(0); }   
  • private:   
  • void FilterUp(int i)   
  • {   
  • for (int j = (i - 1) / 2; j >= 0 && heap[j] > heap[i]; i = j, j = (i - 1) / 2)   
  • swap(heap[i], heap[j]);   
  • }   
  • void FilterDown(int i)   
  • {   
  • for (int j = minchild(i); j < heap.size() && heap[j] < heap[i]; i = j, j = minchild(i))   
  • swap(heap[i], heap[j]);   
  • }   
  • vector heap;   
  • };   
  • #endif   
  • #ifndef MFSets_H   
  • #define MFSets_H   
  • class MFSets   
  • {   
  • public:   
  • MFSets(int maxsize) : size(maxsize)   
  • {   
  • parent = new int[size + 1];   
  • for (int i = 0; i <= size; i++) parent[i] = -1;   
  • }   
  • ~MFSets() { delete []parent; }   
  • void merge(int root1, int root2)//root1!=root2   
  • {   
  • parent[root2] = root1;   
  • }   
  • int find(int n)   
  • {   
  • if (parent[n] < 0) return n;   
  • return find(parent[n]);   
  • }   
  • private:   
  • int size;   
  • int* parent;   
  • };   
  • #endif

  

 

  Prim算法

  如果从顶点入手,就能得到另一种方法。从只含有一个顶点的集合开始,寻找集合外面的顶点到这个集合里的顶点最近的一条边,然后将这个顶点加入集合,修改因为这个顶点的加入而使得集合外面的顶点到集合里的顶点的最短距离产生变化的分量。因为需要对每个顶点扫描,邻接矩阵储存的图是最合适Prim算法的。

  • template <class name, class dist> int AdjMatrix::MinSpanTree(MSTedge* a)   
  • {   
  • dist* lowC = new dist[vNum]; int* nearV = new int[vNum];   
  • int i, j, k;   
  • for (i = 0; i < vNum; i++) { lowC[i] = edge[0][i]; nearV[i] = 0; } nearV[0] = -1;   
  • for (k = 0; k < vNum-1; k++)//Prim Start   
  • {   
  • for (i = 1, j = 0; i < vNum; i++)   
  • if (nearV[i] != -1 && lowC[i] < lowC[j]) j = i;//find low cost   
  • a[k] = MSTedge(nearV[j], j, lowC[j]); nearV[j] = -1; //insert MST   
  • if (a[k].cost == NoEdge) return k - 1;//no edge then return   
  • for (i = 1; i < vNum; i++)//modify low cost   
  • if (nearV[i] != -1 && edge[i][j] < lowC[i]) { lowC[i] = edge[i][j]; nearV[i] = j; }   
  • }   
  • return k;   
  • }

  【附注】这里需要说明一下,对于edge[I][I]这样的是应该是0呢还是NoEdge呢?显然0合理,但是不好用。并且,从不锈钢转子泵有权图无权图统一的角度来说,是NoEdge更好。因此,在我的有权图的邻接矩阵中,主对角线上的元素是NoEdge,而不是书上的0。

  测试程序

  储存和操作分离,没想到得到了一个有趣的结果——对于最后的无向图而言,最小生成树的算法对外表现不知道是采用了那个算法。

  • template <class name, class dist, class mem>   
  • bool Graph::MinSpanTree()   
  • {   
  • MSTedge* a = new MSTedge[vNum() - 1];   
  • int n = data.MinSpanTree(a); dist sum = dist();   
  • if (n < vNum() - 1) return false;//不够N-1条边,不是生成树   
  • for (int i = 0; i < n; i++)   
  • {   
  • cout << '(' << getV(a[i].v1) << ',' << getV(a[i].v2) << ')' << a[i].cost << ' ';   
  • sum += a[i].cost;   
  • }   
  • cout << endl << "MinCost: " << sum << endl;   
  • delete []a;   
  • return true;   
  • }

  最后的转子泵测试图的数据取自《数据结构算法与应用-C++语言描述》(中文译名)

  • #include    
  • using namespace std;   
  • #include "Graph.h"   
  • int main()   
  • {   
  • Graph<charint, AdjMatrix<charint> > a(100);//改为Link储存为Kruskal算法   
  • a.insertV('A'); a.insertV('B');   
  • a.insertV('C'); a.insertV('D');   
  • a.insertV('E'); a.insertV('F');   
  • a.insertV('G');   
  • a.insertE('A''B', 28); a.insertE('A''F', 10);   
  • a.insertE('B''C', 16); a.insertE('C''D', 12);   
  • a.insertE('D''E', 22); a.insertE('B''G', 14);   
  • a.insertE('E''F', 25); a.insertE('D''G', 18);   
  • a.insertE('E''G', 24);   
  • a.MinSpanTree();   
  • return 0;   
  • 静安鲜花/普陀保洁公司/长宁租车/普陀汽车租赁/金山婚庆礼仪/制砂机/真空机/海水泵/柱塞阀/锥形混合机/螺丝帽/陶瓷刀/注册公司/装修公司/空调维修/代开发票
posted @ 2012-02-23 13:11  lanhe  阅读(820)  评论(0编辑  收藏  举报
数据中心