最短路径问题06

最短路径问题是图论研究中的一个经典问题, 旨在寻找图中两结点之间的最短路径,即从某顶点出发,沿图的边到达另一顶点所经过的路径中,各边上权值之和最小的一条路径——最短路径。

最短路径计算根据原点的数量,可以分为单源点最短路径和全源最短路径。单源点的最短路径问题,是在带权有向图中,求指定的一个顶点到其余各点的最短路径;全源最短路径是在有向或无向带权图中,求出所有顶点对之间的最短路径。
在这里插入图片描述
求单源点最短路径经典算法有 Dijkstra 算法和 Bellman-Ford 算法。全源最短路径算法主要有 Floyd-Warshall 算法和 Johnson 算法。
在这里插入图片描述
最短路径计算从运算数据是否变化,可分为静态最短路计算和动态最短路计算。静态路径最短算法是外界环境不变,计算最短路径,主要有 Dijkstra 算法、A*(A Star)算法。动态路径最短路径计算,是在外界环境不断发生变化,即不能预测的情况下计算最短路径,如在游戏中敌人或障碍物不断移动的情况下,典型的有 D* 算法。

单源最短路径算法——Dijkstra 算法

Dijkstra 算法是典型的最短路径路由算法(为数据包从出发端到目的端选择路径的过程被称为“路由”,相应的算法为“路由算法”),用于计算有向图中一个顶点到其他所有顶点的最短路径。算法使用了广度优先搜索策略,解决非负权值有向图的单源最短路径问题,算法最终得到一个最短路径树。

  1. 问题描述

给定带权有向图 G 和源点 v,求从 v 到 G 中其余各项点的最短路径。图中权值非负。

  1. 问题分析

求从源点 v 到 G 中其余各项点的最短路径,一种可能的方法是枚举出所有路径,并计算出每条路径的长度,然后选择最短的一条。
在这里插入图片描述
从一个顶点 v 到任意顶点 vi 的最短路径不外乎两种可能,一是从 v 到 vi 只有一条路径,则它就是最短路径;二是从 v 到 vi 有多条路径,则需要在这 n 条路径中确定哪一条最短。
在这里插入图片描述
中间点:第八行二列单元格(v2,v3)。

  1. Dijkstra 算法思路描述

(a):设源点为 v0,源点 v0 的值为 0,v0 到其余点最短路径为 ∞,(路径值记录在各项点圆圈中)。
(b):首次从 v0 出发有3条直接的路径,分别到顶点 v2、v4、v5,长度分别为 10 、30 、100,修改相应顶点圈中的值,其中最短的路径对应的顶点为 v2;
(c):确定新的扩展点 u=v2(v2点变深色),以 u 做中间点:Dist(v0,v3)=Dist(v0,v2)+Dist(v2,v3)=60,比原先的值 ∞ 小,修改之。
此时,v0 到 v3、v4、v5的路径中(v0到v2的最短距离已经确认,就不再考虑v2点),最短的路径对应顶点为 v4,距离为 30 。
(d):u=v4,可以修改 v3、v5点的值。
(e):u=v3,无修改。
(f):u=v5,与 v0 有通路的顶点均已处理完毕。 在这里插入图片描述
把上面的计算过程的中间结果,记录下来。
在这里插入图片描述

  1. Dijkstra 算法具体步骤
    (1)在Dist中找最小值对应的点u;将u加入S。
    (2)以u做中间点,分别计算源点v0到其他各顶点的距离;若小于原来的距离,则修改Dist和Path数组。
    具体到数据结构,则可表述为:
    如果Dist[x]>Dist[u]+AdjMatrix[u][x],则Dist[x]=Dist[u]+AdjMatrix[u][x]。
    (3)重复步骤(1)(2)直到所有与v0有路径的顶点全部都加入到S中。

为什么 Dijkstra 算法的限定条件为非负权值?

讨论:当所有边权都为正时,由于不会存在一个距离更短的没扩展过的点 u,所以 v0 到这个 u 点的距离一旦确认就不会再被改变;若存在负权边,则会在扩展时产生更短的距离,有可能就迫害了已经更新的点的距离不会改变的性质。所以只有当所有边权都非负时,才能保证算法的正确性,故用 Dijkstra 求最短路的图不能有负权边。

  1. 算法代码实现
