分享
 
 
 

数据结构学习(C++)——图【3】(无向图)(下)

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

最小生成树

说人是最难伺候的,真是一点不假。上面刚刚为了“提高可靠性”添加了几条多余的边,这会儿又来想办法怎么能以最小的代价把所有的顶点都连起来。可能正是人的这种精神才使得人类能够进步吧——看着现在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<name, dist>::MinSpanTree(MSTedge<dist>* a)

{

MinHeap<MSTedge<dist> > E; int i, j, k, l = 0;

MFSets V(vNum); list<edge>::iterator iter;

for (i = 0; i < vNum; i++)

for (iter = vertices[i].e->begin(); iter != vertices[i].e->end(); iter++)

E.insert(MSTedge<dist>(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 <vector>

using namespace std;

#define minchild(i) (heap[i*2+1]<heap[i*2+2]?i*2+1:i*2+2)

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<T> 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<name, dist>::MinSpanTree(MSTedge<dist>* 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<dist>(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<name, dist, mem>::MinSpanTree()

{

MSTedge<dist>* a = new MSTedge<dist>[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 <iostream>

using namespace std;

#include "Graph.h"

int main()

{

Graph<char, int, AdjMatrix<char, int> > 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;

}

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有