六讲贯通C++图的应用之四 最小生成树(1)
笔者从基本储存方法、DFS和BFS、无向图、最小生成树、最短路径以及活动网络(AOV、AOE)六个方面详细介绍C++图的应用。这篇我们该介绍最小生成树了。
最小生成树
说人是最难伺候的,真是一点不假。上面刚刚为了“提高可靠性”添加了几条多余的边,这会儿又来想办法怎么能以最小的代价把所有的顶点都连起来。可能正是人的这种精神才使得人类能够进步吧――看着现在3GHz的CPU真是眼红啊,我还在受500MHz的煎熬,然后再想想8086……
正如图的基本元素是顶点和边,从这两个方向出发,就能得到两个算法――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
- 上一篇:六讲贯通C++图的应用之三 无向图(1)
- 下一篇:C++必须明白的基础20条