<C/C++图>最小生成树:Prim算法

来源:互联网 发布:淘宝内衣女店主 编辑:程序博客网 时间:2024/05/16 08:51

一,最小生成树算法基本概念

最小生成树是数据结构中图的一种重要应用,它的要求是从一个有n个节点的带权完全图中选择n-1条边并使这个图仍然连通(也即得到了一棵生成树),同时还要考虑使树的权之和最小。最小生成树可以用,Kruskal(克鲁斯卡尔)算法或Prim(普里姆)算法求出。


1,Prim算法

1),算法简单描述:

1).输入:一个加权连通图,其中顶点集合为V,边集合为E;

2).初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;

3).重复下列操作,直到Vnew = V:

a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);

b.将v加入集合Vnew中,将<u, v>边加入集合Enew中;

4).输出:使用集合Vnew和Enew来描述所得到的最小生成树

2),下面对算法的图例描述:

绿色顶点及边表示:已经被选中为最小生成树中的顶点

淡蓝色顶点及边表示:当前距离已选中的顶点中的最小权值边及其相连的顶点

蓝色表示顶点及边表示:当前已经选中顶点可达的顶点或者边

图例说明不可选可选已选(Vnew 

此为原始的加权连通图。每条边一侧的数字代表其权值。---

1,选取任意顶点为起始点,比如D。,
2,顶点ABEF通过单条边与D相连。A是距离D最近的顶点,因此将A及对应边AD以高亮表示。
C, GA, B, E, FD 

1,下一个顶点为距离DA最近的顶点。BD为9,距A为7,E为15,F为6。因此,FDA最近,因此将顶点F与相应边DF以高亮表示。C, GB, E, FA, D算法继续重复上面的步骤。距离A为7的顶点B被高亮表示。CB, E, GA, D, F 

在当前情况下,可以在CEG间进行选择。CB为8,EB为7,GF为11。E最近,因此将顶点E与相应边BE高亮表示。C, E, GA, D, F, B 

这里,可供选择的顶点只有CGCE为5,GE为9,故选取C,并与边EC一同高亮表示。C, GA, D, F, B, E

顶点G是唯一剩下的顶点,它距F为11,距E为9,E最近,故高亮表示G及相应边EGGA, D, F, B, E, C

现在,所有顶点均已被选取,图中绿色部分即为连通图的最小生成树。在此例中,最小生成树的权值之和为39。A, D, F, B, E, C, G


2,Kruskal算法

Kruskal算法与Prim算法的不同之处在于,Kruskal在找最小生成树结点之前,需要对所有权重边做从小到大排序。将排序好的权重边依次加入到最小生成树中,如果加入时产生回路就跳过这条边,加入下一条边。当所有结点都加入到最小生成树中之后,就找出了最小生成树。


1).算法简单描述

1).记Graph中有v个顶点,e个边

2).新建图Graphnew,Graphnew中拥有原图中相同的e个顶点,但没有边

3).将原图Graph中所有e个边按权值从小到大排序

4).循环:从权值最小的边开始遍历每条边 直至图Graph中所有的节点都在同一个连通分量中

                if 这条边连接的两个节点于图Graphnew中不在同一个连通分量中

                                         添加这条边到图Graphnew

2),下面对算法的图例描述:

(每次所选的边都要与原所选边进行是否为圈的判断)

首先第一步,我们有一张图Graph,有若干点和边 

 

将所有的边的长度排序,用排序的结果作为我们选择边的依据。这里再次体现了贪心算法的思想。资源排序,对局部最优的资源进行选择,排序完成后,我们率先选择了边AD。这样我们的图就变成了右图

 

 

 

在剩下的变中寻找。我们找到了CE。这里边的权重也是5

依次类推我们找到了6,7,7,即DF,AB,BE。

下面继续选择, BC或者EF尽管现在长度为8的边是最小的未选择的边。但是现在他们已经连通了(对于BC可以通过CE,EB来连接,类似的EF可以通过EB,BA,AD,DF来接连)。所以不需要选择他们。类似的BD也已经连通了(这里上图的连通线用红色表示了)。

最后就剩下EG和FG了。当然我们选择了EG。最后成功的图就是右:

 



二,C++模板实现

1,Graph.h代码实现如下:

