數算---最短路徑

前言

最短路徑也是數據結構與算法中常見的問題,不同于最小生成樹,最短路徑問題則是需要找出起點到終點之間路途最短的路徑,屬于路徑規劃范疇的問題了。本篇文章將介紹兩種最短路徑的算法。
示例:如下圖所示,找出V0到V8兩點間距離最短的路徑。

示例

迪杰斯特拉[Dijkstra]算法

鄰接矩陣如下圖所示:


思路:

  1. 初始化三個數組,final數組用于記錄V0到Vw之間最短路徑是否有結果,final[w] = 1;D數組表示V0到某個頂點Vw的路徑;P數組表示各個頂點的前驅頂點的下標。
  2. 從V1開始,V8結束,利用k為當前頂點下標,循環遍歷,每一次循環判斷當前頂點到其余頂點最小路徑。首先判斷final數組中是否已經有結果,final[w] = 1,則跳過,final[w] = 0,則比較此時V0-->Vw之間距離最小值 與 鄰接矩陣中arc[k][w] 的和 D[w]的大小,取較小者設為D[w]的值。即V0到Vw的最短距離。(條件:!final[0~9] && min + G.arc[k][w] < D[w])

執行過程:

  1. 初始化三個數組,默認final數組全為0,D數組為鄰接矩陣第一行數據,代表V0到連接頂點的已知距離(并非最小距離),P數組值全為0。

  2. 從0開始,則(D)[v0] = 0,V0到V0距離為0; final[v0] = 1,表示V0到V0沒有路徑; (P)[v0] = -1,V0到V0沒有路徑,所以前驅頂點下標為-1;

  3. 來到V1頂點,k = 1, min = 1, 首先判斷final[0~9]的情況,再判斷min + G.arc[k][w] < D[w]條件是否滿足,滿足則將對應P[w]位置的值設為k,即前驅頂點為V1。


  4. 按照以上步驟k范圍[0~8],min 為當前D[k]的值,然后進行比對,最終得到如下結果:


    最終所有的頂點都經過了。此時final數組全為1,D數組中表示V0到其余頂點最短的路徑值,P數組則是表示每個頂點的前驅頂點下標。
    V0--->V3: 4-->2-->1-->0 最短路徑值為7
    V0--->V6: 3-->4-->2-->1-->0 最短路徑值為10

代碼實現

#define MAXEDGE 20
#define MAXVEX 20
#define INFINITYC 65535

typedef int Status;
typedef struct
{
    int vexs[MAXVEX];
    int arc[MAXVEX][MAXVEX];
    int numVertexes, numEdges;
}MGraph;

/*用于存儲最短路徑下標的數組*/
typedef int Patharc[MAXVEX];
/*用于存儲到各點最短路徑權值的和*/
typedef int ShortPathTable[MAXVEX];

/*10.1 創建鄰近矩陣*/
void CreateMGraph(MGraph *G)
{
    int i, j;
    
    G->numEdges=16;
    G->numVertexes=9;
    
    for (i = 0; i < G->numVertexes; i++)
    {
        G->vexs[i]=i;
    }
    
    for (i = 0; i < G->numVertexes; i++)
    {
        for ( j = 0; j < G->numVertexes; j++)
        {
            if (i==j)
                G->arc[i][j]=0;
            else
                G->arc[i][j] = G->arc[j][i] = INFINITYC;
        }
    }
    
    G->arc[0][1]=1;
    G->arc[0][2]=5;
    G->arc[1][2]=3;
    G->arc[1][3]=7;
    G->arc[1][4]=5;
    
    G->arc[2][4]=1;
    G->arc[2][5]=7;
    G->arc[3][4]=2;
    G->arc[3][6]=3;
    G->arc[4][5]=3;
    
    G->arc[4][6]=6;
    G->arc[4][7]=9;
    G->arc[5][7]=5;
    G->arc[6][7]=2;
    G->arc[6][8]=7;
    
    G->arc[7][8]=4;
    
    
    for(i = 0; i < G->numVertexes; i++)
    {
        for(j = i; j < G->numVertexes; j++)
        {
            G->arc[j][i] =G->arc[i][j];
        }
    }
    
}

/*10.2 求得網圖中2點間最短路徑
 Dijkstra 算法
 G: 網圖;
 v0: V0開始的頂點;
 p[v]: 前驅頂點下標;
 D[v]: 表示從V0到V的最短路徑長度和;
 */
