layout: post

title: 最短路径算法表示

date: 2017-04-16

tag: 数据结构和算法

目录

  • TOC
    {:toc}

最短路径问题

  • 两个不同顶点之间的所有路径中,边的权值之和最小的那一条路径;第一个顶点为源点(Source);最后一个顶点为终点(Destination)

无权图单源最短路径

  • 从某固定源点出发,求其到所有其他顶点的最短路径
  • 无权图(无论是否有向):按照路径长度递增(非递减)的顺序找出到各个顶点的最短路

最短路径算法表示_单源最短路径

  • 类似于BFS,运用队列dist[W] = S到W最短距离;dist[S] = 0;path[W] = S到W路上经过的顶点;时间复杂度T = O(V + E)

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>

using namespace std;

/* 图的邻接表表示法 */
#define MaxVertexNum 100 /*最大顶点数设为100*/
#define INFINITY 65535 /*设为双字节无符号整数的最大值为65535*/
typedef int Vertex; /*用顶点下标表示顶点,为整型*/
typedef int WeightType; /*边的权值设为整型*/
typedef char DataType; /*顶点存储的数据类型设为字符型*/

/*边的定义*/
typedef struct ENode* PtrToENode;
struct ENode
{
Vertex V1, V2; //有向边<v1,v2>
WeightType Weight;//权重
};
typedef PtrToENode Edge;

/*邻接点的定义*/
typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode
{
Vertex AdjV; //邻接点下标
WeightType Weight; //边权重
PtrToAdjVNode Next; //指向下一个邻接点的指针
};

/*顶点表头结点的定义*/
typedef struct VNode
{
PtrToAdjVNode FirstEdge; //边表头指针
DataType Data; //存顶点的数据
//注意:很多时候,顶点无数据,此时Data可以不出现
}AdjList[MaxVertexNum];

/*图结点的定义*/
typedef struct GNode *PtrToGNode;
struct GNode
{
int Nv; //顶点树
int Ne; //边数
AdjList G; //邻接表
};

typedef PtrToGNode LGraph; /* 以邻接表方式存储的图类型 */

/*邻接表存储--无权图的段园最短路径算法*/
/* dist[]和path[]全部初始化为-1*/
void Unweighted(LGraph Graph, int dist[], int path[], Vertex S)
{
queue<Vertex> Q;
Vertex V;
PtrToAdjVNode W;

dist[S] = 0;/*初始化源点*/
Q.push(S);
while (!Q.empty())
{
V = Q.front();
Q.pop();
for (W = Graph->G[V].FirstEdge; W; W = W->Next) /*对V的每个邻接点W->AdjV */
{
if (dist[W->AdjV]==-1) //未被访问过
{
dist[W->AdjV] = dist[V] + 1; //W->AdjV到S的距离更新
path[W->AdjV] = V; //将V记录在S到W->AdjV的路径上;方便后序输出路径
Q.push(W->AdjV);
}
}
} //end while
}



有权图单源最短路径

  • 单源最短路径问题,即在图中求出给定顶点到其它任一顶点的最短路径。在弄清楚如何求算单源最短路径问题之前,必须弄清楚最短路径的最优子结构性质。

一.最短路径的最优子结构性质

  • 该性质描述为:如果P(i,j)={Vi....Vk..Vs...Vj}是从顶点i到j的最短路径,k和s是这条路径上的一个中间顶点,那么P(k,s)必定是从k到s的最短路径。下面证明该性质的正确性。
  • 假设P(i,j)={Vi....Vk..Vs...Vj}是从顶点i到j的最短路径,则有P(i,j)=P(i,k)+P(k,s)+P(s,j)。而P(k,s)不是从k到s的最短距离,那么必定存在另一条从k到s的最短路径P'(k,s),那么P'(i,j)=P(i,k)+P'(k,s)+P(s,j)<P(i,j)。则与P(i,j)是从i到j的最短路径相矛盾。因此该性质得证。

二.Dijkstra算法

  • 由上述性质可知,如果存在一条从i到j的最短路径(Vi.....Vk,Vj),Vk是Vj前面的一顶点。那么(Vi...Vk)也必定是从i到k的最短路径。为了求出最短路径,Dijkstra就提出了以最短路径长度递增,逐次生成最短路径的算法。譬如对于源顶点V0,首先选择其直接相邻的顶点中长度最短的顶点Vi,那么当前已知可得从V0到达Vj顶点的最短距离dist[j]=min{dist[j],dist[i]+matrix[i][j]}。根据这种思路,
  • 假设存在G=<V,E>,源顶点为V0,U={V0},dist[i]记录V0到i的最短距离,path[i]记录从V0到i路径上的i前面的一个顶点。
    - 1.从V-U中选择使dist[i]值最小的顶点i,将i加入到U中; - 2.更新与i直接相邻顶点的dist值。(dist[j]=min{dist[j],dist[i]+matrix[i][j]}) - 3.知道U=V,停止。

理解思路

最短路径算法表示_最短路径_02

算法复杂度

最短路径算法表示_单源最短路径_03

  • 对于找最小距离的算法,可以用堆实现

/*!
* \file 图-最短路径问题.cpp
*
* \author ranjiewen
* \date 2017/04/16 11:31
*
*
*/

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>

using namespace std;

/* 图的邻接表表示法 */
#define MaxVertexNum 100 /*最大顶点数设为100*/
#define INFINITY 65535 /*设为双字节无符号整数的最大值为65535*/
typedef int Vertex; /*用顶点下标表示顶点,为整型*/
typedef int WeightType; /*边的权值设为整型*/
typedef char DataType; /*顶点存储的数据类型设为字符型*/