#include "stdafx.h"#include "iostream"#include "queue"using namespace std;template<class DistType/*边的权值的类型*/> class Edge//边的定义{public:Edge(int dest, DistType weight){m_nposTable=dest;m_distWeight=weight; m_pnext=NULL;}~Edge(){}public:int m_nposTable;//该边的目的顶点在顶点集中的位置DistType m_distWeight;//边的权重值Edge<DistType> *m_pnext;//下一条边(注意不是下一个顶点,因为m_nposTable已经知道了这个顶点的位置)};//声明template<class NameType/*顶点集名字类型*/, class DistType/*距离的数据类型*/> class Graph;template<class NameType/*顶点集名字类型*/, class DistType/*距离的数据类型*/> class Vertex//顶点的定义{public:Vertex(){padjEdge=NULL;m_vertexName=0;}~Vertex(){Edge<DistType> *pmove = padjEdge;while (pmove){padjEdge = pmove->m_pnext;delete pmove;pmove = padjEdge;}}private:friend class Graph<NameType,DistType>;//允许Graph类任意访问NameType m_vertexName;//顶点中的数据内容Edge<DistType> *padjEdge;//顶点的邻边};template<class NameType/*顶点集名字类型*/, class DistType/*距离的数据类型*/> class Graph{public:Graph(int size = m_nDefaultSize/*图顶点集的规模*/){m_pVertexTable = new Vertex<NameType, DistType>[size];  //为顶点集分配内存if (m_pVertexTable == NULL){exit(1);}m_numVertexs=0;m_nmaxSize=size;m_nnumEdges=0;}~Graph(){Edge<DistType> *pmove;for (int i=0; i < this->m_numVertexs; i++){pmove = this->m_pVertexTable[i].padjEdge;if (pmove){this->m_pVertexTable[i].padjEdge = pmove->m_pnext;delete pmove;pmove = this->m_pVertexTable[i].padjEdge;}}delete[] m_pVertexTable;}int GetNumEdges(){//获得边的数目return m_nnumEdges/2;}int GetNumVertexs(){//获得顶点数目return m_numVertexs;}bool IsGraphFull() const{     //图满的?return m_nmaxSize == m_numVertexs;}//在顶点集中位置为v1和v2的顶点之间插入边bool InsertEdge(int v1, int v2, DistType weight=m_Infinity); //插入顶点名字为vertex的顶点bool InsertVertex(const NameType vertex);  //打印图void PrintGraph();   //顶点v到其他各个顶点的最短路径(包括自身)void Dijkstra(int v, DistType *shotestpath);//获取顶点集中位置为v1和v2的顶点之间边的权重值DistType GetWeight(int v1, int v2); //获得在顶点集中的位置为v的顶点的名字NameType GetVertexValue(int v);//用该顶点的名字来寻找其在顶点集中的位置int GetVertexPosTable(const NameType vertex);  //得到顶点v的邻点中权值最小的那条边Edge<DistType> *GetMin(int v, int *visited);   //最小生成树void Prim(Graph<NameType, DistType> &graph);  //深度搜索优先void DFS(int v, int *visited);      void DFS();//广度优先搜索void BFS(int v, int *visited);void BFS();//获取第v个顶点的名字(或者说内容)NameType GetVertexName(int v);   //获得顶点v的第一个相邻顶点,如果没有就返回-1int GetFirst(int v);       //获得顶点v1的邻点v2后的邻点int GetNext(int v1, int v2);private:Vertex<NameType, DistType> *m_pVertexTable;   //顶点集int m_numVertexs;//图中当前的顶点数量int m_nmaxSize;//图允许的最大顶点数static const int m_nDefaultSize = 10;       //默认的最大顶点集数目static const DistType m_Infinity = 65536;  //边的默认权值(可以看成是无穷大)int m_nnumEdges;//图中边的数目};//返回顶点vertexname在m_pVertexTable(顶点集)中的位置//如果不在顶点集中就返回-1template<class NameType, class DistType> int Graph<NameType, DistType>::GetVertexPosTable(const NameType vertexname){for (int i=0; i < this->m_numVertexs; i++){if (vertexname == m_pVertexTable[i].m_vertexName){return i;}}return -1;}//打印图中的各个顶点及其链接的边的权重template<class NameType, class DistType> void Graph<NameType, DistType>::PrintGraph(){Edge<DistType> *pmove;for (int i=0; i<this->m_numVertexs; i++){cout << this->m_pVertexTable[i].m_vertexName << "->";pmove = this->m_pVertexTable[i].padjEdge;while (pmove){cout << pmove->m_distWeight << "->" << this->m_pVertexTable[pmove->m_nposTable].m_vertexName << "->";pmove = pmove->m_pnext;}cout << "NULL" << endl;}}//获得在顶点集中的位置为v的顶点的名字template<class NameType, class DistType> NameType Graph<NameType, DistType>::GetVertexValue(int v){if (v<0 || v>=this->m_numVertexs){cerr << "查找的顶点位置参数有误,请检查!" <<endl;exit(1);}return m_pVertexTable[v].m_vertexName;}//返回顶点v1和v2之间的边权值,//如果没有直接相连(即不是一条边直接相连)则返回无穷大template<class NameType, class DistType> DistType Graph<NameType, DistType>::GetWeight(int v1, int v2){if (v1>=0 && v1<this->m_numVertexs && v2>=0 && v2<this->m_numVertexs){if (v1 == v2){return 0;}Edge<DistType> *pmove = m_pVertexTable[v1].padjEdge;while (pmove){if (pmove->m_nposTable == v2){return pmove->m_distWeight;}pmove = pmove->m_pnext;}}return m_Infinity;}//顶点依次插入到分配好的顶点集中template<class NameType, class DistType> bool Graph<NameType, DistType>::InsertVertex(const NameType vertexname){if (IsGraphFull()){cerr<<"图已经满,请勿再插入顶点!"<<endl;return false;}else{this->m_pVertexTable[this->m_numVertexs].m_vertexName = vertexname;this->m_numVertexs++;}return true;}//在顶点集位置为v1和v2的顶点之间插入权值为weght的边(务必保持输入的准确性,否则.....)template<class NameType, class DistType> bool Graph<NameType, DistType>::InsertEdge(int v1, int v2, DistType weight){if (v1 < 0 && v1 > this->m_numVertexs && v2 < 0 && v2 > this->m_numVertexs){cerr<<"边的位置参数错误,请检查! "<<endl;return false;}else{Edge<DistType> *pmove = m_pVertexTable[v1].padjEdge;if (pmove == NULL)//如果顶点v1没有邻边{ //建立顶点v1的第一个邻边(该邻边指明了目的顶点)m_pVertexTable[v1].padjEdge = new Edge<DistType>(v2, weight);m_nnumEdges++;//图中边的数目return true;}else//如果有邻边{while (pmove->m_pnext){pmove = pmove->m_pnext;}pmove->m_pnext = new Edge<DistType>(v2, weight);m_nnumEdges++;//图中边的数目return true;}}}template<class NameType, class DistType>void Graph<NameType, DistType>::Dijkstra(int v, DistType *shPath){int num =GetNumVertexs();int *visited = new int[num];for (int i=0; i < num; i++){//初始化visited[i] = 0;//未访问shPath[i] = this->GetWeight(v, i);//顶点v(当前中间点)到各个相邻顶点的边权值,其他情况返回无穷大}visited[v] = 1;//第v个顶点初始化为被访问,并以他为中点点开始找最短路径for (int i = 1; i < num; i++){DistType min = this->m_Infinity;int u=0;//寻找新的中间点u,依据就是数组中权值最小的那个点的位置(且没被访问过)for (int j=0; j < num; j++){   if (!visited[j]){if (shPath[j]<min){min = shPath[j];//获得当前shPath数组中的最小边权重u = j;//用u来记录获取最小值时的顶点位置,即新的中间点}}}visited[u] = 1;//已经确定的最短路径//以u为中间点寻找顶点v到顶点w的最短路径for (int w=0; w < num; w++){  DistType weight = this->GetWeight(u, w);//顶点u(当前中间点)到各个相邻顶点的边权值,其他情况返回无穷大if (!visited[w] && weight != this->m_Infinity ){if ( shPath[u]+weight < shPath[w] ){shPath[w] = shPath[u] + weight;//更新顶点v到w的最短路径值}}}}delete[] visited;}//获得顶点v1的邻点v2后的邻点template<class NameType, class DistType> int Graph<NameType, DistType>::GetNext(int v1, int v2){if (-1 != v1){Edge<DistType> *pmove = this->m_pVertexTable[v1].padjEdge;while (NULL != pmove->m_pnext){if (pmove->m_nposTable==v2){return pmove->m_pnext->m_nposTable;}pmove = pmove->m_pnext;}        }return -1;}//从第v个顶点开始深度遍历template<class NameType, class DistType> void Graph<NameType, DistType>::DFS(int v, int *visited){cout << "->" << this->GetVertexName(v);visited[v] = 1;int firstVertex = this->GetFirst(v);//获得顶点v的第一个相邻顶点,若没有则返回-1while (-1 != firstVertex){if (!visited[firstVertex])//如果没有访问过{cout << "->" << this->GetWeight(v, firstVertex);//获得顶点v及其邻点firstVertex之间的权值DFS(firstVertex, visited);}firstVertex = this->GetNext(v, firstVertex);//获得顶点v的邻点firstVertex后的邻点,如果没有就返回-1}}template<class NameType, class DistType> void Graph<NameType, DistType>::DFS(){int *visited = new int[this->m_numVertexs];for (int i=0; i<this->m_numVertexs; i++){visited[i] = 0;}cout << "head";DFS(0, visited);//从第一个顶点开始遍历cout << "--->end";}template<class NameType, class DistType> void Graph<NameType, DistType>::BFS(){int *visited = new int[this->m_numVertexs];for (int i=0; i<this->m_numVertexs; i++){visited[i] = 0;}cout << "head";BFS(0, visited);//从第一个顶点开始遍历cout << "--->end";}//从第v个顶点开始广度遍历template<class NameType, class DistType> void Graph<NameType, DistType>::BFS(int v, int *visited){cout << "->" << this->GetVertexName(v);visited[v]=1;queue<int> que;//=new queue<int>[this->GetNumVertexs()];que.push(v);//进队(队列的末端)while (!que.empty()){v=que.front();//出队首元素que.pop();//删除队首元素int firstvertex=GetFirst(v);while(firstvertex != -1){if (!visited[firstvertex]){cout << "->" << this->GetWeight(v, firstvertex);//获得顶点v及其邻点firstVertex之间的权值que.push(firstvertex);visited[firstvertex]=1;cout << "->" << this->GetVertexName(firstvertex);}firstvertex=GetNext(v,firstvertex);}}}//获得在顶点集中的位置为v的顶点的名字template<class NameType, class DistType> NameType Graph<NameType, DistType>::GetVertexName(int v){if (v<0 || v>=this->m_numVertexs){cerr << "查找的顶点位置参数有误,请检查!" <<endl;exit(1);}return m_pVertexTable[v].m_vertexName;}//获得顶点v的第一个相邻顶点,如果没有就返回-1template<class NameType, class DistType> int Graph<NameType, DistType>::GetFirst(int v){if (v<0 || v>=this->m_numVertexs){return -1;}Edge<DistType> *ptemp = this->m_pVertexTable[v].padjEdge;return m_pVertexTable[v].padjEdge ? m_pVertexTable[v].padjEdge->m_nposTable : -1;}template<class NameType, class DistType> Edge<DistType>* Graph<NameType, DistType>::GetMin(int v, int *visited){Edge<DistType> *pmove = this->m_pVertexTable[v].padjEdge; Edge<DistType> *ptemp = new Edge<DistType>(0, this->m_Infinity);Edge<DistType> *pmin = ptemp;while (pmove){if (!visited[pmove->m_nposTable] && pmin->m_distWeight>pmove->m_distWeight){pmin = pmove;}pmove = pmove->m_pnext;}if (pmin == ptemp){delete ptemp;return NULL;}delete ptemp;return pmin;}template<class NameType, class DistType> void Graph<NameType, DistType>::Prim(Graph<NameType, DistType> &graphprim){ int *nodeVertex = new int[this->m_numVertexs];    //用来存储被访问过的顶点int *visited = new int[this->m_numVertexs];//设置顶点被访问过与否int count = 0;Edge<DistType> *ptemp1;Edge<DistType> *ptemp2 = new Edge<DistType>(0, this->m_Infinity);Edge<DistType> *pmin;int min=0;//初始化最小生成树for (int i=0; i<this->m_numVertexs; i++){graphprim.InsertVertex(this->m_pVertexTable[i].m_vertexName);nodeVertex[i] = 0;visited[i] = 0;}visited[0] = 1;//从第一个顶点开始,并标记为以访问while(++count < this->m_numVertexs){pmin = ptemp2;pmin->m_distWeight = this->m_Infinity;//获得已访问顶点和未访问顶点之间的最小权值for (int i=0; i<count; i++){ptemp1 = GetMin(nodeVertex[i], visited);if (NULL == ptemp1){continue;}if (pmin->m_distWeight > ptemp1->m_distWeight){pmin = ptemp1;min = nodeVertex[i];            }}nodeVertex[count] = pmin->m_nposTable;visited[nodeVertex[count]] = 1;graphprim.InsertEdge(pmin->m_nposTable, min, pmin->m_distWeight);graphprim.InsertEdge(min, pmin->m_nposTable, pmin->m_distWeight);}delete ptemp2;delete[] nodeVertex;delete[] visited;}