#include <stdio.h>
#define N 20							//图的最大顶点数
#define MAX 32767 
typedef struct							//图的邻接矩阵类型
{      
    int AdjMatrix[N][N]; 					//邻接矩阵AdjMatrix 
    int VexNum,ArcNum;					//顶点数,弧数
    //int vexs[N];						//存放顶点信息---如该顶点的下一个顶点
} AM_Graph;

void DisplayAM(AM_Graph g);				//输出邻接矩阵
void Dijkstra(AM_Graph g,int v0);			//Dijkstra算法从顶点v0到其余各顶点的最短路径
void DisplayPath(int dist[],int path[],int s[],int n,int v0);		//由path计算最短路径
void PPath(int path[],int i,int v0);

/*======================================
Dijkstra算法
函数功能:从源点到其余各顶点的最短路径
函数输入:图的邻接矩阵、源点v0
函数输出:无
=========================================*/
void Dijkstra(AM_Graph g,int v0)
{
    	int i,j;
    	int Dist[N];						//最短距离数组,记录v0到顶点j的最短距离
	int Path[N];					//中间点路径数组,记录顶点j的前一个顶点(j的前趋)
    	int S[N]; 						//最短路径顶点集,值 1:顶点入集,值0:顶点未入集
	int MinDis; 					//距v0的最小距离
    	int u;      					//距v0最小距离的顶点

    for (i=0;i<g.VexNum;i++) 
    {    
        Dist[i]=g.AdjMatrix[v0][i];		//距离初始化
        if (g.AdjMatrix[v0][i]<MAX)		//若v0到i有路径
             Path[i]=v0;				//i的前趋为v0
        else Path[i]=-1;				//i无前趋,标记为-1
        S[i]=0;					//S[]=0,表示顶点i不在S集中
    }

    S[v0]=1;						//首次,源点v0入S集
    for (i=0;i<g.VexNum;i++)                 
    {            
        MinDis=MAX;				//初始时设置到v0的最小距离为MAX
        u=-1;						//u为-1表示无对应顶点
        
		//在Dist中找最小值及对应顶点u
		for (j=0; j<g.VexNum; j++)
            if (S[j]==0 && Dist[j]<MinDis) 
            {         
                MinDis=Dist[j];    
                u=j;
            }

        if(MinDis!=MAX)   S[u]=1;//顶点u加入S集中
        else break;

		//以u做中间点,查看v0到其他各点的距离
        	for (j=0;j<g.VexNum;j++)
		{//选取不在S集中且与u连通的点j
            if (S[j]==0 && g.AdjMatrix[u][j]<MAX)
			{ //若[v0到j的距离]>[v0到u的距离+u到j的距离]
                if (Dist[j]>Dist[u]+g.AdjMatrix[u][j])   
                {    
                    //修改v0到j的距离
                    Dist[j]=Dist[u]+g.AdjMatrix[u][j]; 
                    Path[j]=u;//修改j的前趋点
                }
			}
		}
    }
    printf("输出最短路径:\n");
    DisplayPath(Dist,Path,S,g.VexNum,v0);		//输出最短路径
}

/*===========================================
函数功能:输出源点到其余各点的最短路径
函数输入:最短距离数组、路径数组、最短路径顶点集、顶点数、源点
函数输出:无
===========================================*/
void DisplayPath(int Dist[],int Path[],int S[],int n,int v0) 
{
    int i;
    for (i=0;i<n;i++)
        if (S[i]==1 && i!=v0) 					//在S集中的顶点才有路径输出
        {    
            printf("从%d到%d的最短路径长度为:%d",v0,i,Dist[i]);
            printf("\t路径为:%d—",v0);
            PPath(Path,i,v0);
            printf("%d\n",i);
        }
        else  
            printf("从%d到%d不存在路径\n",v0,i);
}

