(三) 图的最短路径问题

(三) 图的最短路径问题

1. 问题分类

(1)单源最短路径问题:从固定源点出发,求其到所有其他顶点的最短路径

  • 无权图
  • 有权图

(2)多源最短路径问题:从固定源点出发,求任意两顶点间的最短路径

2. 无权图的最短路径算法

  • 按照递增(非递减)的顺序找出源点到各个顶点的最短路。

  • 思想类似于BFS

    [外链图片转存失败(img-CKQDy8zU-1567356507444)(C:\Users\alway\AppData\Roaming\Typora\typora-user-images\1567349652122.png)]

void Unweighted(Vertex s) {
    // visited[v] = true;
    Enqueue(s, Q);
    while(!isEmpty(Q)) {
        v = Dequeue(Q);
        for (v的每个邻接点w) {
            if(dist[w]!=-1) { // dist初始化为-1, dist[v] = 0
                // visited[v] = true;
                dist[w] = dist[v] + 1;
                path[w] = v; // path 初始化为-1, s到w的路上经过v
                Enqueue(w, Q);
            }
        }
    }
}

3. 有权图的最短路径算法-----Dijkstra算法

  • 按递增的顺序找出源点到各个顶点的最短路

    算法:
  • 令S={源点 s + 已经确定了最短路径的顶点 v i v_i vi}

  • 对任一未收录的顶点v,定义dist[v]为 s 到 v 的最短路径长度,但该路径仅经过S中的顶点。即 { s − > ( v i ∈ S ) − > v } \{s->(v_i\in S)->v\} {s>(viS)>v}的最小长度。

  • 若路径是按递增(非递减)的顺序生成的,则:

    • 真正的最短路必须只经过S中的顶点(反证法可验证)
    • 每次从未收录的顶点中选一个dist最小的收录(贪心)
    • 增加一个 v 进到 S,可能影响另外一个w的dist值!(只影响那些与v直接相连的顶点)
    • dist[w] = min(dist[w], dist[v] + <v, w>)
void Dijkstra(Vertex s) {
	while (1) {
        v = 未收录顶点中的dist最小的顶点;
        if(满足条件的v不存在) {
            break;
        }
        collected[v] = true;
        for (v的每个邻接点w) {
            if(!collected[w]) 
                if(dist[w] + <v, w> < dist[w]) {
                    dist[w] = dist[w] + <v, w>;
                    path[w] = v;
                }
        }
    }
}
// 不能解决有负边的情况,dist[w] + <v, w> < dist[v]....
  • 选择未收录顶点中dist最小的顶点

1)直接扫描所有未收录顶点---- O ( ∣ V ∣ ) O(|V|) O(V)

  • ​ Dijkstra算法时间复杂度 T = O ( ∣ V ∣ 2 + ∣ E ∣ ) T=O(|V|^2+|E|) T=O(V2+E)
  • ​ 对于稠密图效果好
    2)将dist存在最小堆中---- O ( l o g ∣ v ∣ ) O(log|v|) O(logv)
  • ​ 更新dist[w]— O ( l o g ∣ V ∣ ) O(log|V|) O(logV)
  • ​ Dijkstra算法时间复杂度 T = O ( ∣ V ∣ l o g ∣ V ∣ + ∣ E ∣ l o g ∣ V ∣ ) = O ( ∣ E ∣ l o g ∣ V ∣ ) T=O(|V|log|V|+|E|log|V|)=O(|E|log|V|) T=O(VlogV+ElogV)=O(ElogV)
  • ​ 对于稀疏图效果好

3. 多源最短路径算法-----Floyd算法

  1. 直接将单源最短路算法调用 |V| 遍
    • T = O ( ∣ V ∣ 3 + ∣ E ∣ × ∣ V ∣ ) T=O(|V|^3+|E|\times|V|) T=O(V3+E×V)
    • 对于稀疏图效果好
  2. Floyd算法
    • T = O ( ∣ V ∣ 3 ) T=O(|V|^3) T=O(V3)
    • 对于稠密图效果好
算法:
  • D k [ i ] [ j ] D^k[i][j] Dk[i][j]=路径 { i − &gt; { l &lt; = k } − &gt; j } \{i-&gt;\{l&lt;=k\}-&gt;j\} {i>{l<=k}>j}的最小长度
  • D 0 , D 1 , . . . , D ∣ V ∣ − 1 [ i ] [ j ] D^0, D^1, ..., D^{|V|-1}[i][j] D0,D1,...,DV1[i][j]即给出了 i 到 j 的真正最短距离
  • D k − 1 D^{k-1} Dk1递推到 D k D^k Dk
    • 或者 k ∉ k\notin k/最短路径 { i − &gt; { l &lt; = k } − &gt; j } \{i-&gt;\{l&lt;=k\}-&gt;j\} {i>{l<=k}>j},则 D k = D k − 1 D^k=D^{k-1} Dk=Dk1
    • 或者 k ∈ k\in k最短路径 { i − &gt; { l &lt; = k } − &gt; j } \{i-&gt;\{l&lt;=k\}-&gt;j\} {i>{l<=k}>j},则该路径必定由两段最短路径组成:
      • D k [ i ] [ j ] = D k − 1 [ i ] [ k ] + D k − 1 [ k ] [ j ] D^k[i][j]=D^{k-1}[i][k]+D^{k-1}[k][j] Dk[i][j]=Dk1[i][k]+Dk1[k][j]
