<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),下面对算法的图例描述:
绿色顶点及边表示:已经被选中为最小生成树中的顶点
淡蓝色顶点及边表示:当前距离已选中的顶点中的最小权值边及其相连的顶点
蓝色表示顶点及边表示:当前已经选中顶点可达的顶点或者边
此为原始的加权连通图。每条边一侧的数字代表其权值。---
1,选取任意顶点为起始点,比如D。,
2,顶点A、B、E和F通过单条边与D相连。A是距离D最近的顶点,因此将A及对应边AD以高亮表示。C, GA, B, E, FD
1,下一个顶点为距离D或A最近的顶点。B距D为9,距A为7,E为15,F为6。因此,F距D或A最近,因此将顶点F与相应边DF以高亮表示。C, GB, E, FA, D算法继续重复上面的步骤。距离A为7的顶点B被高亮表示。CB, E, GA, D, F
在当前情况下,可以在C、E与G间进行选择。C距B为8,E距B为7,G距F为11。E最近,因此将顶点E与相应边BE高亮表示。无C, E, GA, D, F, B
这里,可供选择的顶点只有C和G。C距E为5,G距E为9,故选取C,并与边EC一同高亮表示。无C, GA, D, F, B, E
顶点G是唯一剩下的顶点,它距F为11,距E为9,E最近,故高亮表示G及相应边EG。无GA, 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也已经连通了(这里上图的连通线用红色表示了)。
二,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,本人将及时编辑掉!
- 最小生成树Prim算法C语言
- Prim最小生成树算法(C++)
- <C/C++图>最小生成树:Prim算法
- 图的最小生成树Prim算法朴素版(C++)
- Prim算法求图的最小生成树--C代码
- C语言实现图的Prim最小生成树算法
- [C++]最小生成树--Prim算法&Kruskal算法
- 最小生成树Prim算法朴素版 C语言实现
- 最小生成树Prim算法朴素版 C语言实现
- 数据结构(C实现)------- 最小生成树之Prim算法
- 最小生成树--prim算法的c语言描述
- Prim最小生成树算法,c代码,非伪…
- C语言最小生成树prim算法(USACO3.1)
- C语言-数据结构-prim算法求最小生成树
- C语言——Prim算法实现最小生成树
- 最小生成树Prim算法朴素版 C语言实现
- c语言:最小生成树之Prim算法
- 无向图的最小生成树算法的C程序实现代码(Prim算法)
- 黑马程序员-----面向对象
- Android学习阶段总结:自己做一个闹钟能学到什么?
- 第一篇博客,开个头,希望以后继续加油
- 算符优先分析
- 负载均衡的几种实现技术
- <C/C++图>最小生成树:Prim算法
- Android应用程序永久获取root权限方法
- 负载均衡的几种实现方式
- MYSQL数据库迁移到ORACLE数据库
- 每一个人都是一个独特的个体
- Linux下搭建Java环境变量并实现第一个Java小程序
- 鸡兔同笼
- Java final 修饰符的用法总结
- Extracting Data from array of hashes Ruby