• 最短路径算法表示



    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

  • 相关阅读:
    Redis环境搭建(整合SprinigBoot一键切换集群和单机环境)
    Docker + SpringBoot2.0快速构建镜像
    kafka实现分布式事务
    【本地事物】
    【数据结构之二叉树】
    变量
    python 介绍
    编程语言的区别
    路飞学城python教程 第二章练习题
    windows下nginx配合ffmpeg推送视频流
  • 原文地址:https://www.cnblogs.com/ranjiewen/p/6718893.html
Copyright © 2020-2023  润新知