(三) 圖的最短路徑問題

(三) 圖的最短路徑問題

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 + 已經確定了最短路徑的頂點viv_i}

  • 對任一未收錄的頂點v,定義dist[v]爲 s 到 v 的最短路徑長度,但該路徑僅經過S中的頂點。即{s>(viS)>v}\{s->(v_i\in S)->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|)

  • ​ Dijkstra算法時間複雜度T=O(V2+E)T=O(|V|^2+|E|)
  • ​ 對於稠密圖效果好
    2)將dist存在最小堆中----O(logv)O(log|v|)
  • ​ 更新dist[w]—O(logV)O(log|V|)
  • ​ Dijkstra算法時間複雜度T=O(VlogV+ElogV)=O(ElogV)T=O(|V|log|V|+|E|log|V|)=O(|E|log|V|)
  • ​ 對於稀疏圖效果好

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

  1. 直接將單源最短路算法調用 |V| 遍
    • T=O(V3+E×V)T=O(|V|^3+|E|\times|V|)
    • 對於稀疏圖效果好
  2. Floyd算法
    • T=O(V3)T=O(|V|^3)
    • 對於稠密圖效果好

算法:

  • Dk[i][j]D^k[i][j]=路徑{i&gt;{l&lt;=k}&gt;j}\{i-&gt;\{l&lt;=k\}-&gt;j\}的最小長度
  • D0,D1,...,DV1[i][j]D^0, D^1, ..., D^{|V|-1}[i][j]即給出了 i 到 j 的真正最短距離
  • Dk1D^{k-1}遞推到DkD^k
    • 或者kk\notin最短路徑{i&gt;{l&lt;=k}&gt;j}\{i-&gt;\{l&lt;=k\}-&gt;j\},則Dk=Dk1D^k=D^{k-1}
    • 或者kk\in最短路徑{i&gt;{l&lt;=k}&gt;j}\{i-&gt;\{l&lt;=k\}-&gt;j\},則該路徑必定由兩段最短路徑組成:
      • Dk[i][j]=Dk1[i][k]+Dk1[k][j]D^k[i][j]=D^{k-1}[i][k]+D^{k-1}[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; /* 算法執行完畢,返回正確標記 */
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章