/*边的定义*/
typedef struct ENode* PtrToENode;
struct ENode
{
Vertex V1, V2; //有向边<v1,v2>
WeightType Weight;//权重
};
typedef PtrToENode Edge;

/*图结点的定义*/
typedef struct GNode_ *PtrToGNode;
struct GNode_ //区别GNode
{
int Nv; //顶点树
int Ne; //边数
WeightType G[MaxVertexNum][MaxVertexNum]; //邻接矩阵
DataType Data[MaxVertexNum];// 存顶点的数据
//注意:很多情况下,顶点无数据,此时Data[]可以不用出现
};
typedef PtrToGNode MGraph; /*用邻接矩阵存储的图类型*/

/* 邻接矩阵存储 --有权图的单源最短路径算法*/
Vertex FindMinDist(MGraph Graph, int dist[], int collected[])
{
/*返回未被收录顶点中dist最小者*/
Vertex minV, V;
int MinDist = INFINITY;

for (V = 0; V <= Graph->Nv;V++)
{
if (collected[V]==false&&dist[V]<MinDist)
{
/*若未被收录,且dist[V]更小*/
MinDist = dist[V];
minV = V; //更新对于的顶点
}
}

if (MinDist<INFINITY) //若找到最小dist
{
return minV;
}
else
{
return -1; //没有找到
}
}

bool Dijkstra(MGraph Graph, int dist[], int path[], Vertex S)
{
int collected[MaxVertexNum];
Vertex V, W;
/*初始化:此处默认邻接矩阵中不存在的边用INFINITY表示*/
for (V = 0; V < Graph->Nv;V++)
{
dist[V] = Graph->G[S][V];
if (dist[V]<INFINITY) //直接相连的节点
{
path[V] = S;
}
else
{
path[V] = -1;
}
collected[V] = false;
}

/*先将起点收入集合*/
dist[S] = 0;
collected[S] = true;
while (1)
{
/*V=未被收录顶点中dist最小者*/
V = FindMinDist(Graph, dist, collected);
if (V==-1)
{
break; //这样的V不存在,算法结束
}
collected[V] = true; //收录V
for (W = 0; W < Graph->Nv;W++) //对图中的每个顶点W
{
/*若w是v的邻接点并且未被收录*/
if (collected[W]==false&&Graph->G[V][W]<INFINITY)
{
if (Graph->G[V][W]<0)
{
return false; //若有负边,不能正确解决,返回错误标记
}
if (dist[V] + Graph->G[V][W]<dist[W]) /* 若收录V使得dist[W]变小 */
{
dist[W] = dist[V] + Graph->G[V][W]; /* 更新dist[W] */
path[W] = V; /* 更新S到W的路径 */
}
}
}
} //end while

return true; /* 算法执行完毕,返回正确标记 */
}

#include <stack>
void showPath(int *path, int v, int v0) //打印最短路径上的各个顶点
{
stack<int> s;
int u = v;
while (v != v0)
{
s.push(v);
v = path[v];
}
s.push(v);
while (!s.empty())
{
cout << s.top() << " ";
s.pop();
}
}

int main()
{
int N, E;
while (cin>>N>>E&&E!=0) //简单创建图
{
int i, j;
int V, W, weight; //表示一条边的信息
MGraph Graph = (MGraph)malloc(sizeof(struct GNode_)); //NULL有错
int *dist = (int*)malloc(sizeof(int)*N);
int *path = (int*)malloc(sizeof(int)*N);

for (i = 0; i < N;i++)
{
for (j = 0; j < N;j++)
{
Graph->G[i][j] = INFINITY;
}
}
Graph->Ne = E;
Graph->Nv = N;
for (i = 0; i < E;i++)
{
cin >> V >> W >> weight;
Graph->G[V][W] = weight;
}

int S=0; //源点
Dijkstra(Graph, dist, path, S);
for (i = 0; i < N;i++)
{
if (i!=S)
{
showPath(path, i, S);
cout << dist[i] << endl;
}
}
}
return 0;
}



测试结果:

最短路径算法表示_最短路径_04

多源最短路

算法描述

  • 1)算法思想原理:
  • Floyd算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是寻找从点i到点j的最短路径。从动态规划的角度看问题,我们需要为这个目标重新做一个诠释(这个诠释正是动态规划最富创造力的精华所在)
  • 从任意节点i到任意节点j的最短路径不外乎2种可能,1是直接从i到j,2是从i经过若干个节点k到j。所以,我们假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。
  • 2).算法描述:
  • a.从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。   
  • b.对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知的路径更短。如果是更新它。

最短路径算法表示_单源最短路径_05

  • 3).算法复杂度
    最短路径算法表示_最短路径_06

实现


/* 邻接矩阵存储 - 多源最短路算法 */

bool Floyd( MGraph Graph, WeightType D[][MaxVertexNum], Vertex path[][MaxVertexNum] )
{
Vertex i, j, k;

/* 初始化 */
for ( i=0; i<Graph->Nv; i++ )
for( j=0; j<Graph->Nv; j++ ) {
D[i][j] = Graph->G[i][j];
path[i][j] = -1;
}

for( k=0; k<Graph->Nv; k++ )
for( i=0; i<Graph->Nv; i++ )
for( j=0; j<Graph->Nv; j++ )
if( D[i][k] + D[k][j] < D[i][j] ) {
D[i][j] = D[i][k] + D[k][j];
if ( i==j && D[i][j]<0 ) /* 若发现负值圈 */
return false; /* 不能正确解决,返回错误标记 */
path[i][j] = k;
}
return true; /* 算法执行完毕,返回正确标记 */
}


Reference


C/C++基本语法学习 STL C++ primer