第24章:单源最短路径

原创 2016年05月31日 14:22:35

最短路径一般来说是简单路径,即不包含环路的路径。一方面最短路径不能包含权重为负值的环路,因为若包含权重为负值的环路,则我们可以遍历这环路任意遍,则总是得到比之前权重值比之前少的最短路径,我们规定如果从源结点到某一结点的某条路径上存在权重为负值的环路,我们认为其最短路径权重值为负无穷。另一方面,如果最短路径包含权重值和为正值或0的环路,我们可以把这个环路删除,得到一个权重值更少或和之前一样的的最短路径,并且该路径是简单路径。因此不失一般性,我们可以假定找到的最短路径中没有环路,即它们都是简单路径。

有一个关于最短路径的定理,那就是最短路径的子路径也是最短路径,证明见算法导论第24章。

书中介绍了bellman-ford,利用拓扑排序和Dijkstra这三种算法来解决单源最短路径问题。它们的适用情形如下:

算法 适用情形
bellman-ford 有向图(能应用于无向图,因为可以把无向图理解为有向图),边的权重可以为负值,允许有环路,如果该环路权重和为负值,则算法会探测并报告其存在
利用拓扑排序 有向图,边的权重可以为负值,但不能有环路,所以不能应用于无向图
Dijkstra 有向图(也能应用于无向图),边的权重必须不能为非负值,可以有环路,当然该环路的权重和为非负值

一:bellman-ford算法

bellman-ford算法解决的是一般情况下的单源最短路径问题,在这里,边的权重可以为负值。给定带权重的有向图**G=(V,E)和权重函数w: E->**Rbellman-ford算法返回一个布尔值,以表明是否存在一个从源结点可以到达的权重为负值的环路。如果存在这样一个环路,算法将告诉我们不存在解决方案,返回false;如果没有这种环路存在,算法将给出最短路径和它们的权重

bellman-ford算法通过对边进行松弛操作来渐渐地降低从源结点s到每个结点v的最短路径的估计值v.d,知道该估计值与实际的最短路径权重δ(s,v)相同时为止。该算法返回true值当且仅当输入图不包含可以从源结点到达的权重为负值的环路。该算法的总运行时间为O(VE)。代码如下:

//smallest_weight[v]代表的是顶点v到源顶点s的最短距离;
bool bellman_ford(const int source,const vector<list<int>>& graph,const vector<vector<double>>& edge_weights,
const vector<edge>& graph_edge,vector<double>& smallest_weight,vector<int>& pred)
{
        const int NIL=-1;  //NIL代表不存在的顶点;

        for(int v=0;v!=graph.size();++v)
        {
                pred[v]=NIL;
                smallest_weight[v]=DBL_MAX;
        }
        smallest_weight[source]=0;

        for(int i=0;i!=graph.size()-1;++i) //对每条边松弛|V|-1次;
                for(int j=0;j!=graph_edge.size();++j)
                {
                        //graph_edge[j]表示存在边(u,v);
                        int u=graph_edge[j].start;
                        int v=graph_edge[j].end;

                        if(smallest_weight[u]!=DBL_MAX&&smallest_weight[v]>smallest_weight[u]+edge_weights[u][v]){
                                smallest_weight[v]=smallest_weight[u]+edge_weights[u][v];
                                pred[v]=u;
                        }
                }

        //检验是否存在从源顶点s可以抵达的权重为负值的环路;
        for(int i=0;i!=graph_edge.size();++i)
        {
                int u=graph_edge[i].start;
                int v=graph_edge[i].end;

                if(smallest_weight[u]!=DBL_MAX&&smallest_weight[v]>smallest_weight[u]+edge_weights[u][v])
                        return false;
        }

        return true;
}

