數據結構學習(C++)——圖(無向圖下)
更新時間: 2007-05-25 11:27:12來源: 粵嵌教育瀏覽量:1000
小生成樹
說人是難伺候的,真是一點不假。上面剛剛為了“提高可靠性”添加了幾條多余的邊,這會兒又來想辦法怎么能以小的代價把所有的頂點都連起來。可能正是人的這種精神才使得人類能夠進步吧——看著現在3GHz的CPU真是眼紅啊,我還在受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 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 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 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
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++)——不知是這組數據好是怎么的,殷版居然原封不動的照抄了《數據結構算法與應用-C++語言描述》(中文譯名)
#include
using namespace std;
#include "Graph.h"
int main()
{
Graph > 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;
}