void ShortestPath_Dijkstra(MGraph G, int v0, Patharc *P, ShortPathTable *D)
{
    int v,w,k,min;
    k = 0;
    /*final[w] = 1 表示求得頂點V0~Vw的最短路徑*/
    int final[MAXVEX];
    
    /*1.初始化數據*/
    for(v=0; v<G.numVertexes; v++)
    {
        //全部頂點初始化為未知最短路徑狀態0
        final[v] = 0;
        //將與V0 點有連線的頂點最短路徑值;
        (*D)[v] = G.arc[v0][v];
        //初始化路徑數組p = 0;
        (*P)[v] = 0;
    }
    
    //V0到V0的路徑為0
    (*D)[v0] = 0;
    //V0到V0 是沒有路徑的.
    final[v0] = 1;
    //v0到V0是沒有路徑的
    (*P)[v0] = -1;
    
  
    
    //2. 開始主循環,每次求得V0到某個頂點的最短路徑
    for(v=1; v<G.numVertexes; v++)
    {
        
        //當前所知距離V0頂點最近的距離
        min=INFINITYC;
        /*3.尋找離V0最近的頂點*/
        for(w=0; w<G.numVertexes; w++)
        {
            if(!final[w] && (*D)[w]<min)
            {
                k=w;
                //w頂點距離V0頂點更近
                min = (*D)[w];
            }
        }
        
        //將目前找到最近的頂點置為1;
        final[k] = 1;
        
        /*4.把剛剛找到v0到v1最短路徑的基礎上,對于v1 與 其他頂點的邊進行計算,得到v0與它們的當前最短距離;*/
        for(w=0; w<G.numVertexes; w++)
        {
            //如果經過v頂點的路徑比現在這條路徑長度短,則更新
            if(!final[w] && (min + G.arc[k][w]<(*D)[w]))
            {
                //找到更短路徑, 則修改D[W],P[W]
                //修改當前路徑的長度
                (*D)[w] = min + G.arc[k][w];
                (*P)[w]=k;
            }
        }
    }
}

int main(void)
{
    printf("最短路徑-Dijkstra算法\n");
    int i,j,v0;
    MGraph G;
    Patharc P;
    ShortPathTable D;
    v0=0;
    
    CreateMGraph(&G);
    
    ShortestPath_Dijkstra(G, v0, &P, &D);
    
    printf("最短路徑路線:\n");
    for(i=1;i<G.numVertexes;++i)
    {
        printf("v%d -> v%d : ",v0,i);
        j=i;
        while(P[j]!=-1)
        {
            printf("%d ",P[j]);
            j=P[j];
        }
        printf("\n");
    }
    
    printf("\n最短路徑權值和\n");
    for(i=1;i<G.numVertexes;++i)
        printf("v%d -> v%d : %d \n",G.vexs[0],G.vexs[i],D[i]);
    
    printf("\n");
    return 0;
}
弗洛伊德[Floyd]算法

這種算法的大致含義就是利用中間過頂點K,計算出頂點V到頂點W(W=[0~8])之間的最短路徑,利用鄰接矩陣G,比較D[v][w]D[v][k] + D[k][w] 之間的大小,更新二維數組D,并且利用二維數組P記下此時的k,公式如下:

D[v][w] = min{ D[v][w], D[v][k] + D[k][w] }

利用實時更新的二維數組D和一個記錄頂點下標k的二維數組P來記錄算法中涉及的信息。


以上是D數組和P數組默認初始值。

演算過程

  1. k = 0 , v 的范圍[0~8], w的范圍 [0~8], 意思就是以V0頂點為中轉點,比較此時D[v][w] 與 D[v][0]+ D[0][w] 的大小,發現并沒有任何變化,D[v][w] < (D[v][0]+ D[0][w])

  2. k = 1, v 的范圍[0~8], w的范圍 [0~8],意思就是以V1頂點為中轉點,比較此時D[v][w] 與 D[v][0]+ D[0][w] 的大小,一圈比較下來,得到如下結果:


    k=1

    如上圖中的結果,相應的也需要更改P數組紅色區域值為1,如下所示:


    k=1
  3. k 由 2 -> 8, v 的范圍[0~8], w的范圍 [0~8],意思就是以V1頂點為中轉點,比較此時D[v][w] 與 D[v][0]+ D[0][w] 的大小,由此循環比較,最終得到的最終結果如下:


    最終結果

如上圖最終結果可知,D數組第一行即V0到各個頂點的最小距離,結果與上方第一種方法結果一樣:

那么V0到各個頂點最短距離求出來了,但是怎么知道V0到各個頂點最短距離是怎么走的,例如V0-V8 經過哪些頂點呢?

解答:以V0~V8為例,從P數組中看出,P[0][8] = 1, 說明經過頂點V1,將1替代0,P[1][8] = 2, 說明經過頂點2,再將2替代1得到P[2][8] = 4, 則表示經過頂點4,依次類推,最終經過定頂點情況為:V0->V1->V2->V4->V3->V6->V7->V8

代碼實現

#define MAXEDGE 20
#define MAXVEX 20
#define INFINITYC 65535

typedef int Status;    /* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */

typedef struct
{
    int vexs[MAXVEX];
    int arc[MAXVEX][MAXVEX];
    int numVertexes, numEdges;
}MGraph;

typedef int Patharc[MAXVEX][MAXVEX];
typedef int ShortPathTable[MAXVEX][MAXVEX];

/* 11.1 構成鄰近矩陣 */
void CreateMGraph(MGraph *G)
{
    int i, j;
    
    /* printf("請輸入邊數和頂點數:"); */
    G->numEdges=16;
    G->numVertexes=9;
    
    for (i = 0; i < G->numVertexes; i++)/* 初始化圖 */
    {
        G->vexs[i]=i;
    }
    
    for (i = 0; i < G->numVertexes; i++)/* 初始化圖 */
    {
        for ( j = 0; j < G->numVertexes; j++)
        {
            if (i==j)
                G->arc[i][j]=0;
            else
                G->arc[i][j] = G->arc[j][i] = INFINITYC;
        }
    }
    
    G->arc[0][1]=1;
    G->arc[0][2]=5;
    G->arc[1][2]=3;
    G->arc[1][3]=7;
    G->arc[1][4]=5;
    
    G->arc[2][4]=1;
    G->arc[2][5]=7;
    G->arc[3][4]=2;
    G->arc[3][6]=3;
    G->arc[4][5]=3;
    
    G->arc[4][6]=6;
    G->arc[4][7]=9;
    G->arc[5][7]=5;
    G->arc[6][7]=2;
    G->arc[6][8]=7;
    
    G->arc[7][8]=4;
    
    
    for(i = 0; i < G->numVertexes; i++)
    {
        for(j = i; j < G->numVertexes; j++)
        {
            G->arc[j][i] =G->arc[i][j];
        }
    }
    
}

/* 11. 2
 Floyd算法,求網圖G中各頂點v到其余頂點w的最短路徑P[v][w]及帶權長度D[v][w]。
 Patharc 和 ShortPathTable 都是二維數組;
 */
void ShortestPath_Floyd(MGraph G, Patharc *P, ShortPathTable *D)
{
    int v,w,k;
    
    /* 1. 初始化D與P 矩陣*/
    for(v=0; v<G.numVertexes; ++v)
    {
        for(w=0; w<G.numVertexes; ++w)
        {
            /* D[v][w]值即為對應點間的權值 */
            (*D)[v][w]=G.arc[v][w];
             /* 初始化P P[v][w] = w*/
            (*P)[v][w]=w;
        }
    }
    
    //2.K表示經過的中轉頂點
    for(k=0; k<G.numVertexes; ++k)
    {
        for(v=0; v<G.numVertexes; ++v)
        {
            for(w=0; w<G.numVertexes; ++w)
            {
                /*如果經過下標為k頂點路徑比原兩點間路徑更短 */
                if ((*D)[v][w]>(*D)[v][k]+(*D)[k][w])
                {
                    /* 將當前兩點間權值設為更小的一個 */
                    (*D)[v][w]=(*D)[v][k]+(*D)[k][w];
                    /* 路徑設置為經過下標為k的頂點 */
                    (*P)[v][w]=(*P)[v][k];
                }
            }
        }
    }
}

int main(void)
{
    printf("Hello,最短路徑弗洛伊德Floyd算法");
    int v,w,k;
    MGraph G;
    
    Patharc P;
    ShortPathTable D; /* 求某點到其余各點的最短路徑 */
    
    CreateMGraph(&G);
    
    ShortestPath_Floyd(G,&P,&D);
    
    //打印所有可能的頂點之間的最短路徑以及路線值
    printf("各頂點間最短路徑如下:\n");
    for(v=0; v<G.numVertexes; ++v)
    {
        for(w=v+1; w<G.numVertexes; w++)
        {
            printf("v%d-v%d weight: %d ",v,w,D[v][w]);
            //獲得第一個路徑頂點下標
            k=P[v][w];
            //打印源點
            printf(" path: %d",v);
            //如果路徑頂點下標不是終點
            while(k!=w)
            {
                //打印路徑頂點
                printf(" -> %d",k);
                //獲得下一個路徑頂點下標
                k=P[k][w];
            }
            //打印終點
            printf(" -> %d\n",w);
        }
        printf("\n");
    }
    
    //打印最終變換后的最短路徑D數組
    printf("最短路徑D數組\n");
    for(v=0; v<G.numVertexes; ++v)
    {
        for(w=0; w<G.numVertexes; ++w)
        {
            printf("%d\t",D[v][w]);
        }
        printf("\n");
    }
    //打印最終變換后的最短路徑P數組
    printf("最短路徑P數組\n");
    for(v=0; v<G.numVertexes; ++v)
    {
        for(w=0; w<G.numVertexes; ++w)
        {
            printf("%d ",P[v][w]);
        }
        printf("\n");
    }
    
    return 0;
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容