算法导论 第23章 最小生成树 斐波那契堆实现优先队列

来源:互联网 发布:php商城开发视频教程 编辑:程序博客网 时间:2024/05/22 06:24

最小生成树(Minimum Spanning Tree)问题

    对于一个无向连通图G=(V,E),找出一个无回路的子集T包含于E,连接了所有的顶点,且其权值之和

    

为最小。由于T无回路且连接了所有的顶点,因而必然是一棵树,即生成树,又由于权值和最小,因而叫做最小生成树。确定图G的树T的问题成为最小生成树问题。如下图是图G,阴影边是最小生成树的边,可知权值和为37.

最小生成树一般算法


GENERIC - MST(G){A <- NULL;while A is not a MST{find an edge(u, v) has the minimum weight is safe for A;insert (u,v) to A;}return A;}

安全边(safe edge),顾名思义就是在当前剩下的边中具有最小权值,且加入MST后不会形成环的边。具体算法的实现就在于如何筛选安全边。根据筛选安全边的不同,一般有两种最为常用的算法,Kruskal和Prim算法,它们都是贪心算法。


Kruskal算法

    kruskal算法时贪心策略的运用。将所有边按照权值从小到大排序,然后顺序扫描这些边,只要是安全的即将边加入MST中。判断是否安全的准则是边两端点是否有同一个祖先,采用不相交数据集合可以很快查得。下面是其算法代码:

MST-KRUSKAL(G){A <- NULL;for each vertex v in V[G]MAKE-SET(V);sort all edges of E into nondecreasing order by weight;for every edge(u,v) in E,taken in nondecreasing order by weightif FIND-SET(u) != FIND-SET(v){insert (u, v) to A;UNION(u, v);}return A;}


时间复杂度O(ElgE),由于是连通图,故|V| - 1 <= E <= |V|^2,所以lgE = O(lgV),故时间也可表述为O(ElgV)。具体实现代码如下,后面将会给出用到的所有代码,相关约定请参看图的基本算法(一)。