2,主程序代码如下:

// ConsoleAppMyGraph.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include "Graph.h"#include <iostream>using namespace std;int _tmain(int argc, _TCHAR* argv[]){Graph<char *, int> graph(7);char *vertex[7] = {"【地大】", "【武大】", "【华科】", "【交大】", "【北大】", "【清华】", "【复旦】"};//顶点集for (int i=0; i<7; i++){graph.InsertVertex(vertex[i]);}cout<<"一,图的初始化(邻结表存储):======================================"<<endl;graph.PrintGraph();cout<<"图中顶点的数目:"<<graph.GetNumVertexs()<<endl;cout <<endl;int edge[][3] = {{0, 1, 43}/*地大到武大的距离*/, {0, 2, 12}, {1, 2, 38}, {2, 3 ,1325},{3, 6, 55},{4, 5, 34}, {4, 6, 248},{0,3,400},{2,6,314},{2,4,37}};    //分配距离 int len=sizeof(edge)/sizeof(edge[0]);for (int i=0; i < len; i++){graph.InsertEdge(edge[i][0], edge[i][1], edge[i][2]);graph.InsertEdge(edge[i][1], edge[i][0], edge[i][2]);}cout<<"二,添加边后的图(无向图):=================================="<<endl;graph.PrintGraph();cout<<"图中边的数目(实际上是所示边数的两倍,因为是双向的):"<<graph.GetNumEdges()<<endl;cout <<endl;cout<<"三,Dijkstra法最短路径为:=========================="<<endl;int shortestPath[7];//存储Dijkstra算法最短路径值graph.Dijkstra(0, shortestPath);for (int i=0; i<7; i++){cout << graph.GetVertexValue(0) << "--->" << graph.GetVertexValue(i) << ":   " << shortestPath[i] <<endl;}cout<<endl;cout<<"四,图的遍历:=========================="<<endl;cout<<"1,DFS深度优先遍历结果:"<<endl;graph.DFS();cout<<endl<<endl;cout<<"2,BFS广度优先遍历结果:"<<endl;graph.BFS();cout<<endl<<endl;cout<<"五,最小生成生成树:================================="<<endl;Graph<char *, int> graphPrim;graph.Prim(graphPrim);cout<<"使用DFS遍历:"<<endl;graphPrim.DFS();cout<<endl<<endl;cout<<"使用BFS遍历:"<<endl;graphPrim.BFS();system("color 0A");system("pause");return 0;}



3,测试结果:




参考资源:

【1】《算法导论》

【2】《百度文库》

【3】《维基百科》

【4】https://en.wikipedia.org/wiki/Kruskal%27s_algorithm

【5】https://en.wikipedia.org/wiki/Prim%27s_algorithm

【6】http://baike.baidu.com/linkurl=tHoWD0_Xcu_fSMMoQgLmwh_0nR1Uk0xfYRhd8zgVYSX8DMsHPSXRsQZhgvw7SL9NyHDrQkU7j7B80uusAZbl4PzSGZzJwxqHba_FJ7jAkApSmLUjq2PswoytLdvZpL7nWoUCR4jJp_MxGyGA06YQKf5WU6sHZNBbUePG6NiytO

【7】http://blog.csdn.net/todd911/article/details/9219937

【8】http://blog.chinaunix.net/uid-25324849-id-2182922.html

【9】http://www.cnblogs.com/rollenholt/archive/2012/04/09/2439055.html



注:

本文部分文字学习并copy自网络,代码参考并改写于《算法导论》.

如果侵犯了您的版权,请联系本人tangyibiao520@163.com,本人将及时编辑掉!



1 0