void print_bellman_ford(const int source,const vector<list<int>>& graph,
const vector<vector<double>>& edge_weights,const vector<edge>& graph_edge)
{
        vector<double> smallest_weights(graph.size());
        vector<int> pred(graph.size());

        bool is_loop=bellman_ford(source,graph,edge_weights,graph_edge,smallest_weights,pred);

        if(is_loop==false){
                cout<<"存在着一个可以从源结点 "<<source<<" 可以抵达的权重为负值的环路"<<endl;
                return;
        }

        cout<<"source vertex is: "<<source<<endl;
        cout<<"vertex\tshortest path"<<endl;
        for(int v=0;v!=graph.size();++v)
                if(v!=source){
                        cout<<v<<"\t"<<flush;
                        print_path(source,v,pred);
                        cout<<"(smallest weights: ";
                        if(smallest_weights[v]!=DBL_MAX)
                                cout<<smallest_weights[v]<<" )"<<endl;
                        else
                                cout<<"infinity )"<<endl;

                }
}

//辅助的edge类和print_path()函数代码如下:

struct edge{
        edge(int s=0,int e=0,double w=0):start(s),end(e),weight(w){};
        int start,end;
        double weight;
};


//to print the path from source to v;
void print_path(int source,int v,const vector<int>& pred)
{
          const int NIL=-1; //NIL代表不存在的顶点
        if(v==source)
                cout<<source;
        else if(pred[v]==NIL)
                cout<<"no path from "<<source<<" to "<< v<<endl;
        else{
                print_path(source,pred[v],pred);
                cout<<" to "<<v;
        }
}

二:有向无环图中的单源最短路径问题:

根据结点的拓扑排序次序来对带权重的**有向无环图**G=(V,E)进行边的松弛操作,我们便可以在Θ(V+E)时间内计算出从单个源结点到所有结点之间的最短路径。在有向无环图中,即使存在权重为负值的边,但因为没有权重为负值的环路,最短路径都是存在的。

我们的算法先对有向无环图进行拓扑排序,以便确定节点之间的一个线性次序。如果有向无环图包含从结点u到结点v的一条路径,则u在拓扑排序的次序中位于结点v的前面。我们只需要按照拓扑排序的次序对结点进行一遍处理即可。每次对一个结点进行处理时,我们对从该结点出发的所有边进行松弛操作。代码如下:

void DAG_shortest_path(const int source,const vector<list<int>>& graph,const vector<vector<double>>& edge_weights,
vector<double>& smallest_weight,vector<int>& pred)
{
        //求出该有向无环图的拓扑排序;topological_sort()函数代码见第22章拓扑排序那一小节。
        auto topo_sort=topological_sort(graph);

        const int NIL=-1; //NIL代表不存在的顶点;
        for(int v=0;v!=graph.size();++v)
        {
                smallest_weight[v]=DBL_MAX;
                pred[v]=NIL;
        }
        smallest_weight[source]=0;

        for(auto iter1=topo_sort.begin();iter1!=topo_sort.end();++iter1)
        {
                int u=*iter1;
                for(auto iter2=graph[u].begin();iter2!=graph[u].end();++iter2)
                {
                        int v=*iter2;
                        if(smallest_weight[u]!=DBL_MAX&&smallest_weight[v]>smallest_weight[u]+edge_weights[u][v]){
                                smallest_weight[v]=smallest_weight[u]+edge_weights[u][v];
                                pred[v]=u;
                        }
                }
        }
}

void print_DAG_shortest_path(const int source,const vector<list<int>>& graph,const vector<vector<double>>& edge_weights)
{
        vector<double> smallest_weights(graph.size());
        vector<int> pred(graph.size());

        DAG_shortest_path(source,graph,edge_weights,smallest_weights,pred);

        cout<<"source vertex is: "<<source<<endl;
        cout<<"vertex\tshortest path"<<endl;
        for(int v=0;v!=graph.size();++v)
                if(v!=source){
                        cout<<v<<"\t"<<flush;
                        print_path(source,v,pred);
                        cout<<"(smallest weights: ";
                        if(smallest_weights[v]!=DBL_MAX)
                                cout<<smallest_weights[v]<<" )"<<endl;
                        else
                                cout<<"infinity )"<<endl;

                }
}

