数据结构——最短路径问题

——本节内容为Bilibili王道考研《数据结构》P61~64视频内容笔记。


目录

一、最小生成树

1.最小生成树概念

(1)定义

(2)性质

2.最小生成树算法

(1)Prim算法

(2)Kruskal算法

二、最短路径算法

1.问题

2.BFS算法(无权图)

(1)无权图

(2)代码实现

(3)代码解释

(4)图示(以2顶点开始)

(5)补充

3.Dijkstra算法

(1)衍生

(2)示例

(3)补充

4.Floyd算法

(1)衍生

(2)算法思想

(3)例子

(4)代码实现

(5)复杂度

(6)补充


一、最小生成树

1.最小生成树概念

(1)定义

        对于一个带权连通无向图G=(V,E),生成树不同,每棵树的权(即树中所有边上的权值之和)也可能不同。设R为G的所有生成树的集合,若T为R中边的权值之和最小的生成树,则T称为G的最小生成树(Minimum-Spanning-Tree,MST)。

(2)性质

        ①最小生成树可能有多个,但边的权值之和总是唯一且最小的;

        ②最小生成树的边数=顶点数-1;砍掉一条则不连通,增加一条边则会出现回路;

        ③如果一个连通图本身就是一棵树,则其最小生成树就是它本身;

        ④只有连通图才有生成树,非连通图只有生成森林。


2.最小生成树算法

(1)Prim算法

        ①思路:从某一个顶点开始构建生成树;每次将代价最小的新顶点纳入生成树,直到所有顶点都纳入为止;

        ②时间复杂度O(|V|^{2}),适用于边稠密图;


(2)Kruskal算法

        ①思路:每次选择一条权值最小的边,使这条边的两头连通(原本已经连通的就不选),直到所有结点都连通;

        ②时间复杂度O(|E|log_{2}|E|),适用于边稀疏图;


二、最短路径算法

1.问题

(1)只有单独一个源头,从该源头结点出发,到达其他任意一个顶点所需的最短路径;

(2)每对顶点之间的最短路径;


2.BFS算法(无权图)

(1)无权图

        无权图可以视为一种特殊的带权图,只是每条边的权值都为1;

(2)代码实现

void BFS_MIN_Distance(MGraph G, int u)					//求顶点u到其他顶点的最短路径
{
	for (int i = 0; i < G.vexnum; ++i)					//d[i]表示从u到i结点的最短路径
	{	
		d[i] = INT_MAX;									//初始化最短路径
		path[i] = -1;									//最短路径从哪个顶点过来
	}
	d[u] = 0;
	visited[u] = true;
	Enqueue(Q, u);
	while (!isEmpty(Q))									//BFS算法主过程
	{
		Dequeue(Q, u);									//队头元素u出队
		for(int w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w))
			if (!visited[w])							//w为u的尚未访问的邻接结点
			{
				d[w] = d[u] + 1;						//路径长度+1
				path[w] = u;							//最短路径应该从u到w
				visited[w] = true;						//设已访问标记
				Enqueue(Q, w);							//顶点w入队
			}
	}
}

(3)代码解释

        ①需创建两个数组d[]、path[]分别代表最短路径和前驱顶点;

        ②由BFS算法改造而来。先对新增的两个数组进行初始化,再将BFS算法中的访问函数visit进行改造,变为:记录最短路径并更改访问顶点的前驱顶点;

(4)图示(以2顶点开始)

(5)补充

        ①如上图的例子,2到8的最短路径长度=d[8]=3;通过path数组可知,2到8的最短路径为:
2->6->7->8;

        ②这样生成的广度优先生成树一定是以2为根的,高度最小的生成树;


3.Dijkstra算法

(1)衍生

        为解决BFS算法局限于不带权图的情况,对于带权图的最短路径,采用Dijkstra算法。

