博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
2020-11-22
阅读量:4089 次
发布时间:2019-05-25

本文共 4027 字,大约阅读时间需要 13 分钟。

思考这个算法已经两天了。之前发过一个Bellman-Ford算法,但那一个使用的是收缩法,十分耗费时间。于是开始尝试动态规划,代码如下,已经可以解决有负值时的求解最短路径,但是没有处理负圈的功能。因为我没有能力在动态规划的递归函数中进行负圈的判断(个人感觉Algorithm design 中“n-1!=n"一说有误)。如果在递归外部判断,那么该方法就失去了相对于收缩法的优势,时间复杂度将相等。所以那位大神可以在递归函数中判断负圈,请不吝赐教。当然Bellman-Ford算法并不是解决这个问题的最佳算法,但是为了练习动态规划,我还是花费了许多时间。

Have fun coding,i_human.Have fun coding,everyone!

THE CODE:

 

// Bellman-Ford solved by dynamic prgramming.cpp : 定义控制台应用程序的入口点。

//

 

#include "stdafx.h"

#include<iostream>

#define N 100

#define m 10000  //当作正无穷使用

 

using namespace std;

 

int find(int i,int t);

void prin();

 

int n,e,l;

int pre[2][N];

int edge[N][N];

 

int main()

{

    int u,v,length;

    cin>>n>>e;

    for(int i=1;i<=n;i++)

    {

        for(int j=1;j<=n;j++)

        {

            edge[i][j]=m;

        }

    }

    for(int i=1;i<=n;i++)

    {

        pre[0][i]=-1;

        pre[1][i]=m;

    }

    pre[0][1]=1;    //以1作为源点

    pre[1][1]=0;

    for(int i=0;i<e;i++)

    {

        cin>>u>>v>>length;

        edge[u][v]=length;

    }

    for(int k=2;k<=n;k++)

        find(e,k);        

    prin();

    system("pause");

    return 0;

}

 

int find(int i,int t)

{

    if(pre[0][t]!=-1 && t!=1)

        return find(i-1,pre[0][t])+pre[1][t];

    else if(t==1)

        return 0;

    else

    {

        int M=m;

        for(int j=1;j<=n;j++)

        {

            if(edge[j][t]!=m && find(i-1,j)+edge[j][t]<M)

            {

                M=find(i-1,j)+edge[j][t];

                l=j;

            }

        }

        pre[0][t]=l;

        pre[1][t]=edge[l][t];

        return find(i-1,l)+edge[l][t];

    }

}

 

void prin()

{

    for(int i=2;i<=n;i++)

    {

        cout<<i<<"<--";

        int j=pre[0][i];

        while(j!=1)

        {

            cout<<j<<"<--";

            j=pre[0][j];

        }

        cout<<"1"<<endl;

    }

}

Floyd算法:

状态:

d[k][i][j]定义:“只能使用第1号到第k号点作为中间媒介时,点i到点j之间的最短路径长度。”

动态转移方程:

d [ k ] [ i ] [ j ] = m i n ( d [ k − 1 ] [ i ] [ j ] , d [ k − 1 ] [ i ] [ k ] + d [ k − 1 ] [ k ] [ j ] ) ( k , i , j ∈ [ 1 , n ] ) d[k][i][j] = min(d[k-1][i][j], d[k-1][i][k]+d[k-1][k][j])(k,i,j∈[1,n]) d[k][i][j]=min(d[k−1][i][j],d[k−1][i][k]+d[k−1][k][j])(k,i,j∈[1,n])

Bellman-Ford 算法:

状态:

d[k][u]定义:从源点v出发最多经过不构成负权值回路的k条边到达终点u的最短路径的长度

动态转移方程:

d i s t [ k ] [ u ] = m i n ( d i s t [ k − 1 ] [ u ] , m i n ( d i s t [ k − 1 ] [ j ] + E d g e [ j ] [ u ] ) ) , j = 0 , 1 , . . . , n − 1 ; j ! = u dist[k][u] = min(dist[k-1][u] ,min(dist[k-1][j]+Edge[j][u])),j = 0,1,...,n-1;j!=u dist[k][u]=min(dist[k−1][u],min(dist[k−1][j]+Edge[j][u])),j=0,1,...,n−1;j!=u

从上面可以看出这两种算法,对于子问题考虑前者考虑的是基于点的中转,后者考虑是基于边的中转,基于边的中转需要考虑不形成回路,求最小的话,非负数情况下,肯定是没有回路的,存在负权回路,就没有最小值,需要检测是否有负权回路。

Bellman-Ford 算法基于动态规划的原始实现:

#%%

# dist[k][u] = min{ dist[k-1][u] ,min{dist[k-1][j]+Edge[j][u]} },j = 0,1,...,n-1;j!=u

import numpy as np

def Bellman_Ford_original(graph,start):
    vertex_num = len(graph)
    edge_num_ = vertex_num-1
    list = np.full((edge_num_+1,vertex_num+1),np.inf)
#    for i in range(1,vertex_num+1):
#        list[1,i] = graph[start,i-1]
    list[:,start+1] =0
    
    for k in range(1,edge_num_+1):
        for u in range(1,vertex_num+1):
            result = list[k-1,u]
            for j in range(1,vertex_num+1):
                if  graph[j-1,u-1]>0 and graph[j-1,u-1]<10000 and result > list[k-1,j] + graph[j-1,u-1]:
                    result = list[k-1,j] + graph[j-1,u-1]
            list[k,u] =result                     
    return list[k,1:]
            

使用滚动数组加入空间优化:list[k-1,u],list[k-1,j],假如使用一维数组的话,list[k-1,u]是未更新的主句没问题,list[k-1,j]就不好说了,既有可能是更新过的数据,也有可能是未更新的数据,理论上应该不能用滚动数组优化,但是:只要数据可以被更新,就代表v到u的距离可以边更小,是不影响整体数据向更小方向推进的。

使用一维数组优化版本,加入了追踪解实现:

def Bellman_Ford(graph,start):

    vertex_num = len(graph)
    edge_num_ = vertex_num-1
    list = np.full((vertex_num+1),np.inf)
    list[start+1] = 0
    
    path =[-1] * vertex_num    
    for i in range(vertex_num):
        if graph[start,i] < 10000:
            path[i] = start    
    path[start] = None
    
    
#    for i in range(1,vertex_num+1):
#        list[i] = graph[start,i-1]
                    

    

    for k in range(1,edge_num_+1):
        flag = True
        for u in range(1,vertex_num+1):
            for j in range(1,vertex_num+1):
                w = graph[j-1,u-1]
                if w>0 and w <10000 and list[u] > list[j] + w :
                    list[u] = list[j] + w
                    path[u-1] = j-1
                    flag = False
         # 提前退出,并检查是否有负回路              
        if flag == True:
            for u in range(1,vertex_num+1):
                for j in range(1,vertex_num+1):
                    if list[u] > list[j] + graph[j-1,u-1] :
                        print "there is a - cycle"
                        break
            break

        

    return list[1:],path

另外一种理解,从松弛的角度:

第一,初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。

第二,进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
第三,遍历途中所有的边(edge(u,v)),判断是否存在这样情况:
d(v)>d(u) + w(u,v)
则返回false,表示途中存在从源点可达的权为负的回路。
之所以需要第三部分的原因,是因为,如果存在从源点可达的权为负的回路。则应为无法收敛而导致不能求出最短路径。

 

转载地址:http://uzuii.baihongyu.com/

你可能感兴趣的文章
网易云音乐移动客户端Vue.js
查看>>
ES7 await/async
查看>>
ES7的Async/Await
查看>>
React Native WebView组件实现的BarCode(条形码)、(QRCode)二维码
查看>>
每个人都能做的网易云音乐[vue全家桶]
查看>>
JavaScript专题之数组去重
查看>>
Immutable.js 以及在 react+redux 项目中的实践
查看>>
Vue2.0全家桶仿腾讯课堂(移动端)
查看>>
React+Redux系列教程
查看>>
react-native 自定义倒计时按钮
查看>>
19 个 JavaScript 常用的简写技术
查看>>
ES6这些就够了
查看>>
微信小程序:支付系列专辑(开发指南+精品Demo)
查看>>
iOS应用间相互跳转
查看>>
iOS开发之支付宝集成
查看>>
iOS开发 支付之银联支付集成
查看>>
iOS开发支付集成之微信支付
查看>>
浅谈JavaScript--声明提升
查看>>
React非嵌套组件通信
查看>>
Websocket 使用指南
查看>>