最短路算法(3种算法)

1.最短路

最短路,顾名思义,最短的路径。我们把边带有权值的图称为带权图。边的权值可以理解为两点之间的距离。一张图中任意两点之间会有不同的路径相连。最短路径就是指连接两点的这些路径中最短的一条。我们有四种算法可以有效地解决最短路径问题,但是当出现负边权时,有些算法不适用。

2. Floyd算法(解决多源最短路径):时间复杂度O(n^3), 空间复杂度(n^2)

推荐一篇博客。写的非常易懂:Floyd

  • 分类:多源最短路径算法。
  • 作用:1.求最短路。 2.判断一张图中的两点是否相连。
  • 优点:实现极为简单
  • 缺点:只有数据规模较小且时空复杂度都允许时才可以使用
    = 思想:3层循环,第一层枚举中间点k,第二层与第三层枚举两个端点i,j。若有dis[i][j] > dis[i][k] + dis[k][j] 则把dis[i][j]更新成dis[i][k] + dis[k][j](原理还是很好理解的)。
  • 实现:
    (a)初始化:点i,j如果有边相连,则dis[i][j] = w[i][j]。如果不相连,则dis[i][j] = 0x7fffffff(int极限值),表示两点不相连(或认为相隔很远)。
    (b)算法代码:
for(int k = 1; k <= n; k++)  //枚举中间点(必须放最外层)
  for(int i = 1; i <= n; i++)  //枚举端点i
    if(i != k)
      for(int j = 1; j <= n; j++)  //枚举端点j
        if(i != j && j != k && dis[i][j] > dis[i][k] + dis[k][j])
          dis[i][j] = dis[i][k] + dis[k][j];

完整代码

 1     #include 
 2     int main()  
 3     {  
 4     int e[10][10],k,i,j,n,m,t1,t2,t3;  
 5     int inf=99999999; //用inf(infinity的缩写)存储一个我们认为的正无穷值
 6     //读入n和m,n表示顶点个数,m表示边的条数
 7         scanf("%d %d",&n,&m);  
 8     //初始化
 9     for(i=1;i<=n;i++)  
10     for(j=1;j<=n;j++)  
11     if(i==j) e[i][j]=0;    
12     else e[i][j]=inf;  
13     //读入边
14     for(i=1;i<=m;i++)  
15         {  
16             scanf("%d %d %d",&t1,&t2,&t3);  
17             e[t1][t2]=t3;  
18         }  
19     //Floyd-Warshall算法核心语句
20     for(k=1;k<=n;k++)  
21     for(i=1;i<=n;i++)  
22     for(j=1;j<=n;j++)  
23     if(e[i][j]>e[i][k]+e[k][j] )   
24                         e[i][j]=e[i][k]+e[k][j];  
25     //输出最终的结果
26     for(i=1;i<=n;i++)  
27         {  
28     for(j=1;j<=n;j++)  
29             {  
30                 printf("%10d",e[i][j]);  
31             }  
32             printf("\n");  
33         }  
34     return 0;  
35     }  

Floyd-Warshall算法不能解决带有“负权回路”(或者叫“负权环”)的图,因为带有“负权回路”的图没有最短路。

  • 如果是一个没有边权的图,把相连的两点间距离设为dis[i][j] = true,不相连的两点设为dis[i][j] = false,用Floyed算法的变形:
for(int k = 1; k <= n; k++)  //枚举中间点
  for(int i = 1; i <= n; i++)  //枚举端点i
    if(i != k)
      for(int j = 1; j <= n; j++)  //枚举端点j
        if(i != j && j != k)
          dis[i][j] = dis[i][j] || (dis[i][k] && dis[k][j]);(判断是否相连)

3. 迪杰斯特拉算法(解决单源最短路径)

  • 分类:
    单源最短路径算法。
  • 适用于:
    稠密图(侧重对点的处理)。
  • 时间复杂度:
    1.朴素:O(N^2)
    2.堆优化:O(n * logn)
  • 缺点:
    不能处理存在负边权的情况。
  • 算法思想:
    把点分为两类,一类是已经确定最短路径的点,称之为“标记点”;另一类则是还未确定最短路径的点,称之为“未标记点”。如果要求出一个点的最短路径,就是把这个“未标记点”变成“标记点”,从起点到“未标记点”的最短路径上的中转点在这个时刻只能是“标记点”。
    Dijkstra的算法思想,就是一开始将起点到起点的距离标记为0,而后进行n次循环,每次找出一个到起点距离dis[u]最短的点u,将它从“未标记点”变为“标记的点”。随后枚举所有的“未标记的点”vi,如果以此“标记的点”为中转点到达“未标记的点”vi的路径dis[u] + w[u][vi]更短的话,这将它作为vi的“更短路径”dis[vi](此时还不确定是不是vi的最短路径)。
    就这样,我们每找到一个“标记的点”,就尝试着用它修改其他所有的:“未标记的点”,故每一个终点一定能被它的最后一个中转点所修改,而求得最短路径。
  • 优化思想:
    利用堆(优先队列),把冗杂的枚举查找变成更加快速的堆直接弹出。
  • 实现思路:
    (a)初始化:dis[v] = oo (v != s); dis[s] = 0; pre[s] = 0;
    (b)for(int i = 1; i <= n; i++)
    1.在没有被访问过的点中找一个顶点u使得dis[u]是最小的。
    2.u标记为已确定最短路径。
    3.for与u相连的每个未确定最短路径的顶点v。
(伪代码)
if(dis[u] + w[u][v] < dis[v])
  {
    dis[v] = dis[u] + w[u][v];
    pre[v] = u;
  }
#define inf 99999999
/***构建邻接矩阵edge[][],且1为源点***/
for(i = 1; i <= n; i++) dst[i] = edge[1][s]for(i = 1; i <= n; i++) book[i] = 0;
book[1] = 1;
for(i = 1; i <= n-1; i++){
	//找到离源点最近的顶点u,称它为新中心点
	min = inf;
	for(j = 1; j <= n; j++){
		if(book[j] == 0 && dst[j] < min){
			min = dst[j];
			u = j;
		}
	}
	book[u] = 1;
	//更新最短路径数组
	for(k = 1; k <= n; k++){
		if(edge[u][k] < inf && book[k] == 0){
			if(dst[k] > dst[u] + edge[u][k])
				dst[k] = dst[u] + edge[u][k];			
		}
	}
}

4. Bellman算法

  • 分类:
    单源最短路径算法。
  • 适用于:
    稀疏图(侧重于对边的处理)。
  • 优点:
    可以求出存在负边权情况下的最短路径。
  • 缺点:
    无法解决存在负权回路的情况。
  • 时间复杂度:
    O(NE),N是顶点数,E是边数。(因为和边有关,所以不适于稠密图)
  • 算法思想:
    很简单。一开始认为起点是“标记点”(dis[1] = 0),每一次都枚举所有的边,必然会有一些边,连接着“未标记的点”和“已标记的点”。因此每次都能用所有的“已标记的点”去修改所有的“未标记的点”,每次循环也必然会有至少一个“未标记的点”变为“已标记的点”。
  • 算法实现:
    初始化:dis[s] = 0; dis[v] = oo(v != s); pre[s] = 0;
(伪代码)
for(int i = 1; i <= n - 1; i++)
  for(int j = 1; j <= E; j++)  //注意要枚举所有边,不能枚举点
    if(dis[u] + w[j] < dis[v])  //u, v分别是这条边连接的两个点
      {
        dis[v] = dis[u] + w[j]
        pre[v] = u;
      }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值