(2)示例

        ①初始:从V0开始,初始化三个数组信息如下:

        其中,final数组标记各顶点是否已找到最短路径,初始化时,因为V0到V0的路径为0,也就意味着我们已经找到V0的最短路径,所以将final[0]标记为true;

        dist数组标记最短路径长度,若能够找到一个暂时的最短路径长度,就标记为该长度,否则标记为∞;

        path数组标记为路径上的直接前驱,初始化均为-1,V0没有直接前驱,就设为-1即可。

        ②第1轮:循环遍历所有结点,找到还没确定最短路径,且dist最小的顶点Vi,令final[i]=true;然后检查所有邻接自Vi的顶点,若其final值为false,则更新dist和path信息

         首先能找到final为false中最小的dist顶点V4,令final[4]为true,即表示已经可以确定V4的最短路径为5,并且其直接前驱为V0;

        判断邻接第一个final为false的V1,从V0到V4有一条长度为5的路径,而又可以找到一条V4到V1为3的路径,二者之和比最先得到的10要小,此时把dist[1]改为8,path[1]改为4;

        判断邻接第二个final为false的V2,从V4到V2有一条长度为9的路径,加上V0到V4的5,可以暂时找到V2的最短路径为14,修改dist[2]=14,path[2]=4;

        判断邻接第三个final为false的V3,从V4到V3有一条长度为2的路径,加上V0到V4的5,可以暂时找到V3的最短路径为7,修改dist[3]=7,path[3]=4;

        ③第2轮:与第1轮思路一样

         首先能找到final为false中最小的dist顶点V3,令final[3]为true,即表示已经可以确定V3的最短路径为7,并且其直接前驱为V4;

        判断邻接唯一一个final为false的V2,V3的最短路径为7,加上V3到V2的路径6,等于13,小于14,所以修改dist[2]=13,path[2]=3;

        ④第3轮:与第1轮思路一样

         首先能找到final为false中最小的dist顶点V1,令final[1]为true,即表示已经可以确定V1的最短路径为8,并且其直接前驱为V4;

        判断邻接唯一一个final为false的V2,V1的最短路径为8,加上V1到V2的路径1等于9,小于13,修改dist[2]=9,path[2]=1;

        ⑤第4轮:操作同上

        此时没有final为false的顶点,算法结束。

(3)补充

        ①V0到V2的最短路径长度为dist[2]=9,且通过path[]可知,V0到V2的最短路径为V2<-V1<-V4<-V0;

        ②时间复杂度:O(|V|^{2});

        ③对于负权值带权图,Dijkstra算法并不适用,如下:


4.Floyd算法

(1)衍生

        求出每一对顶点之间的最短路径;

(2)算法思想

        动态规划思想,将问题的求解分为多个阶段;对于n个顶点的图G,求任意一对顶点Vi->Vj之间的最短路径可分为如下几个阶段:

        #初始:不允许在其他顶点中转,最短路径是?

        #0:若允许在V0中转,最短路径是?

        #1:若允许在V0、V1中转,最短路径是?

        #2:若允许在V0、V1、V2中转,最短路径是?

        ......

        #3:若允许在V0、V1、V2......Vn-1中转,最短路径是?

(3)例子

        #初始:

         #0:

        #0判断完后(#1):

 

        #1判断完后(#2):

 

        #2判断完后,可由最终矩阵判断最短路径已经具体路径经过顶点:

(4)代码实现

//......准备工作,根据图的信息初始化矩阵A和path
for (int k = 0; k < n; k++)						//考虑以Vk作为中转点
{
	for (int i = 0; i < n; i++)					//遍历整个矩阵,i为行号,j为列号
	{
		for (int j = 0; j < n; j++)
		{
			if (A[i][j] > A[i][k] + A[k][j])	//以Vk为中转点的路径更短
			{
				A[i][j] = A[i][k] + A[k][j];	//更新最短路径长度
				path[i][j] = k;					//中转点
			}
		}
	}
}

(5)复杂度

        ①时间复杂度:O(|V|^{3})

        ②空间复杂度:O(|V|^{2})

(6)补充

        ①在找完整路径时,需对path矩阵进行彻底的分解才能找到最终的具体最短路径,如下:

        ②虽然Floyd算法可以解决带负权值的图,但不能解决带负权回路的图,如下:

猜你喜欢

转载自blog.csdn.net/weixin_64084604/article/details/128383161
今日推荐