size_t AGraph::kruskal(AGraph *mst){//克鲁斯卡尔算法求最小生成树,返回最小权值和,最小生成树记录在mst中struct findRoot:public binary_function<vector<size_t>,size_t,size_t>{//局部函数对象类,用于查询并查集size_t operator()(const vector<size_t> &UFS, size_t v)const{while (v != UFS[v]) v = UFS[v];return v;}};struct edgeCompare:public binary_function<edge,edge,bool>{//局部函数对象类,用于边比较器bool operator()(const edge &left, const edge &right)const{return left.weight < right.weight;}};vector<edge> E;transformGraph(E);//将邻接链表转换为边集合vector<size_t> UFS(nodenum + 1);size_t sum = 0;for (size_t i = 1; i <= nodenum; ++i)UFS[i] = i;//初始化并查集Union-Find-Setsort(E.begin(), E.end(), edgeCompare());//对edge边按权值非递减排序for (size_t i = 0; i != E.size(); ++i){//逐条考察边size_t u_root = findRoot()(UFS, E[i].u), v_root = findRoot()(UFS, E[i].v);if (u_root != v_root){//若属于不同的树,即根节点不一样,则加入sum += E[i].weight;mst->add2Edges(E[i].u, E[i].v, E[i].weight);UFS[u_root] = v_root;}}return sum;}

该程序完全照着前面的GENERIC-MST来实现,应该很好理解的。


Prim算法

    prim算法和kruskal算法一样也是贪心的。和后者不同的是,后者在某一时刻,A中可能存在着好几棵树,即A是一个森林,因为有时候选择的边和之前的树并不连接着;而prim算法则是从某一顶点开始,沿着能够到达的当前权值最小的边生长,直至包含所有顶点,任意时刻,A中仅存在一棵树。非常类似于我们以后将会介绍的Dijkstra单源最短路径算法。

    如何有效的实现prim算法,关键在于能够较容易的选择当前能够到达的最小权值边,然后将其添加到MST中。我们采用斐波那契堆来实现最小优先队列,每次取的这样的边。

    采用的斐波那契堆是这样的:fibonacci_heap<size_t,size_t>,键和值均为size_t类型,其中值是顶点的编号,键是该顶点当前距离MST的最小距离,初始时,除了源点键位0,其他顶点均为无穷大(用0x7ffffff表示)。同时,为了简便地修改顶点到MST的距离,即键值,我们将上述实现的斐波那契堆的insert操作做了少许修改——让其具有返回值,返回与图中顶点i关联的节点的地址——存储于一个数组V的第i槽位。当该节点从堆中抽取后,即顶点i将被纳入MST时,我们将会把该数组V[i]槽位置空,表示其不再属于堆。

下面是实现的具体C++源代码:

size_t AGraph::prim(AGraph *mst,size_t u){//普利姆算法求最小生成树,采用斐波那契堆。返回最小权值和;mst存储最小生成树,时间O(E+VlgV)vector<size_t> parent(nodenum + 1);//存储每个顶点在斐波那契堆中的对应节点的地址,这样便于修改距离等vector<fibonacci_heap_node<size_t,size_t>*> V(nodenum + 1);fibonacci_heap<size_t, size_t> Q;//斐波那契堆,键为距离,值为顶点标号for (size_t i = 1; i <= nodenum; ++i){parent[i] = i;if (i == u) V[i] = Q.insert(0, i);//向堆中插入元素,并且将节点句柄存入数组else V[i] = Q.insert(MAX, i);}size_t sum = 0;while (!Q.empty()){pair<size_t, size_t> min = Q.extractMin();V[min.second] = nullptr;//置空,标志着该节点已删除sum += min.first;for (edgeNode *curr = graph[min.second]; curr; curr = curr->nextEdge){//以其为中介,更新各点到MST的距离if (V[curr->adjvertex] != nullptr && curr->weight < V[curr->adjvertex]->key){Q.decreaseKey(V[curr->adjvertex], curr->weight);parent[curr->adjvertex] = min.second;}}//将该边加入MSTif (min.second != u) mst->add2Edges(parent[min.second], min.second,min.first);}return sum;}

由于使用斐波那契堆实现最小优先队列,故时间复杂度为O(E+VlgV)。目前测试一切正常,对于任意节点作为源均能得到正确结果,比如,按书上图23-5以顶点5(按字母顺序编号)为源点可得到如下结果:

习题23.1-11

    设边为(u,v).

    1、将(u,v)加入T中,此时必形成环;

    2、以u为起点搜寻另一条从u到v的路径,或者经v搜寻能够再次到达u的路径;

    3、将该路径上的最大权值边删除即可。


习题23.2-7

   由于加入的是新顶点,设为u,则与其关联的边的端点一个u,一个在MST中,因而分属于不同的树,因而随便加入一条边都不会形成环,那么:

   1、若是邻接表形式的话,则只要搜寻u的邻接链表找到最小权值边加入MST即可,时间为O(V),最多V条边;

   2、若是邻接矩阵则要搜寻u那行,时间亦为O(V);

   因此,最快需要O(V)时间更新。


思考题


本篇博客用到所有完整C++代码,斐波那契堆请参考这里

#include<iostream>#include<algorithm>#include<fstream>#include<vector>#include<utility>#include"FibonacciHeap.h"//#define NOPARENT 0#define MAX0x7fffffffusing namespace std;//enum color{ WHITE, GRAY, BLACK };struct edgeNode{//边节点size_t adjvertex;//该边的关联的顶点size_t weight;//边权重edgeNode *nextEdge;//下一条边edgeNode(size_t adj, size_t w) :adjvertex(adj), weight(w), nextEdge(nullptr){}};struct edge{//边,和edgeNode有别size_t u, v;size_t weight;edge(size_t u_, size_t v_, size_t w) :u(u_), v(v_), weight(w){}};class AGraph{//无向图private:vector<edgeNode*> graph;size_t nodenum;void transformGraph(vector<edge>&);public:AGraph(size_t n = 0){editGraph(n); }void editGraph(size_t n){nodenum = n;graph.resize(n + 1);}size_t size()const { return nodenum; }void initGraph();//初始化无向图edgeNode* search(size_t, size_t);//查找边void add1Edge(size_t, size_t, size_t);void add2Edges(size_t, size_t, size_t);//添加边void delete1Edge(size_t, size_t);void delete2Edges(size_t, size_t);//删除边size_t kruskal(AGraph*);size_t prim(AGraph*,size_t);void print();void destroy();~AGraph(){ destroy(); }};void AGraph::initGraph(){size_t start, end;size_t w;ifstream infile("F:\\mst.txt");while (infile >> start >> end >> w)add1Edge(start, end, w);}void AGraph::transformGraph(vector<edge> &E){for (size_t i = 1; i != graph.size(); ++i){//改造edgeNode,变成edgeedgeNode *curr = graph[i];while (curr != nullptr){if (i < curr->adjvertex){//顶点u,v之间的边只存储一条,(u,v),且u < v。edge e(i, curr->adjvertex, curr->weight);E.push_back(e);}curr = curr->nextEdge;}}}edgeNode* AGraph::search(size_t start, size_t end){edgeNode *curr = graph[start];while (curr != nullptr && curr->adjvertex != end)curr = curr->nextEdge;return curr;}void AGraph::add1Edge(size_t start, size_t end, size_t weight){edgeNode *curr = search(start, end);if (curr == nullptr){edgeNode *p = new edgeNode(end, weight);p->nextEdge = graph[start];graph[start] = p;}}inline void AGraph::add2Edges(size_t start, size_t end, size_t weight){add1Edge(start, end, weight);add1Edge(end, start, weight);}void AGraph::delete1Edge(size_t start, size_t end){edgeNode *curr = search(start, end);if (curr != nullptr){if (curr->adjvertex == end){graph[start] = curr->nextEdge;delete curr;}else{edgeNode *pre = graph[start];while (pre->nextEdge->adjvertex != end)pre = pre->nextEdge;pre->nextEdge = curr->nextEdge;delete curr;}}}inline void AGraph::delete2Edges(size_t start, size_t end){delete1Edge(start, end);delete1Edge(end, start);}size_t AGraph::kruskal(AGraph *mst){//克鲁斯卡尔算法求最小生成树,返回最小权值和,最小生成树记录在mst中struct findRoot:public binary_function<vector<size_t>,size_t,size_t>{//局部函数对象类,用于查询并查集size_t operator()(const vector<size_t> &UFS, size_t v)const{while (v != UFS[v]) v = UFS[v];return v;}};struct edgeCompare:public binary_function<edge,edge,bool>{//局部函数对象类,用于边比较器bool operator()(const edge &left, const edge &right)const{return left.weight < right.weight;}};vector<edge> E;transformGraph(E);//将邻接链表转换为边集合vector<size_t> UFS(nodenum + 1);size_t sum = 0;for (size_t i = 1; i <= nodenum; ++i)UFS[i] = i;//初始化并查集Union-Find-Setsort(E.begin(), E.end(), edgeCompare());//对edge边按权值非递减排序for (size_t i = 0; i != E.size(); ++i){//逐条考察边size_t u_root = findRoot()(UFS, E[i].u), v_root = findRoot()(UFS, E[i].v);if (u_root != v_root){//若属于不同的树,即根节点不一样,则加入sum += E[i].weight;mst->add2Edges(E[i].u, E[i].v, E[i].weight);UFS[u_root] = v_root;}}return sum;}size_t AGraph::prim(AGraph *mst,size_t u){//普利姆算法求最小生成树,采用斐波那契堆。返回最小权值和;mst存储最小生成树,时间O(E+VlgV)vector<size_t> parent(nodenum + 1);//存储每个顶点在斐波那契堆中的对应节点的地址,这样便于修改距离等vector<fibonacci_heap_node<size_t,size_t>*> V(nodenum + 1);fibonacci_heap<size_t, size_t> Q;//斐波那契堆,键为距离,值为顶点标号for (size_t i = 1; i <= nodenum; ++i){parent[i] = i;if (i == u) V[i] = Q.insert(0, i);//向堆中插入元素,并且将节点句柄存入数组else V[i] = Q.insert(MAX, i);}size_t sum = 0;while (!Q.empty()){pair<size_t, size_t> min = Q.extractMin();V[min.second] = nullptr;//置空,标志着该节点已删除sum += min.first;for (edgeNode *curr = graph[min.second]; curr; curr = curr->nextEdge){//以其为中介,更新各点到MST的距离if (V[curr->adjvertex] != nullptr && curr->weight < V[curr->adjvertex]->key){Q.decreaseKey(V[curr->adjvertex], curr->weight);parent[curr->adjvertex] = min.second;}}//将该边加入MSTif (min.second != u) mst->add2Edges(parent[min.second], min.second,min.first);}return sum;}inline void AGraph::print(){for (size_t i = 1; i != graph.size(); ++i){edgeNode *curr = graph[i];cout << i;if (curr == nullptr) cout << " --> null";elsewhile (curr != nullptr){cout << " --<" << curr->weight << ">--> " << curr->adjvertex;curr = curr->nextEdge;}cout << endl;}}void AGraph::destroy(){for (size_t i = 1; i != graph.size(); ++i){edgeNode *curr = graph[i], *pre;while (curr != nullptr){pre = curr;curr = curr->nextEdge;delete pre;}graph[i] = curr;}}const size_t nodenum = 9;size_t main(){AGraph graph(nodenum), mst(nodenum);graph.initGraph();graph.print();cout << endl;cout << graph.prim(&mst,5) << endl;mst.print();getchar();return 0;}




1 0
原创粉丝点击