Fork me on GitHub

最短路径算法表示


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,停止。
    

理解思路

算法复杂度

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

/*!
 * \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;
}


测试结果:

多源最短路

算法描述

  • 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 比己知的路径更短。如果是更新它。

  • 3).算法复杂度

实现


/* 邻接矩阵存储 - 多源最短路算法 */
 
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
posted @ 2017-04-16 15:31  ranjiewen  阅读(1127)  评论(0编辑  收藏  举报