《算法笔记》读书记录DAY_44

CHAPTER_10  提高篇(4)——图算法专题

10.4.1最短路径

最短路径是图论中一个很经典的问题:给定图G(V,E),求一条从起点到终点的路径,使得这条路径上经过的所有边的边权之和最小。

如下图所示,在从V0到V6的路径中,V0—V1—V4—V6的路径的边权之和等于3,达到最小,因此这条路径称为V0—V6的最短路径。

对给定任意的图G(V,E)和起点S、终点T,求其最短路径的常用算法有:Dijkstra、Bellman-Ford、SPFA和Floyd。

10.4.2Dijkstra算法

Dijkstra(可读作迪杰斯特拉)用来解决单源最短路径问题,即给定图G和起点S,通过算法得到S到达其他每个顶点的最短距离。

Dijkstra的基本思想是对图G设置集合SET,存放已经被访问的顶点,然后每次从集合V-SET中选择与起点S的最短距离最小的一个顶点(记为u),访问并加入集合SET。之后,令顶点u为中介点,优化起点S与所有从u能到达的顶点v之间的最短距离。这样的操作只需n次(n为顶点个数),直到集合SET已包含所有顶点。

我们以0为起点使用Dijkstra算法,通过下面示例来理解:

扫描二维码关注公众号,回复: 13291314 查看本文章

SET最初为空,从起点0到各顶点的距离为{0,INF,INF,INF,INF,INF,INF,INF,INF},其中INF表示无穷大。现在选择具有最小距离值的顶点。拾取顶点0,将其包含在SET中。因此SET变为{0}。将0包括到SET后,更新其相邻顶点的距离值。与0相邻的顶点是1和7,距离值1和7更新为4和8。至此SET和V-SET集合更新如下图(绿色为SET,红色为V-SET):

从集合V-SET中选择与0的最短距离最小的顶点将其添加到SET。将1加入SET,所以SET现在变为{0,1}。顶点1与其相邻的顶点2的距离值为1,顶点0到顶点2的距离值更新为12。至此SET和V-SET集合更新如下图(绿色为SET,红色为V-SET):

从集合V-SET中选择与0的最短距离最小的顶点将其添加到SET。将7加入SET,因此SET现在变为{0,1,7}。顶点7与顶点6和8相邻,顶点0到顶点6的距离值更新为9,顶点0到顶点8的距离值更新为15。至此SET和V-SET集合更新如下图(绿色为SET,红色为V-SET):

从集合V-SET中选择与0的最短距离最小的顶点将其添加到SET。将6加入SET,因此SET现在变为{0,1,7,6}。顶点6与顶点5和8相邻,顶点0到顶点5的距离值更新为11,顶点0到顶点8的距离值还是15。至此SET和V-SET集合更新如下图(绿色为SET,红色为V-SET):

 

接下来在V-SET中,与0最近的一个点是5,然后将顶点5加入SET......通过重复上述步骤,直到SET已经包含所有顶点(即所有顶点颜色变绿),我们得到如下最短路径树:

通过这棵最短路径树,我们得到了从起点0出发,到达所有顶点的最短路径和路径值。

Dijkstra算法的具体实现

通过上面的介绍,我们已经了解Dijkstra算法的流程。但是要实现代码,我们还要解决两个关键问题:如何实现集合SET?如何实现起点S到达顶点Vi(0<=i<=N-1)的最短距离?

(1)集合SET可以用一个bool数组vis[]来实现,即当vis[i]==true时表示顶点Vi已经访问(即Vi在SET中);反之Vi不在SET中。

(2)令int型数组d[]表示起点S到顶点Vi的最短距离。初始时除了起点s的d[s]=0,其他顶点都赋值为INF。INF表示无限大,可以用16进制0x3fffffff表示INF。

实现代码(邻接表):

#define INF 0x3fffffff

const int maxn=1001;          //最大顶点数 
int n;                        //顶点数
int d[maxn];                  //记录最短路径 
bool vis[maxv]={0};           //实现SET 

struct node {
	int v,distance;           //v为边的终点,distance为边权 
};
vector<node> Adj[maxn];       //邻接表 

void Dijkstra() {
	fill(d,d+n,INF);
	d[s]=0;                                   //初始化d[]
	for(int i=0;i<n;i++) {
		int u=-1,MIN=INF;                     //MIN存放最小的INF
		for(int j=0;j<n;j++) {                //找到未访问顶点中d[]最小的 
			if(vis[j]==0&&d[j]<MIN) {
				u=j;
				MIN=d[j];
			} 
		}
		//找不到小于INF的d[u],说明剩下的顶点和起点S不连通
		if(u==-1)
			return;
		vis[u]=1;                              //标记u为已访问
		for(int j=0;j<Adj[u].size();j++) {     //获得每个u能到达的顶点 
			int v=Adj[u][j].v;
			if(vis[v]==0&&d[u]+Adj[u][j].distance<d[v]) {
				d[v]=d[u]+Adj[u][j].distance;  //优化d[v] 
			}
		} 
	} 
}

有两点需要注意:

(1)Dijkstra算法只适用于边权都是非负的情况,如果边权出现负数,最好使用SPFA算法;

(2)上面算法实现了从起点S到每个顶点的单源最短路径值,但是没有解决从S到Vi的最短路径是哪一条的问题。要解决这个问题,我们需要设置一个数组pre[],令pre[v]表示从S到V的最短路径上V的前一个顶点,同时时令pre[s]=s。这样,我们只需在上面优化d[v]的部分,加入pre[v]=u语句。这样最终记录起点S到V的最短路径上的前一个顶点u,在由pre[u]=w获得u的前一个顶点w,如此递推至起点pre[s]=s,我们就获得了S到V最短路径上的所有顶点。

猜你喜欢

转载自blog.csdn.net/jgsecurity/article/details/121068560