【图(中)】最短路径问题

1、最短路径问题的抽象

  • 在网络中,求两个不同顶点之间的所有路径中,边的权值之和最小的那一条路径
    • 这条路径就是两点之间的最短路径(Shortest Path)
    • 第一个顶点为源点(Source)
    • 最后一个顶点为终点(Destination)

2、问题分类

  • 单源最短路径问题:从某固定源点出发,求其到所有其他顶点的最短路径
    • (有向)无权图
    • (有向)有权图
  • 多源最短路径问题:求任意两顶点间的最短路径

3、无权图的单源最短路算法

  • 按照递增(非递减)的顺序找出到各个顶点的最短路
    【图(中)】最短路径问题_第1张图片

无权图的单源最短路算法

void Unweighted(Vertex S)
{
	Enqueue(S, Q);
	while (!IsEmpty(Q))
	{
		V = Dequeue(Q);
		for (V 的每个邻接点W)
			if (dist[W] == -1)
			{
				dist[W] = dist[V] + 1;
				path[W] = V;
				Enqueue(W, Q);
			}
	}
}

dist[W]:S到W的最短距离,初始化时,初始化为不可能的数,这里初始化为-1
dist[S]:0
path[W]:S到W的路上经过的某顶点,path[W] = V表示S到W的路径上,W上一步为V。顺着path数组顺着向前推,就是路径,用栈可以得到顺序的路径。

下图所示,源点为V3,经过上面的算法,每一个顶点都被标记的到源点的距离,从每一个顶点反向推可以得到源点到该点最短路径经过的顶点。
【图(中)】最短路径问题_第2张图片

时间复杂度:

如果有|V|个顶点和|E|条边的图用邻接表存储,则
T = O( |V| + |E| )
每个顶点入栈一次,出栈一次,每条边被访问一次

4、有权图的单源最短路算法(Dijkstra算法)

【图(中)】最短路径问题_第3张图片
这里不考录负值圈

按照递增的顺序找出到各个顶点的最短路

Dijkstra 算法

  • 令S={源点s + 已经确定了最短路径的顶点vi}(先将路径较小的顶点收录进来,如剩下未收录的顶点中路径分别为3,5,2,无穷大,则下一步将距离为2的收录进来)
  • 对任一未收录的顶点v,定义dist[v]为s到v的最短路径长度,但该路径仅经过S中的顶点。即路径在这里插入图片描述的最小长度(注意:这里的最短路径不是最终的最短路径,随着其他顶点加进来,dist[v]会逐渐变小,最终成为最终的最短路径)
  • 若路径是按照递增(非递减)的顺序生成的,则
    • 真正的最短路必须只经过S中的顶点(为什么?)
    • 每次从未收录的顶点中选一个dist最小的收录(贪心)
    • 增加一个v进入S,可能影响另外一个w的dist值!
    • dist[w] = min{dist[w], dist[v] + 的权重}
  1. 真正的最短路必须只经过S中的顶点,原因如下:
    现在假设在集合S之外存在一个顶点w,使得s经w到到v的距离小于s直接到v的距离,则s到w的距离一定小于s到v的距离。下一步,我们要收录顶点v,s到w的距离小于s到v的距离,顶点w应该在v之前被收录到集合S中了,自相矛盾。

  2. 如果收录v使得s到w的路径变短,则:s到w的路径一定经过v,并且v到w有一条边,原因如下:
    在这里插入图片描述
    现在假设v到w的路径上还有一点a,则s到a的路径长度一定大于s到v的路径长度,由于若路径是按照递增(非递减)的顺序生成的,所以a的收录应该在点v之后,自相矛盾。

  3. Dijkstra算法中的dist应该如何初始化?
    如果s到w有直接的边,则dist[w]=的权重;否则dist[w]定义为正无穷

//不能解决有负边的情况
void Dijkstra(Vertex s)
{
	while (1)
	{
		V = 未收录顶点中dist最小者;
		if (这样的V不存在)
			break;
		collected[V] = true;
		for (V 的每个邻接点W)
			if (collected[W] == false)
				if (dist[V] + E<V, W> < dist[W])
				{
					dist[W] = dist[V] + E<V, W>;
					path[W] = V;
				}
	}
}

【图(中)】最短路径问题_第4张图片

有权图的单源最短路算法时间复杂度

  • 方法1:直接扫描所有未收录顶点– O( |V| )

    • T = O( |V|2 + |E| )
    • 对于稠密图效果好
  • 方法2:将dist存在最小堆中– O( log|V| )

    • 更新dist[w]的值– O( log|V| )
    • T = O( |V| log|V| + |E| log|V| ) = O( |E| log|V| )
    • 对于稀疏图效果好

5、多源最短路算法

  • 方法1:直接将单源最短路算法调用|V|遍
    • T = O( |V|3 + |E|*|V|)
    • 对于稀疏图效果好
  • Floyd算法
    • T = O( |V|3 )
    • 对于稠密图效果好

Floyd 算法

  • Dk[i][j] = 路径{ i → { l<=k } → j }的最小长度,i到j的最短路径,只经过编号小于等于k的顶点。

  • D0, D1, …, D|V|-1[i][j]即给出了i到j的真正最短距离

  • 最初的D-1是什么?

  • 当Dk-1已经完成,递推到Dk时:

    • 或者k不属于最短路径{ i → { l<=k } → j },则Dk= Dk-1
    • 或者k属于最短路径{ i → { l<=k } → j },则该路径必定由两段最短路径组成: Dk[i][j]=Dk-1[i][k]+Dk-1[k][j]

i到j的顶点路径经过顶点k时,i到j的顶点路径包括两段,i到k,k到j,这两段距离中间都不包含k,所以应该在k-1步的时候已经求出。

D矩阵应该初始化:带权的邻接矩阵,对角元是0;如果i和j之间有直接边相连,初始化为边的权重,没有直接的边,D[i][j]应该定义为正无穷

void Floyd()
{
	for (i = 0; i < N; i++)
		for (j = 0; j < N; j++)
		{//初始化就是邻接矩阵,有相邻节点初始化为权重,
		//没有直接边相连,初始化为正无穷,对角元素为0,就是邻接矩阵
			D[i][j] = G[i][j];
			path[i][j] = -1;
		}
	for (k = 0; k < N; k++)
		for (i = 0; i < N; i++)
			for (j = 0; j < N; j++)
				if (D[i][k] + D[k][j] < D[i][j])
				{
					D[i][j] = D[i][k] + D[k][j];
					path[i][j] = k;
				}
}

根据path打印路径,path[i][j]=k;表示从结点i到结点j要经过结点k,递归调用,从节点i到节点k要经过那些顶点,从节点k到节点j要经过哪些顶点,最终得到路径。

你可能感兴趣的:(数据结构,数据结构心法指南)