单元最短路问题

1.单源短路问题(Bellman-Ford算法)

贪心策略,始终选择从出发点s到达顶点i的当前最短距离为d[i].那么就会出现下列推导关系:

d[i]=min{d[j]+cost[j][i]|e=(j,i)属于E}(其中cost(j,i)表示从顶点j到顶点i的权值)
如果给定的图是DAG,那么就可以按拓扑序给顶点编号,并且利用上述推到关系计算,但是如果给定图
中有圈,就无法依照这样的顺序来进行计算。在这种情况下,记到达当前顶点i的距离是d[i],
并设初值d[s]=0,d[i]=INF(足够大的常数),再不断使用该递推关系更新d[i],只要途中不存在负圈
更新就是有限的。

//从顶点from指向顶点to的权值为cost的边
struct edge{
    int form;
    int to;
    int cost;
}; 
edge es[MAX+E]; //边
int d[MAX_V]; //从源点到顶点i的距离
int V,E; //V顶点数,E边数
//求解从顶点S出发到达所有顶点的最短距离
void bellman_ford(int s){
    for(int i=0;i<V;i++) d[i]=INF;
    d[s]=0;
    while(true){
        bool flag = false;
        for(int i=0;i<E;i++){
            edge e = es[i];
            if(d[e.from]!=INF && d[e.to]>d[e.from]+e.cost){
                d[e.to]=d[e.from]+e.cost;
                flag = true;
            } 
        }
        //存在负圈 
        if(!flag)
            break;
    }
} 
如果图中不存在负圈,那么最短路径不会经过同一个顶点两次(也就是说,最多通过|V|-1条边)

外层循环最多执行|V|-1次。因此时间复杂度为O(|V|*|E|).如果存在负圈,那么在第|V|次循环中也会
更新d,因此可以用该性质检查负圈。如果从一开始对所有的顶点i,都把d[i]初始化为0,那么就可以检查
所有的负圈。

// 返回值为true,表明存在负圈 
bool find_negative_loop(){
    memset(d,0,sizeof(d));
    for(int i=0;i<V;i++){
        for(int j=0;j<E;j++){
            edge e = es[j];
            if(d[e.form]!=INF && d[e.to]>d[e.from]+e.cost){
                d[e.to] = d[e.from]+e.cost;
            }
            //如果第n次更新了,就说明存则负圈
            if(i==V-1)
                return true; 
        }
    }
    return false;
} 

2.单源最短路径(Dijkstra算法)

 Dijkstra算法处理的情况是Bellman-Ford算法处理情况的一个特例,也就是没有负边的情况。

在Bellman_Ford算法中,如果d[i]还不是最短距离的话,那么即使进行d[j]=d[i]+cost(i,j)的更新,
的d[j]也不会变成最短距离。同时,即使d[i]没有任何变化,每次循环也要检查一遍从i出发的所有边
而Dijkstra算法对此作出了改进:
(1)找到最短距离已经确定的顶点,从它出发更新相邻顶点的最短距离
(2)此后不需要关注(1)中的最短距离已经确定的顶点。
此处最重要的就是如何确定最短距离已经确定的顶点。在初始化时,只有起点的最短距离是确定的。
而在尚未使用的顶点中,距离d[i]最小的顶点的最短距离已经确定。

//cost[u][v]表示边e=(u,v)的权值,不存在INF 
int cost[MAX_V][MAX_V]; 
//从顶点s出发到达各个顶点的最短距离 
int d[MAX_V];
//已经使用过的顶点
bool used[MAX_V];
//顶点数
int V;
//从顶点s出发到各个顶点的最短距离
void dijkstra(int s){
    //初始化used,d数组 
    for(int i=0;i<V;i++){
        d[i]=INF;
        used[i]=false;
    }
    used[s]=true;
    while(true){
        int v = -1;
        //找到从未使用的顶点中选择一个距离当下顶点距离最短的顶点 
        for(int i=0;i<V;i++){
            if(!used[i] && (v==-1 || d[v]>d[i])){
                v= i;
            }
        } 
        /*
        *以上for循环结束以后会产生两种结果:v==-1或者v!=-1
        * 当v==-1:表明每一个顶点都已经用过,此时应该退出
        *       或者是d[i] 0<=i<V均相等,但是这种情况不存在,
        *       因为d[s]=0 ,d[i]=INF(初始情况) 0<=i<V and i!=s
        * 当v!=-1 :表明存在该顶点  第一次执行的时候v=s 
        */ 
        if(v==-1)
            break;
        used[v]=true;

        //更新从当下顶点到各个顶点的最短距离
        for(int i=0;i<V;i++){
            d[i]=min(d[i],d[v]+cost[v][i]);
        } 
    }
}
使用邻接矩阵实现Dijtstra算法的复杂度是O(|V|2)。同样会使用邻接表时间复杂度也是一样。

时间的耗费主要是数值的更新以及取出最小值两个操作。因此使用堆来进行操作。把每个顶点当前的
最短距离用堆维护,在更新最短距离时,把对应元素向根的方向移动以满足堆的性质。每次从堆内部取
出的最小值就是下一次要使用的顶点。这样堆中共有元素O(|V|)个,共进行O(|E|)次操作,因此该算法
的复杂度为O(|E|log|V|)

struct edge{
    int to ; //一条边的终点。如果是无向图,那么一条边会记录两次,如果是有向图仅仅记录终点 
    int cost;
}; 
typedef pair<int,int> P; //first是最短距离,second是顶点的 编号

int V; // 顶点的数目
vector<edge> G[MAX_V]; //图
int d[MAX_V]; //从顶点s出发到达各个顶点的最短距离 

void dijkstra2(int s){
    //通过指定greater<P>参数,堆按照first从小到大顺序取出
    priority_queue<P,vector<P>,greater<P>> que;

    //初始化
    for(int i=0;i<V;i++){
        d[i]=INF;
    } 
    d[s]=0;
    que.push(0,s);
    //每一个顶点进入优先队列一次,当队列空时,所有的顶点都已经被访问一次 
    while(!que.empty()){
        P p=que.top(); que.pop();
        int v = p.first;
        for(int u=0;u<G[v].size();u++){
            edge e=G[v][u];
            if(d[e.to]>d[v]+e.cost){
                d[e.to]=d[v]+e.cost;
                que.push(P(d[e.to],e.to));
            }
        } 
    }
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值