单源最短路径复习--Dijkstra算法和Floyd算法

昨天复习了一下单源最短路径问题,今天总结一下。

解决单源最短路径问题,我们熟知的算法首先就是Dijkstra算法了。Dijkstra算法的核心就是贪心思想。我在以前的博客中也写过这个算法:图的拓扑排序、关键路径、最短路径算法 – C++实现,现在看以前的博客,我的代码思路还是很清晰的。Dijkstra算法可以求出某一点到其他所有点的最短路径,本文还将介绍一种可求出所有点对的最短路径的算法——Floyd算法。

Dijkstra算法

Dijkstra算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,知道扩展到终点为之。Dijkstra算法的要求是图中不存在负权边。因为Dijkstra算法基于贪心策略,它是短视的。如果存在某个路径上有负权边,可能绕了几圈得到的结果甚至是更优的,所以Dijkstra算法在有负权边的图应用上是失败的。

Dijkstra算法的具体解释我就不说了,如果不明白概念可参考这篇博客的概念解释:最短路径—Dijkstra算法和Floyd算法

本文所有测试用例所用graph就是下面这幅图片中的graph:

下面给出我的代码:

#include <iostream>
#include <vector>
#include <iomanip>
#include <limits.h>

const int NUM_VERTICES = 5;  //A B C D E

void print_solution(std::vector<int>& dist, std::vector<int>& path)
{
    for(auto i : dist)
        std::cout<<std::setw(3)<<i<<' ';
    std::cout<<std::endl;
    for(auto i : path)
        std::cout<<std::setw(3)<<i<<' ';
    std::cout<<std::endl;
}

void dijkstra(std::vector<std::vector<int>>& graph, int source)
{
 std::vector<int> dist(NUM_VERTICES, 0), path(NUM_VERTICES, -1);
    for(int i=1; i<NUM_VERTICES; ++i){
        dist[i] = graph[source][i] == 0 ? INT_MAX : graph[source][i];
        path[i] = source;
    }   

    std::vector<bool> visited(NUM_VERTICES, false);
    visited[source] = true;

    for(int i=0; i<NUM_VERTICES-1; ++i){
        int min = INT_MAX;
        int min_index = -1;

        for(int j=0; j<NUM_VERTICES; ++j){
            if(!visited[j] && dist[j] < min){
                min = dist[j];
                min_index = j;
            }
        }
        visited[min_index] = true;

 for(int k=0; k<NUM_VERTICES; ++k){
            int weight = graph[min_index][k] == 0 ? INT_MAX : graph[min_index][k];
            if(!visited[k] && weight != INT_MAX
                           && dist[min_index] != INT_MAX
                           && dist[min_index]+weight < dist[k]){
                dist[k] = dist[min_index] + weight;
                path[k] = min_index;
            }
        }
    }

    print_solution(dist, path);
}

int main()
{
    std::vector<std::vector<int>>
    graph = {{0,  10, 0,  30, 100}, //A
             {0,  0,  50, 0,  0  }, //B
             {0,  0,  0,  0,  10 }, //C
             {0,  0,  20, 0,  60 }, //D
             {0,  0,  0,  0,  0  }};//E
          //  A   B   C   D   E
    dijkstra(graph, 0);  //param 0 means vertex 'A'
    return 0;
}                    

输出结果:
这里写图片描述

Dijkstra算法的时间复杂度是 O(|E|+|V|2|)=O(V2) (参考:Dijkstra算法时间复杂度)。对于稠密的图来说,|E| 就是|V|^2,所以Dijsstra算法中遍历dist[min_index]+weigth那个循环加上最外层循环时间复杂度为 θ(|V|2) ,对于稠密图,这就是最优的了,总的时间复杂度正好是 θ(|E|+|V|2)=θ(|V|2+|V|2)=O(|V|2) 。但是对于非稠密图,这个 |E| 实际没这么大,甚至 |E|=|V| ,这个算法就未免效率低下了。