/*==================================
函数功能:打印源点到指定顶点的最短路径
函数输入:路径数组、终点、源点
函数输出:无
屏幕输出:最短路径
=====================================*/
void PPath(int Path[],int i,int v0) 
{
    int k=Path[i];
    if (k==v0)  return;
    else PPath(Path,k,v0);
    printf("%d—",k);
}

/*==================================
函数功能:输出邻接矩阵
函数输入:邻接矩阵
函数输出:无
屏幕输出:邻接矩阵
=====================================*/
void DisplayAM(AM_Graph g)
{
    int i,j;
    for (i=0;i<g.VexNum;i++)
    {
        for (j=0; j<g.VexNum; j++)
		{
            if (g.AdjMatrix[i][j]==MAX) printf("%4s","∞");
            else  printf("%4d",g.AdjMatrix[i][j]);
		}
        printf("\n");
    }
}

int main()
{
    int A[N][6]={{MAX,MAX,10 ,MAX,30 ,100},
                    {MAX,MAX,5  ,MAX,MAX,MAX},
                    {MAX,MAX,MAX,50 ,MAX,MAX},
                    {MAX,MAX,MAX,MAX,MAX,10 },
                    {MAX,MAX,MAX,20 ,MAX,60 },
                    {MAX,MAX,MAX,MAX,MAX,MAX}
                };
    AM_Graph g;						//定义邻接矩阵g
    g.VexNum=6;
    g.ArcNum=8;
    for (int i=0;i<g.VexNum;i++)			//给邻接矩阵赋值
        for (int j=0;j<g.VexNum;j++)
            g.AdjMatrix[i][j]=A[i][j];

    printf("有向图G的邻接矩阵:\n");
    DisplayAM(g);						//输出邻接矩阵
    int v0=1;							//设置起始点
    Dijkstra(g,v0);
    return 0; 
}

Dijkstra算法每次循环权值最小结点加入到 S集内;
每次结点加入S集,都需要更新Dist数组,将较小者加入Dist数组内;
算法使用循环实现,若使用递归呢?

各顶点对间最短路径算法——Floyd 算法

Floyd-Warshall 算法,又称为插点法,是一种用于寻找给定的加权图中多源点之间最短路径的算法。通常可以在任何图中使用,包括有向图、带负权边的图。

(1)问题描述:求带权图中各顶点对间的最短距离。
(2)问题分析:首先设置 Dist 矩阵和 Path 矩阵来记录顶点间的距离和对应的中间点。

在这里插入图片描述
步骤2:
(1)Path(-1)矩阵:顶点 2 到顶点 1,经过中间点 2,记为 Path(2,1)=2
Dist(-1)矩阵:顶点 2 到 1 路径长度为 5,记为 Dist(2,1)=5
(2)以顶点 0 为中间点
因为:Dist(2,1)=Dist(2,0)+Dist(0,1)=3+1=4<5
故有:
Dist(0)矩阵中:Dist(2,1)=4
Path(0)矩阵中:Path(2,1)=0
同理 Dist(2,3)=7;Path(2,3)=0
在这里插入图片描述
在这里插入图片描述

步骤 3 至 5 中 Dist 和 Path 矩阵的变化原理一样。最后将各顶点间的最短路径值以及经过的顶点路径列表给出。

  1. Floyd 算法描述
    在这里插入图片描述
  2. 程序实现
#include<stdio.h>
#define N 100					//图最大顶点个数
#define MAX 32767 
typedef struct					//图的邻接矩阵类型
{      
    int AdjMatrix[N][N]; 			//邻接矩阵
    int VexNum,ArcNum; 		//顶点数,弧数
    //int vexs[N];				//存放顶点信息——如该顶点的下一个顶点
} AM_Graph;
void DisplayAM(AM_Graph g);		//输出邻接矩阵
void Floyd(AM_Graph g);			//弗洛伊德算法——计算每对顶点之间的最短路径
void DisplayPath(int A[][N],int Path[][N],int n);		//输出路径
void PPath(int Path[][N],int i,int j);