三:Dijkstra算法:

Dijkstra算法解决的是带权重的有向图上单源最短路径问题,该算法要求所有边的权重都为非负值。Dijkstra算法在运行过程中维持的关键信息是一组已经被访问的结点集合,从源结点到该集合中每个结点之间的最短路径已经被找到。算法重复从剩于未被访问的结点集合中国选择最短路径估计最小的结点u,将顶点u加入到访问结点的集合中,然后对所有从u出发的边进行松弛。代码如下:

void Dijkstra(const int source,const vector<list<int>>& graph,const vector<vector<double>>& edge_weights,
vector<double>& smallest_weight,vector<int>& pred)
{
        const int NIL=-1; //NIL表示不存在的顶点;
        vector<double> is_visited(graph.size(),false);
        for(int v=0;v!=graph.size();++v)
        {
                smallest_weight[v]=DBL_MAX;
                pred[v]=-1;
        }
        smallest_weight[source]=0;

        for(int i=0;i!=graph.size();++i)
        {
                double min_weight=DBL_MAX;
                int v=NIL;

                for(int j=0;j!=graph.size();++j)
                        if(!is_visited[j]&&smallest_weight[j]<min_weight){
                                min_weight=smallest_weight[j];
                                v=j;
                        }

                if(v==NIL) break;
                is_visited[v]=true;

                for(auto iter=graph[v].begin();iter!=graph[v].end();++iter)
                {
                        int tmp=*iter;
                        if(!is_visited[tmp]&&smallest_weight[v]+edge_weights[v][tmp]<smallest_weight[tmp])
                                {
                                        smallest_weight[tmp]=smallest_weight[v]+edge_weights[v][tmp];
                                        pred[tmp]=v;
                                }
                }
        }
}

void print_Dijkstra(const int source,const vector<list<int>>& graph,const vector<vector<double>>& edge_weights)
{
        vector<double> smallest_weights(graph.size());
        vector<int> pred(graph.size());

        Dijkstra(source,graph,edge_weights,smallest_weights,pred);

        cout<<"source vertex is: "<<source<<endl;
        cout<<"vertex\tshortest path"<<endl;
        for(int v=0;v!=graph.size();++v)
                if(v!=source){
                        cout<<v<<"\t"<<flush;
                        print_path(source,v,pred);
                        cout<<"(smallest weights: ";
                        if(smallest_weights[v]!=DBL_MAX)
                                cout<<smallest_weights[v]<<" )"<<endl;
                        else
                                cout<<"infinity )"<<endl;

                }
}
版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

算法导论-第24章- 单源最短路径 - 24.3 Dijkstra 算法

1、综述 Dijkstra(迪杰斯特拉)算法是典型的最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。 其基本思想是,设置顶点集合...

算法导论-第24章- 单源最短路径 - 24.1 Bellman-Ford 算法

1、综述

算法导论 | 第24章 单源最短路径

零、前言

算法导论 - 第24章 单源最短路径

单源最短路径的算法有两种:Bellman-Ford算法和Dijkstra算法。求最短路径,如果最短路径上存在weight小于0的cycle,则不存在最短路径。因为只要在这个cycle上多循环几次,该路...

算法导论 第24章 单源最短路径

最短路径的定义     在最短路径问题中,给出的是一个带权有向图G=(V,E),加权函数w:E->R为从边到实值的映射。路径p = 的权是其组成该路径的各边的权值之和: 定义从u到v的最短路径的...

《算法导论》第24章 单源最短路径

单元最短路径 单源最短路径

有代价的单源最短路径

问题:有代价的单源最短路径,并要求存储路径。(求最短的路径,并使代价最小)特点:* 存储路径:决定了难以用dijkstra,可以用flody,用path[i][j]表示 i 想走到 j 迈出的第一步。...

单源最短路径

  • 2013-03-07 20:13
  • 20KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)