算法导论——单元最短路径

  单源最短路径问题是指,给定一个图G=(V,E),希望找到从给定源结点s到每个节点v的最短路径。单源最短路径问题可以用来解决很多最短路径的变体。

单目的地最短路径问题:找到从每个结点v到给定目的地结点t的最短路径。将图的每条边翻转,这个问题可以转换为单源最短路径问题。

单结点对最短路径问题:找到从给定结点u到给定结点v的最短路径。如果已经解决了u的单元最短路径问题,则该问题已经解决。

  在单源最短路径问题中,根据图的性质不同有不同的解决方案。主要的性质有:是有向图还是无向图,是权重图还是单位图,有无负权重的边,是否有环。

Dijkstra算法

  Dijkstra算法解决的是非负权重有向图上的单源最短路径问题。算法逻辑为维持一个结点集合S,集合中每个结点之间的最短路径已经被找到,重复从集合V-S中选择与源结点r之间最短路径估计最小的结点u加入到S,然后对所有从u出发的边检查r到u的距离加上该边的距离是否小于该边另一个结点原本的最短路径估计。

如图(a)初始状态,s是源结点,加入到集合S中

(b)检查s出发的路径(s,t)和(s,y)更新y和t的最短路径估计

(c)在剩下结点中选择最短路径估计最小的结点y,然后检查从y出发的路径(y,t)+(s,y)=8<10,更新t的距离为8,同样对x和z也进行同样的操作,结束后y加入到S中

(d)接下来选择z结点,(z,s)+7=14>s,所以s保持不变,而(z,x)+6=13<14,x的值更新为13,然后将z加入到S中。

(e)-(f)的操作同上,不再做详细描述。最终所有点都加入到了S中。

为了完成算法,我们需要存储每个结点的最短路径估计和他们的前驱结点来输出最终路径,同时需要一个最小优先队列来保存V-S中结点的最短路径估计排序,否则需要遍历V-S中的结点来选择出最短的那个。直接遍历所有边的算法复杂度为O(V^2),若使用最小二叉堆来存储优先队列则复杂度可以降低为O((V+E)lgV)。

 

 1 #include<stdio.h>
 2 using namespace std;
 3 #define SIZE 10
 4 #define INFI 10000
 5 
 6 int G[SIZE][SIZE];//邻接矩阵,参数初始化略
 7 int dist[SIZE];//与根间的距离估计
 8 bool visit[SIZE];//是否被访问过
 9 
10 void dijkstra(int root){
11     int i,j;
12     int min;
13     for(i = 0; i < SIZE; i++){
14         dist[i] = INFI;
15         visit[i] = false;
16     }
17     dist[root] = 0;
18     for(i = 0; i <SIZE; i++){
19         min = 0;//寻找剩余点中距离根最近的点
20         for(j = 1; j < SIZE;j++){
21             if(!visit[j] && dist[j] < dist[min]){
22                 min = j;
23             }
24         }
25         for(j = 0; j < SIZE; j++){
26             if(!visit[j] && G[min][j] + dist[min] < dist[j])
27                 dist[j] = G[min][j] + dist[min];//检查是否需要更新最短路径
28         }
29         visit[min] = true;
30     }
31 }

Bellman-Ford算法

  该算法可以解决负权重边的图,算法返回一个布尔值,表明是否存在一个从源节点可以到达的权重为负的环路,若存在则算法将告诉我们不存在解决方案,因为这个环路的存在会导致出现距离为负无穷的点。

  算法需要进行|V|-1次循环,每次循环按照同样的顺序对所有边进行松弛,结束后检查是否存在权重为负的环路。算法复杂度为O(VE),因此该算法在处理密集图时效率会降低,总体效率不如dijkstra算法。

如图所示每一次松弛边的顺序都是(t,x) (t,y) (t,z) (y,x) (y,z) (z,x) (z,s) (s,t) (s,y)。源结点为s,加了阴影的边表示前驱路径。

(a)       更新源结点的距离为0

(b)       按照顺序,仅(s,t) (s,y)可以更新结点的值,t=6 y=7

(c)       (t,z) (y,x)可以更新z和x的值

(d)       (x,t)可以更新t的值

(e)       本次循环没有可以更新的值,检查不存在权值为负的环返回TRUE

 1 #include<stdio.h>
 2 using namespace std;
 3 #define SIZE 10
 4 #define INFI 10000
 5 
 6 int G[SIZE][SIZE];//邻接矩阵,参数初始化略
 7 int dist[SIZE];//与根间的距离估计
 8 int p[SIZE];//前驱结点
 9 
10 void bellmanFord(int root){
11     int i, j, k;
12     for(i = 0; i < SIZE; i++){
13         dist[i] = INFI;
14     }
15     dist[root] = 0;
16     for(i = 0; i < SIZE - 1; i++){
17         for(j = 0; j < SIZE; j++){
18             if(dist[j] == INFI)
19                 continue;//结点无法从源结点到达则先跳过
20             for(k = 0; k < SIZE; k++){
21                 if(G[j][k] != 0 && G[j][k] + dist[j] < dist[k])
22                     dist[k] = G[j][k] + dist[j];//松弛路径
23             }
24         }
25     }
26     for(i = 0; i < SIZE; i++){
27         for(j = 0; j < SIZE; j++){
28             if(G[i][j] != 0 && dist[i] > dist[j] + G[i][j])
29                 return false;//检查有无权重为负的环
30         }
31     }
32 }

差分约束和最短路径

  在线性规划问题中,通常会给定一个m*n的矩阵A、一个m维的向量b和一个n维向量c,希望找到一个n维向量x,使得在有Ax≤b给定的m个约束条件下优化目标函数

使目标函数值最大。在一个差分约束系统中,线性规划矩阵A的每一个行包括一个1和一个-1,其他所有项都是0,每个限制条件变为不等式xj-xi≤bk

如图所示的矩阵和向量可以表示为8个简单不等式,要求出一组可行解。以x作为结点,后面的约束值作为路径权重,可以画出一张约束图

 

可以通过对约束图设定一个源结点,如v0求出最短路径解来获得一组可行解。因为含有负值权重,所以需要通过Bellman-Ford算法来进行求解。

posted @ 2018-08-12 14:29  GrayWind  阅读(1166)  评论(0编辑  收藏  举报