int main()
{
    int A[N][4]={ {0,1,MAX,4},
                {MAX,0,9,2},
                {3,5,0,8},
                {MAX,MAX,6,0 },
			};

    AM_Graph g;				//定义邻接矩阵
    g.VexNum=4;
    g.ArcNum=8;				//4个顶点,8条边
	
	//给邻接矩阵赋值
    for (int i=0;i<g.VexNum;i++)
        for (int j=0;j<g.VexNum;j++)
            g.AdjMatrix[i][j]=A[i][j];

    printf("有向图G的邻接矩阵:\n");
    DisplayAM(g);				//输出邻接矩阵
    Floyd(g);					//调用算法并输出每两点之间的距离
    return 0; 
}

/*======================================
Floyd算法
函数功能:求出图中每对顶点之间的最短路径
函数输入:邻接矩阵
函数输出:无
=========================================*/
void Floyd(AM_Graph g)
{
    int i,j,k;
    int Dist[N][N],Path[N][N];//Dist矩阵记录各点间的最小距离,path矩阵记录路径的中间结点    
    
	//Dist和Path矩阵初始化
	for (i=0;i<g.VexNum;i++)
	{
        for (j=0;j<g.VexNum;j++)
        {    
            Dist[i][j]=g.AdjMatrix[i][j];		//Dist矩阵初始状态为邻接矩阵
            Path[i][j]=-1;					//无中间点时为-1
        }
	}

    	//分别以图中各点做中间点k,遍历Dist矩阵
	for (k=0; k<g.VexNum; k++)
    	{
        	//在Dist矩阵中查看经过k点到其他结点的距离有无改善(变小)
		for (i=0;i<g.VexNum;i++)
        	for (j=0;j<g.VexNum;j++)
		{
            	//经过k点的ij距离比原先小
			if (Dist[i][j]>(Dist[i][k]+Dist[k][j])) 
            { 
                Dist[i][j]=Dist[i][k]+Dist[k][j];		//修改顶点ij间的距离
                Path[i][j]=k;//记录中间点k
            }
		}
    }
    printf("\n输出最短路径:\n");
    DisplayPath(Dist,Path,g.VexNum);   			//输出最短路径
}
/*======================================
函数功能:输出各点的最短路径
函数输入:邻接矩阵、路径数组、顶点数
函数输出:无
======================================*/
void DisplayPath(int A[][N],int Path[][N],int n)
{
    int i,j;
    for (i=0;i<n;i++)
        for (j=0;j<n;j++) 
            if(A[i][j]==MAX)
            {
                if(i!=j)//交通路径中——到达自身结点本就是0距离
                    printf("从%d到%d没有路径\n",i,j);
            }
            else 
            {    
                printf("从%d到%d路径长度为:%d",i,j,A[i][j]);
                printf("\t路径为:");printf("%d-",i);PPath(Path,i,j);
                printf("%d\n",j);
            }
} 
/*======================================
函数功能:打印指定两个顶点间的路径
函数输入:路径数组、顶点1、顶点2
函数输出:无
屏幕输出:顶点间路径
========================================*/
void PPath(int Path[][N],int i,int j) 
{
    int k=Path[i][j];
    if (k==-1)  return;
    PPath(Path,i,k);
    printf("%d-",k);
    PPath(Path,k,j);
}

/*======================================
函数功能:输出邻接矩阵
函数输入:邻接矩阵
函数输出:无
屏幕输出:邻接矩阵
======================================*/
void DisplayAM(AM_Graph g)/*输出邻接矩阵*/
{
    int i,j;
    for (i=0;i<g.VexNum;i++)
    {
        for (j=0;j<g.VexNum;j++)
            if (g.AdjMatrix[i][j]==MAX)
                  printf("%4s","∞");
            else  printf("%4d",g.AdjMatrix[i][j]);
        printf("\n");
    }
}

循环变量 k ,从上一步矩阵取始点到该变量的距离,加该变量到终点的距离同上一步矩阵始点到终点距离比较,取较小值存于矩阵。

  1. 时间复杂度分析

每一个顶点分别要与其他 n-1 个顶点作边的长度比较,因此其时间复杂度为 O(n^3) 。

最短路径问题小结

在这里插入图片描述
Floyd算法时间复杂度比较高,不适合计算大量数据。Floyd算法不能直观反映出各个顶点之间最短路径序列的先后关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值