优化法方法是使用最小堆,我们dist[min_Index]+weight小于dist[k]时,将新的dist[k]的值插入最小堆;在上面查找最小值的操作中,每次从最小堆中取出最小值,并且检查是否visited,如果没visited,那就找到新的顶点了。改进后的算法时间复杂度是 O(|E|lg|V|)

Floyd算法

Floyd算法是解决任意两点间最短路径的一种算法,可以正确处理负权图的最短路径问题。

上面的Dijkstra算法求出了某个点到其他所有点的最短路径,我们要求所有点对的最短路径,有这样一种思路,就是再外面再循环 |V| 次,那么不就求出所由点对的最短路径了吗?时间复杂度为 O(N3)

不过,Dijkstra算法为我们提供了一种动态规划的思想,一个点到另外一个点的最短路径,要么直接到达就是最短的,要么就是经过了一个已经最优化的点间接到达这个点就是最短的,只有这么两种情况。Floyd算法就根据这个思路把所有情况同过DP表的方式计算出来,时间复杂度是一样的,也是 O(N3) ,不过Floyd算法简洁的多。

Floyd算法DP公式:

D0[V][W]=min{D1[V][W],D1[V][K]+D1[K][W]}

D是一个二维矩阵,是一个辅助矩阵,初始状态和graph是一致的,通过该矩阵的变化,我们来修正path矩阵的值即可。

更多的关于Floyd算法的解释参见: 数据结构之最短路径(Floyd) ,包括我下面用的打印函数,可以参见它的解释。不过它的打印函数对于非强连通图有一点问题,我加以修正了。

打印函数实际上意思就是,比如我要找(0, 8)的最短路径,如果path[0][8]的值为k,说明0->8之间经过路径k,且0->k的结果是最优的,所以目前找到了所求路径的一部分{0, k1}。然后我们再次查找(k, 8)的最短路径,看它们两之间有没有中间更优化的路径,比如找到,如果有,那就找到了路径{0, k1, k2},依次下去,知道path[k][8]的结果为8,说明没有了。总的路径就是{0, k1, k2 … 8}。

下面给出我的代码:

#include <iostream>
#include <vector>
#include <iomanip>
#include <limits.h>

const int NUM_VERTICES = 5;  //A B C D E

void print_solution(std::vector<std::vector<int>>& helper, std::vector<std::vector<int>>& path)
{
    for(int i=0; i<NUM_VERTICES; ++i){
        for(int j=i+1; j<NUM_VERTICES; ++j){
            if(helper[i][j] == INT_MAX)
                continue;
            std::cout<<i<<"->";
            int k = path[i][j];
            while(k != j){
                std::cout<<k<<"->";
                k = path[k][j];
     }
            std::cout<<j<<std::endl;
        }
    }
}

void floyd(std::vector<std::vector<int>>& graph)
{
    std::vector<std::vector<int>>
    helper(NUM_VERTICES, std::vector<int>(NUM_VERTICES)),
    path(NUM_VERTICES, std::vector<int>(NUM_VERTICES));

    for(int i=0; i<NUM_VERTICES; ++i){
        for(int j=0; j<NUM_VERTICES; ++j){
            helper[i][j] = graph[i][j] == 0 ? INT_MAX : graph[i][j];
            path[i][j] = j;
        }
    }

    for(int k=0; k<NUM_VERTICES; ++k){
      for(int i=0; i<NUM_VERTICES; ++i){
            for(int j=0; j<NUM_VERTICES; ++j){
                if(helper[i][k] != INT_MAX && helper[k][j] != INT_MAX
                                           && helper[i][j] > helper[i][k] + helper[k][j]){
                    helper[i][j] = helper[i][k] + helper[k][j];
                    path[i][j] = k;
                }
            }
        }
    }

    print_solution(helper, path);
}

int main()
{
    std::vector<std::vector<int>>
    graph = {{0,  10, 0,  30, 100}, //A
             {0,  0,  50, 0,  0  }, //B
             {0,  0,  0,  0,  10 }, //C
             {0,  0,  20, 0,  60 }, //D
             {0,  0,  0,  0,  0  }};//E
          //  A   B   C   D   E
    floyd(graph);  //param 0 means vertex 'A'
    return 0;
}

输出结果:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值