图的那些事儿——Dijkstra和Floyd

最短路问题

Dijkstra算法
说到最短路问题,我相信只要是学习过计算机的人都有听说过Dijkstra他老人家,他对程序的贡献远不止一个算法。

1 提出“goto有害论”;
2 提出信号量和PV原语;
3 解决了“哲学家聚餐”问题;
4 最短路径算法(SPF)和银行家算法的创造者;
5 第一个Algol 60编译器的设计者和实现者;
6 THE操作系统的设计者和开发者;

按照他自己的称呼,他是一个程序员。不得不说,这样的程序员实在是太伟大了。

让我们回到Dijkstra算法上。
这个算法的核心是维护d[i]=>i号结点和起点s距离的估值,之所以是估值,是因为它可能并不是真的最短值。要经历一个过程,才能够成为真正的最短值。

这次我们先看算法好了。

清除所有点的标号(所有点都是未知的)
设d[0]=0,d[i]=INF(无限大)
循环n次{
    #在未知的点中,寻找出d值最小的结点x
    *标记x为已知
    对于从x出发的所有边(x,y)更新 d[y]=min(d[y],d[x]+w[x][y])   
}

我们看到了这个过程(标了*号的这一行)。
是当d[x]为当前所有未知点中的最小值时,别的所有的到达x的走法都是绕远路、舍近求远。所以,这时我们可以确定 d[x]就是x点和起点s的最短距离!
我们来写一个简单的版本好了

//v 标记是否已知
//
memset(v,0,sizeof(v));
for(int i=0;i<n;i++) d[i]=(i==0?0:INF);
for(int i=0;i<n;i++){
    int x,m=INF;
    /*查找未知点中d值最小的结点x*/
    for(int j=0;j<n;j++)if(v[j]&&d[j]<m) m=d[x=j];
    v[x]=1;
    /*以x为中间点,更新别的点的d值*/
    for(int k=0;k<n;k++) d[k]=min(d[k],d[x]+w[x][k]); 
}

代码中带有注释的两处其实都可以优化。
查找最小结点这种工作其实对于一个优先队列来说非常合适。
这个队列中拥有d值以及其对应的结点号(d[i]和i)
还需要定义>操作符

struct HeapNode{
    int d,i;
    bool operator < (const HeapNode& rhs) const{
        return d>rhs.d; 
    } 
}

在正式完成算法之前,我们首先对数据结构进行些许该进。另一个可以优化的地方也在这里,将w[i][j]这样一个N^2的数组转换为一个vector< int> Gi[maxn]来存储边号,用vector< Edge> Edges来存储边。

struct Edge{
    int from,to;
    int dist;
    Edge(int x,int y,int d):from(u),to(v),dist(d){}
}
struct Dijstra{
    vector<int> G[maxn];
    vector<Edge> edges;
    int m,n;
    int d[mniaxn],v[maxn];
    int p[maxn];/*保存父结点*/
    void init(int n){
        this->n=n;
        for(auto item:G)item.clear();
        edges.clear();
    }
    void AddEdge(int from,int to,int dist){
        edges.push_back(Edge(from,to,dist));
        m=edges.size();
        G[from].push_back(m-1);/*m-1刚好为这个边在edges中的索引*/
    }
    void dijkstra(int s){
    ...
    }
}

主算法:

void Dijkstra(int s){
    priority_queue<HeapNode> Q;
    for(int i=0;i<n;i++) d[i]=INF;
    d[s]=0;
    memset(done,0,sizeof(done));
    Q.push((HeadNode){0,s});
    while(!Q.empty()){
        HeapNode x=Q.top();Q.pop();
        int u=x.u;
        if(done[u]) continue;
        done[u]=true;
        for(int i=0;i<G[u].size();i++){
            Edge& e=edge[G[u][i]];
            if(d[e.to]>d[u]+e.dist){
             d[e.to]=d[u]+e.dist;
             p[e.to]=G[u][i];
             Q.push((HeapNode){d[e.to],e.to})
            }
        }
    }
}

是不是有点累了呢。没关系,只要看懂了第一段代码,对Dijkstra的贪心思想熟记于心就可以了!
但是Dijkstra算法在面对有负权边时就无能为力了,有负环的话就意味着最短路径不存在!
这个时候我们需要另一种算法Bellman-Ford算法
Bellman-Ford
我们先直接上代码看看

for(int i=0;i<n;i++) d[i]=INF;
d[0]=0;
for(int k=0;k<n-1;k++)//迭代n-1次
    for(int i=0;i<m;i++)//检查每条边
    {
        int x=u[i],y=v[i];
        if(d[x]<INF) d[y]=min(d[y],d[x]+w[i]);//松弛
    }

需要指出的是:Bellman-Ford算法非常低,其优化这里先不给出

我们应该关注的重点在于Floyd算法:

for(int k=0;k<n;k++)//作为中点
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            d[i][j]=min(d[i][j],d[i][k]+d[k][j]);//松弛

Floyd求出的是各个点对的距离。

版权声明:本文为博主原创文章,转载请标明出处。

转载于:https://www.cnblogs.com/fridge/p/4861900.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值