void Floyd() {
	for (i = 0; i < N; i++) {
        for (j = 0; j < N; j++) {
            D[i][j] = G[i][j];
            path[i][j] = -1;
        }
    }
    for (k = 0; k < N; k++) {
        for (i = 0; i < N; i++) {
            for (j = 0; j < N; j++) {
				if (D[i][k] + D[k][j] < D[i][j]) {
                    D[i][j] = D[i][k] + D[k][j];
                    path[i][j] = k;
                }
            }
        }        
    }
}

4. 代码实现

/* 邻接表存储 - 无权图的单源最短路算法 */
 
/* dist[]和path[]全部初始化为-1 */
void Unweighted ( LGraph Graph, int dist[], int path[], Vertex S )
{
    Queue Q;
    Vertex V;
    PtrToAdjVNode W;
     
    Q = CreateQueue( Graph->Nv ); /* 创建空队列, MaxSize为外部定义的常数 */
    dist[S] = 0; /* 初始化源点 */
    AddQ (Q, S);
 
    while( !IsEmpty(Q) ){
        V = DeleteQ(Q);
        for ( W=Graph->G[V].FirstEdge; W; W=W->Next ) /* 对V的每个邻接点W->AdjV */
            if ( dist[W->AdjV]==-1 ) { /* 若W->AdjV未被访问过 */
                dist[W->AdjV] = dist[V]+1; /* W->AdjV到S的距离更新 */
                path[W->AdjV] = V; /* 将V记录在S到W->AdjV的路径上 */
                AddQ(Q, W->AdjV);
            }
    } /* while结束*/
}
/* 邻接矩阵存储 - 有权图的单源最短路算法 */
 
Vertex FindMinDist( MGraph Graph, int dist[], int collected[] )
{ /* 返回未被收录顶点中dist最小者 */
    Vertex MinV, V;
    int MinDist = INFINITY;
 
    for (V=0; V<Graph->Nv; V++) {
        if ( collected[V]==false && dist[V]<MinDist) {
            /* 若V未被收录,且dist[V]更小 */
            MinDist = dist[V]; /* 更新最小距离 */
            MinV = V; /* 更新对应顶点 */
        }
    }
    if (MinDist < INFINITY) /* 若找到最小dist */
        return MinV; /* 返回对应的顶点下标 */
    else return ERROR;  /* 若这样的顶点不存在,返回错误标记 */
}
 
bool Dijkstra( MGraph Graph, int dist[], int path[], Vertex S )
{
    int collected[MaxVertexNum];
    Vertex V, W;
 
    /* 初始化:此处默认邻接矩阵中不存在的边用INFINITY表示 */
    for ( V=0; V<Graph->Nv; V++ ) {
        dist[V] = Graph->G[S][V];
        if ( dist[V]<INFINITY )
            path[V] = S;
        else
            path[V] = -1;
        collected[V] = false;
    }
    /* 先将起点收入集合 */
    dist[S] = 0;
    collected[S] = true;
 
    while (1) {
        /* V = 未被收录顶点中dist最小者 */
        V = FindMinDist( Graph, dist, collected );
        if ( V==ERROR ) /* 若这样的V不存在 */
            break;      /* 算法结束 */
        collected[V] = true;  /* 收录V */
        for( W=0; W<Graph->Nv; W++ ) /* 对图中的每个顶点W */
            /* 若W是V的邻接点并且未被收录 */
            if ( collected[W]==false && Graph->G[V][W]<INFINITY ) {
                if ( Graph->G[V][W]<0 ) /* 若有负边 */
                    return false; /* 不能正确解决,返回错误标记 */
                /* 若收录V使得dist[W]变小 */
                if ( dist[V]+Graph->G[V][W] < dist[W] ) {
                    dist[W] = dist[V]+Graph->G[V][W]; /* 更新dist[W] */
                    path[W] = V; /* 更新S到W的路径 */
                }
            }
    } /* while结束*/
    return true; /* 算法执行完毕,返回正确标记 */
}
/* 邻接矩阵存储 - 多源最短路算法 */
 
bool Floyd( MGraph Graph, WeightType D[][MaxVertexNum], Vertex path[][MaxVertexNum] )
{
    Vertex i, j, k;
 
    /* 初始化 */
    for ( i=0; i<Graph->Nv; i++ )
        for( j=0; j<Graph->Nv; j++ ) {
            D[i][j] = Graph->G[i][j];
            path[i][j] = -1;
        }
 
    for( k=0; k<Graph->Nv; k++ )
        for( i=0; i<Graph->Nv; i++ )
            for( j=0; j<Graph->Nv; j++ )
                if( D[i][k] + D[k][j] < D[i][j] ) {
                    D[i][j] = D[i][k] + D[k][j];
                    if ( i==j && D[i][j]<0 ) /* 若发现负值圈 */
                        return false; /* 不能正确解决,返回错误标记 */
                    path[i][j] = k;
                }
    return true; /* 算法执行完毕,返回正确标记 */
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值