資料結構學習(C++)——圖【3】(無向圖)(下) (轉)
最小生成樹
說人是最難伺候的,真是一點不假。上面剛剛為了“提高可靠性”新增了幾條多餘的邊,這會兒又來想辦法怎麼能以最小的代價把所有的頂點都連起來。可能正是人的這種精神才使得人類能夠進步吧——看著現在3GHz的真是眼紅啊,我還在受500MHz的煎熬,然後再想想8086……
正如圖的基本元素是頂點和邊,從這兩個方向出發,就能得到兩個演算法——Kruskal演算法(從邊出發)、Prim演算法(從頂點出發)。據說還有別的方法,恕我參考資料有限,不能詳查。
最小生成樹的儲存
顯然用常用的樹的儲存方法來儲存沒有必要,雖然名曰“樹”,實際上,這裡誰是誰的“祖先”、“子孫”並不重要。因此,用如下的MSTedge結構陣列來儲存就可以了。
template
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
{
MinHeap
MFSets V(vNum); list
for (i = 0; i < vNum; i++)
for (iter = vertices[i].e->begin(); iter != vertices[i].e->end(); iter++)
E.insert(MSTedge
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 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) s(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 }; #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 1, 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演算法的。 template { 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 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 bool Graph { MSTedge 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++)——不知是這組資料好是怎麼的,殷版居然原封不動的照抄了《資料結構演算法與應用-C++語言描述》(中文譯名) #include using namespace std; #include "Graph.h" int main() { Graph 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; }Prim演算法
測試
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-984553/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 資料結構學習(C++)——圖【3】(無向圖)(上) (轉)資料結構C++
- 資料結構學習(C++)——圖(總結) (轉)資料結構C++
- 資料結構學習(C++)——圖【4】(最短路徑) (轉)資料結構C++
- 資料結構學習(C++)——圖【2】(DFS和BFS) (轉)資料結構C++
- 資料結構學習(C++)——雙向連結串列 (轉)資料結構C++
- 資料結構學習(C++)——圖【1】(基本儲存方法) (轉)資料結構C++
- 資料結構學習(C++)——圖【5】活動網路(AOV、AOE) (轉)資料結構C++
- 資料結構學習總結--圖資料結構
- 資料結構學習(C++)——遞迴【2】(3) (轉)資料結構C++遞迴
- 資料結構學習(C++)——遞迴【3】(1) (轉)資料結構C++遞迴
- 資料結構學習(C++)——遞迴【3】(2) (轉)資料結構C++遞迴
- 資料結構學習(C++)續——排序【3】交換排序 (轉)資料結構C++排序
- 資料結構學習(C++)——二叉樹【3】 (轉)資料結構C++二叉樹
- 資料結構學習(C++)——序言 (轉)資料結構C++
- 資料結構學習(C++)——樹(總結) (轉)資料結構C++
- 【Python資料結構】無向圖的實現及用數學思想相互轉換Python資料結構
- 資料結構學習(C++)——遞迴【1】 (轉)資料結構C++遞迴
- 資料結構學習(C++)——迴圈連結串列 (轉)資料結構C++
- 資料結構與演算法——有向無環圖的拓撲排序C++實現資料結構演算法排序C++
- 資料結構學習(c++)——二叉樹 (轉)資料結構C++二叉樹
- 資料結構學習(C++)——遞迴【2】(1) (轉)資料結構C++遞迴
- 資料結構學習(C++)——遞迴【2】(2) (轉)資料結構C++遞迴
- 資料結構學習(C++)——遞迴【2】(4) (轉)資料結構C++遞迴
- 重學資料結構(七、圖)資料結構
- 【資料結構】第六章學習小結--- 圖資料結構
- 資料結構學習(C++)——二叉樹【2】 (轉)資料結構C++二叉樹
- 資料結構學習(C++)——二叉樹【1】 (轉)資料結構C++二叉樹
- 學習javascript資料結構與演算法(六)——圖JavaScript資料結構演算法
- 演算法與資料結構學習路線圖演算法資料結構
- 資料結構 - 圖資料結構
- 透過例子學習Lua(3)----Lua資料結構(轉)資料結構
- 【資料結構——圖和圖的儲存結構】資料結構
- 資料結構學習(C++)——棧應用(表示式求值) (轉)資料結構C++
- 資料結構學習(C++)續——排序【2】插入排序 (轉)資料結構C++排序
- 資料結構學習(C++)續——排序【1】測試程式 (轉)資料結構C++排序
- 資料結構學習(C++)續——排序【6】內部排序總結 (轉)資料結構C++排序
- 資料結構學習(C++)——線性鏈式結構總結(代後記)【1】 (轉)資料結構C++
- 資料結構學習(C++)——線性鏈式結構總結(代後記)【2】 (轉)資料結構C++