<C/C++图>单源最短路径:Dijkstra算法

来源:互联网 发布:python是开源的吗 编辑:程序博客网 时间:2024/06/05 23:46

一,Dijkstra算法基本概念

Dijkstra算法使用了广度优先搜索解决非负权有向图的单源最短路径问题,算法最终得到一个最短路径树。该算法常用于路由算法或者作为其他图算法的一个子模块。

该算法的输入包含了一个有权重的有向图 G,以及G中的一个来源顶点 S。我们以 V 表示图 G 中所有顶点的集合。每一个图中的边,都是两个顶点所形成的有序元素对。(u, v) 表示从顶点 u 到 v 有路径相连。我们以 E 表示G中所有边的集合,而边的权重则由权重函数 w: E → [0, ∞] 定义。因此,w(u, v) 就是从顶点 u 到顶点 v 的非负权重(weight)。边的权重可以想像成两个顶点之间的距离。任两点间路径的权重,就是该路径上所有边的权重总和。已知有 V 中有顶点 s 及 t,Dijkstra 算法可以找到 s 到 t的最低权重路径(例如,最短路径)。这个算法也可以在一个图中,找到从一个顶点 s 到任何其他顶点的最短路径。对于不含负权的有向图,Dijkstra算法是目前已知的最快的单源最短路径算法。

1,算法步骤:

1). 初始时令 S={V0},T={其余顶点},T中顶点对应的距离值,若存在,d(V0,Vi)为弧上的权值,若不存在,d(V0,Vi)为∞。

2). 从T中选取一个其距离值为最小的顶点W且不在S中,加入S。

3). 对其余T中顶点的距离值进行修改:若加进W作中间顶点,从V0到Vi的距离值缩短,则修改此距离值。

4).重复上述步骤2、3,直到S中包含所有顶点,即W=Vi为止。

动画演示如下



二,C++模板实现

1,Graph.h代码如下(无向图):

#include "stdafx.h"#include "iostream"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);   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;}}else{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;}}}//Dijkstra单源最短路径算法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;}

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},{3,4,66},{2,4,37}};    //分配距离for (int i=0; i<11; 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;system("color 0A");system("pause");return 0;}

3,测试结果如下:


4,其中的计算过程如下:




邻接矩阵的代码:

本代码还设定了必经点。

#include <stdio.h>#include <string.h>#include <iostream>#include <windows.h>using namespace std;int dijkstra(int(*graphy)[8], int vertexs, int s_v, int e_v, int *path, int *ban_visit);int _tmain(int argc, _TCHAR* argv[]){system("color 0A");int g[8][8];memset(g, -1, sizeof(int)* 8 * 8);for (int i = 0; i < 8; i++)g[i][i] = 0;g[1][2] = 3, g[2][1] = 3;g[1][6] = 1, g[6][1] = 1;g[2][3] = 2;g[2][4] = 1;g[4][3] = 3;g[3][5] = 1;g[3][6] = 1;g[7][3] = 3;g[5][4] = 1;g[5][7] = 2;int path[8] = { 0 };int shortestdist;int mustpass[4] = {2,3,4,7};int ban_visit[10] = {0};ban_visit[5] = 1;for (int i = 0; i < 3; i++){int start_vertex = mustpass[i];int end_vertex = mustpass[i+1];shortestdist = dijkstra(g, 8, start_vertex, end_vertex, path, ban_visit);if (shortestdist != -1){cout << "shortest path, from " << start_vertex << " to " << end_vertex << " is: " << shortestdist << endl;cout << "path: ";for (int i = end_vertex; path[i] != -1; i = path[i])cout << i << "<--";cout << start_vertex << endl << endl;}elsecout << "shortest path, from " << start_vertex << " to " << end_vertex << " is not exist." << endl << endl;}system("pause");return 0;}// 最短路径:Dijkstra, dist[j] = min{dist[j], dist[i]+graphy[i][j]}// graphy: G = (V, E), V:顶点集, E: 边集// e ∈ E & e > 0表示顶点邻接,权值为e, e = -1表示两顶点不邻接//int dijkstra(int(*graphy)[8], int vertexs_nums, int start_vertex, int end_vertex, int *path, int *ban_visit){int *shortpath = new int[vertexs_nums];; //存储起点到各终点的最短路径int *visited = new int[vertexs_nums];;  //已访问过的顶点int num;int min, k;//初始化,-1代表max_valuememset(shortpath, -1, sizeof(int)*vertexs_nums);//起点到终点最短距离为0shortpath[start_vertex] = 0;memset(visited, 0, sizeof(int)*vertexs_nums);memset(path, -1, sizeof(int)*vertexs_nums);num = vertexs_nums - 1;while (num){//贪心策略:从访问过的顶点中,找出最短路径,从已知的最短路径开始延伸//寻找新的中间点k,依据就是数组中权值最小的那个点的位置(且没被访问过)  min = -1;k = -1;for (int i = 0; i<vertexs_nums; i++){if (ban_visit[i] != 1 && visited[i] != 1 && shortpath[i] != -1 &&(min != -1 && shortpath[i] < min || min == -1)){min = shortpath[i];k = i;}}//终点最短路径已找到或所有顶点最短路径都已找到if (end_vertex == k || min == -1)break;//标记访问过的顶点visited[k] = 1;num--;//dist[j] = min{d[j], dist[i]+graphy[i][j]}//更新未访问过邻接顶点的最短路径for (int i = 0; i<vertexs_nums; i++){if (ban_visit[i] != 1 && visited[i] != 1 && graphy[k][i] != -1 &&(shortpath[i] != -1 && shortpath[i] > min + graphy[k][i] || shortpath[i] == -1)){shortpath[i] = min + graphy[k][i];path[i] = k; //更新记录前驱顶点,供最后回溯最短路径}}}min = shortpath[end_vertex];delete[] shortpath;delete[] visited;return min;}


参考资源:

【1】《算法导论》

【2】《维基百科》

【3】《百度百科》

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

【5】http://blog.csdn.net/todd911/article/details/9347053


注:

本文部分文字学习并copy自网络,代码转载并改写于网络资源.

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

2 0
原创粉丝点击