數據結構(五)圖---最短路徑(迪傑斯特拉算法)


一:最短路徑問題

(一)定義

在網絡中,求兩個不同頂點之間的所有路徑中,邊的權值之和最小的那條路徑

1.這條路徑就是兩點之間的最短路徑
2.第一個頂點為源點
3.最后一個頂點終點

(二)分類

單源最短路徑--->有權,無權--->有向,無向

從某固定源點觸發,求其到所有其他頂點的最短路徑

多源最短路徑

求任意兩頂點間的最短路徑
可以通過對每個頂點使用一次單源(不是最好)

二:無權圖的單源最短路徑(有向)

不考慮無向,無向我們使用BFS,進行層次遍歷時,就可以獲取

(一)定義

按照遞增(非遞減)的順序找出各個頂點的最短路徑

找出視圖源點v3到每個頂點的最短路徑

(二)思考

從上圖路徑表我們可以看出,其路徑是按照BFS(有所不同),使用隊列進行遞增訪問各個頂點,從而遍歷了所有頂點。
注意:這里我們不使用棧來實現,因為棧用到回溯法,而且使用棧不能很好找到最短路徑

(三)代碼實現

創建鄰接矩陣時看這個圖                進行結果對比用這個

void unWeight(MGraph G, int s)
{
    int dist[MAXVEX];    //記錄達到下標對應頂點的最小距離
    int path[MAXVEX];    //記錄每個下標對應頂點的前一個經過的頂點
    int i, v, w;
    //生成隊列一會使用
    LinkQueue Q;
    InitQueue(&Q);

    for (i = 0; i < MAXVEX; i++)
        dist[i] = -1;    //全部初始化為-1,表示該頂點未被訪問過,沒有找到最短路徑到這個頂點
    //將源點入隊
    EnQueue(&Q, s);
    dist[s] = 0;
    path[s] = s;    //將這里設置為他自己是自己的上一步,因為后面根本不會去設置他了

    while (!EmptyQueue(Q))
    {
        DeQueue(&Q, &v);
        for (w = 0; w < G.numVertexes; w++)
        {
            if (G.arc[v][w] == 1)    //找到鄰接點w
            {
                if (dist[w] == -1)
                {
                    dist[w] = dist[v] + 1;
                    path[w] = v;
                    EnQueue(&Q, w);
                }
            }
        }
    }

    for (i = 0; dist[i] != -1; i++)  //對各個頂點的最短路徑長度進行打印,以及他的上一步路徑也打印
    {
        printf("%d %c-%c\n", dist[i], G.vers[path[i]], G.vers[i]);
    }
}

(四)全部代碼

#pragma once
#ifndef _QUEUE_H
#define _QUEUE_H

#include <stdio.h>
#include <stdlib.h>

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

#define MAXSIZE 100

typedef int ElemType;
typedef int Status;

typedef struct _qNode
{
    ElemType data;
    struct _qNode* next;
}QNode,*QNodePtr;

typedef struct
{
    QNodePtr front,rear;    //隊頭隊尾指針
}LinkQueue;

Status InitQueue(LinkQueue* Q);
Status EnQueue(LinkQueue* Q, ElemType e);
Status DeQueue(LinkQueue* Q, ElemType* e);
Status EmptyQueue(LinkQueue Q);
Status getHead(LinkQueue Q,ElemType* e);

#endif
queue.h
#include "queue.h"


Status InitQueue(LinkQueue* Q)
{
    if (!Q)
        return ERROR;
    Q->front = Q->rear = (QNodePtr)malloc(sizeof(QNode));
    if (!Q->front)
        return ERROR;
    Q->front->next = NULL;
    return OK;
}

Status EnQueue(LinkQueue* Q, ElemType e)
{
    //尾插法
    if (!Q)
        return ERROR;
    QNodePtr q = (QNodePtr)malloc(sizeof(QNode));
    if (!q)
        return ERROR;
    q->data = e;
    q->next = (*Q).rear->next;
    (*Q).rear->next = q;
    Q->rear = q;
    return OK;
}

Status DeQueue(LinkQueue* Q, ElemType* e)
{
    QNodePtr q;
    if (!Q || !e || EmptyQueue(*Q))
        return ERROR;
    q = Q->front->next;
    Q->front->next = q->next;
    *e = q->data;
    if (Q->rear == q)
        Q->rear = Q->front;
    free(q);
    return OK;
}

Status EmptyQueue(LinkQueue Q)
{
    if (!Q.front->next)
        return TRUE;
    return FALSE;
}

Status getHead(LinkQueue Q,ElemType* e)
{
    QNodePtr q;
    if (EmptyQueue(Q))
        return ERROR;
    q = Q.front->next;
    *e = q->data;
    return OK;
}
queue.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "queue.h"

#define MAXVEX 100    //最大頂點數
#define INFINITY 65535    //用0表示∞

typedef char VertexType;    //頂點類型,字符型A,B,C,D...
typedef int EdgeType;    //邊上權值類型10,15,...

//鄰接矩陣結構
typedef struct
{
    VertexType vers[MAXVEX];    //頂點表
    EdgeType arc[MAXVEX][MAXVEX];    //鄰接矩陣,可看作邊表
    int numVertexes, numEdges;    //圖中當前的頂點數和邊數
}MGraph;


//創建鄰接矩陣
void CreateMGraph(MGraph* G);
//顯示鄰接矩陣
void showGraph(MGraph G);
//進行最小路徑獲取
void unWeight(MGraph G);

int main()
{
    MGraph MG;
    CreateMGraph(&MG);
    showGraph(MG);
    unWeight(MG,2);
    system("pause");
    return 0;
}

//生成鄰接矩陣
void CreateMGraph(MGraph* G)
{
    int i, j, k, w;
    G->numVertexes = 7;
    G->numEdges = 12;
    //讀入頂點信息
    G->vers[0] = 'A';
    G->vers[1] = 'B';
    G->vers[2] = 'C';
    G->vers[3] = 'D';
    G->vers[4] = 'E';
    G->vers[5] = 'F';
    G->vers[6] = 'G';
    G->vers[7] = 'H';
    G->vers[8] = 'I';

    //getchar();    //可以獲取回車符
    for (i = 0; i < G->numVertexes; i++)
        for (j = 0; j < G->numVertexes; j++)
            G->arc[i][j] = INFINITY;    //鄰接矩陣初始化

    //創建了有向鄰接矩陣
    G->arc[0][1] = 1;
    G->arc[0][3] = 1;
    G->arc[1][3] = 1;
    G->arc[1][4] = 1;
    G->arc[2][0] = 1;
    G->arc[2][5] = 1;
    G->arc[3][2] = 1;
    G->arc[3][4] = 1;
    G->arc[3][5] = 1;
    G->arc[3][6] = 1;
    G->arc[4][6] = 1;
    G->arc[6][5] = 1;
}


//顯示鄰接矩陣
void showGraph(MGraph G)
{
    for (int i = 0; i < G.numVertexes; i++)
    {
        for (int j = 0; j < G.numVertexes; j++)
        {
            if (G.arc[i][j] != INFINITY)
                printf("%5d", G.arc[i][j]);
            else
                printf("    0");
        }
        printf("\n");
    }
}

void unWeight(MGraph G, int s)
{
    int dist[MAXVEX];    //記錄達到下標對應頂點的最小距離
    int path[MAXVEX];    //記錄每個下標對應頂點的前一個經過的頂點
    int i, v, w;
    //生成隊列一會使用
    LinkQueue Q;
    InitQueue(&Q);

    for (i = 0; i < MAXVEX; i++)
        dist[i] = -1;    //全部初始化為-1,表示該頂點未被訪問過,沒有找到最短路徑到這個頂點
    //將源點入隊
    EnQueue(&Q, s);
    dist[s] = 0;
    path[s] = s;    //將這里設置為他自己是自己的上一步,因為后面根本不會去設置他了

    while (!EmptyQueue(Q))
    {
        DeQueue(&Q, &v);
        for (w = 0; w < G.numVertexes; w++)
        {
            if (G.arc[v][w] == 1)    //找到鄰接點w
            {
                if (dist[w] == -1)
                {
                    dist[w] = dist[v] + 1;
                    path[w] = v;
                    EnQueue(&Q, w);
                }
            }
        }
    }

    for (i = 0; dist[i] != -1; i++)
    {
        printf("%d %c-%c\n", dist[i], G.vers[path[i]], G.vers[i]);
    }
}
無權最短路徑全部代碼

三:有權的單源最短路徑算法(迪傑斯特拉Dijkstra算法)

(一)了解

 

從v1-v6最小為6,即v1-v4-v7-v6。不一定為經過頂點最小的路,和上面的無權最短路徑不同

注意:我們不考慮負值圈

會導致一直循環,獲取無窮收益。導致所有算法都失效

(二)解決方法

方法和上面的無權路徑還是相似的,就是按照遞增的順序找出各個頂點的最短路

(三)迪傑斯特拉Dijkstra算法

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "queue.h"

#define MAXVEX 100    //最大頂點數
#define INFINITY 65535    //用0表示∞

typedef char VertexType;    //頂點類型,字符型A,B,C,D...
typedef int EdgeType;    //邊上權值類型10,15,...

//鄰接矩陣結構
typedef struct
{
    VertexType vers[MAXVEX];    //頂點表
    EdgeType arc[MAXVEX][MAXVEX];    //鄰接矩陣,可看作邊表
    int numVertexes, numEdges;    //圖中當前的頂點數和邊數
}MGraph;


//創建鄰接矩陣
void CreateMGraph(MGraph* G);
//顯示鄰接矩陣
void showGraph(MGraph G);
//迪卡斯特拉算法,獲取最短路徑
void Dijkatra(MGraph G, int s);

void Dijkatra(MGraph G,int s)
{
    int path[MAXVEX];    //是數組下標表示的頂點所經歷的前一個頂點
    int dist[MAXVEX];    //是數組下標表示的頂點的最小權值路徑和
    //上面兩個數組都有作用,和無權最短路徑一致,但是無權最短路徑可以使用dist是否被設置來判斷一個頂點是否被訪問,
    //但是這里無法使用,因為dist和普里姆算法中的lowcost一樣,是使用貪心算法時,每到一個頂點,我們都會全部更新dist
    //所以我們需要另外一個數組來標志各個頂點是否被訪問
    int final[MAXVEX];
    int i,j,k,min;

    //對數據進行初始化
    for (i = 0; i < G.numVertexes;i++)
    {
        final[i] = 0;    //0表示該數組下標所表示的頂點未被訪問
        path[i] = 0;    //初始化路徑數組為0,表示當前每個都是獨立的根節點
        dist[i] = G.arc[s][i];    //這一步是重點:初始化路徑數組的值為起始v0到各個點的權值
    }
    dist[s] = 0;    //到源點自己的路徑為0
    path[s] = s;    //設置源點的前一個頂點就是自己
    final[s] = 1;    //源點被訪問過了

    //開始主循環,每次求的v0(s)到某個v頂點的最短路徑
    for (i = 0; i < G.numVertexes;i++)
    {
        min = INFINITY;    //和普里姆算法相似
        for (j = 0; j < G.numVertexes;j++)    //由於是有向圖所以都要從0開始,找到他的每個鄰接點
        {
            if (!final[j]&&dist[j]<min)    //若是該頂點沒有被訪問過,且該點到s點的距離小於min,我們就將min設置為他
            {
                k = j;    //記錄下該v到s點的下標和min最小路徑
                min = dist[j];
            }
        }

        final[k] = 1;    //將目前找到的距離v0(S)最近的頂點置為1

        for (j = 0; j < G.numVertexes;j++)    //修正當前最短路徑即距離
        {
            //修正方法就是循環k的每個鄰接點,我們作為三角形來看,若是兩邊之和小於第三邊,那我們原來找的那條直接的最短邊就失效了,用這兩條直接代替
            //所以我們將距離修改,路徑設置為他的上一步k,
            if (!final[j]&&(min+G.arc[k][j])<dist[j])
            {
                //說明找到了更短的路徑,修改dist和path數組
                dist[j] = min + G.arc[k][j];    //修改當前路徑長度
                path[j] = k;
            }
        }
    }

    for (i = 0; i<G.numVertexes; i++)
    {
        printf("%d %c-%c\n", dist[i], G.vers[path[i]], G.vers[i]);
    }
}

int main()
{
    MGraph MG;
    CreateMGraph(&MG);
    showGraph(MG);
    Dijkatra(MG,0);
    system("pause");
    return 0;
}

//生成鄰接矩陣
void CreateMGraph(MGraph* G)
{
    int i, j, k, w;
    G->numVertexes = 7;
    G->numEdges = 12;
    //讀入頂點信息
    G->vers[0] = 'A';
    G->vers[1] = 'B';
    G->vers[2] = 'C';
    G->vers[3] = 'D';
    G->vers[4] = 'E';
    G->vers[5] = 'F';
    G->vers[6] = 'G';
    G->vers[7] = 'H';
    G->vers[8] = 'I';

    //getchar();    //可以獲取回車符
    for (i = 0; i < G->numVertexes; i++)
        for (j = 0; j < G->numVertexes; j++)
            G->arc[i][j] = INFINITY;    //鄰接矩陣初始化

    //創建了有向鄰接矩陣
    G->arc[0][1] = 2;
    G->arc[0][3] = 1;
    G->arc[1][3] = 3;
    G->arc[1][4] = 10;
    G->arc[2][0] = 4;
    G->arc[2][5] = 5;
    G->arc[3][2] = 2;
    G->arc[3][4] = 2;
    G->arc[3][5] = 8;
    G->arc[3][6] = 4;
    G->arc[4][6] = 6;
    G->arc[6][5] = 1;
}


//顯示鄰接矩陣
void showGraph(MGraph G)
{
    for (int i = 0; i < G.numVertexes; i++)
    {
        for (int j = 0; j < G.numVertexes; j++)
        {
            if (G.arc[i][j] != INFINITY)
                printf("%5d", G.arc[i][j]);
            else
                printf("    0");
        }
        printf("\n");
    }
}
全部代碼

void Dijkatra(MGraph G,int s)
{
    int path[MAXVEX];    //是數組下標表示的頂點所經歷的前一個頂點
    int dist[MAXVEX];    //是數組下標表示的頂點的最小權值路徑和
    //上面兩個數組都有作用,和無權最短路徑一致,但是無權最短路徑可以使用dist是否被設置來判斷一個頂點是否被訪問,
    //但是這里無法使用,因為dist和普里姆算法中的lowcost一樣,是使用貪心算法時,每到一個頂點,我們都會全部更新dist
    //所以我們需要另外一個數組來標志各個頂點是否被訪問
    int final[MAXVEX];
    int i,j,k,min;

    //對數據進行初始化
    for (i = 0; i < G.numVertexes;i++)
    {
        final[i] = 0;    //0表示該數組下標所表示的頂點未被訪問
        path[i] = 0;    //初始化路徑數組為0,表示當前每個都是獨立的根節點
        dist[i] = G.arc[s][i]; //這一步是重點:初始化路徑數組的值為起始v0到各個點的權值
    }
    dist[s] = 0;    //到源點自己的路徑為0
    path[s] = s;    //設置源點的前一個頂點就是自己
    final[s] = 1;    //源點被訪問過了

    //開始主循環,每次求的v0(s)到某個v頂點的最短路徑
    for (i = 0; i < G.numVertexes;i++)
    {
        min = INFINITY;    //和普里姆算法相似
        for (j = 0; j < G.numVertexes;j++)    //由於是有向圖所以都要從0開始,找到他的每個鄰接點
 { if (!final[j]&&dist[j]<min)    //若是該頂點沒有被訪問過,且該點到s點的距離小於min,我們就將min設置為他
 { k = j;    //記錄下該v到s點的下標和min最小路徑
               min = dist[j]; } } final[k] = 1;    //將目前找到的距離v0(S)最近的頂點置為1

        for (j = 0; j < G.numVertexes;j++)    //修正當前最短路徑即距離
        {
            //修正方法就是循環k的每個鄰接點,我們作為三角形來看,若是兩邊之和小於第三邊,那我們原來找的那條直接的最短邊就失效了,用這兩條直接代替
            //所以我們將距離修改,路徑設置為他的上一步k,
            if (!final[j]&&(min+G.arc[k][j])<dist[j]) { //說明找到了更短的路徑,修改dist和path數組 dist[j] = min + G.arc[k][j]; //修改當前路徑長度 path[j] = k; }
        }
    }

    for (i = 0; i<G.numVertexes; i++)
    {
        printf("%d %c-%c\n", dist[i], G.vers[path[i]], G.vers[i]);
    }
}

解釋:

迪傑斯特拉算法和普里姆算法和上面的無權最短路徑算法相似,前兩個紅線處也是重點。自己想想。

下面來看第三處

        for (j = 0; j < G.numVertexes;j++)    //修正當前最短路徑即距離
        {
            //修正方法就是循環k的每個鄰接點,我們作為三角形來看,若是兩邊之和小於第三邊,那我們原來找的那條直接的最短邊就失效了,用這兩條直接代替
            //所以我們將距離修改,路徑設置為他的上一步k,
            if (!final[j]&&(min+G.arc[k][j])<dist[j])
            {
                //說明找到了更短的路徑,修改dist和path數組
                dist[j] = min + G.arc[k][j];    //修改當前路徑長度
                path[j] = k;
            }
        }

我們選取源點的第一次循環來講解

1.首先:我們前面的代碼已經確定了源點(0)的最短路徑

例如上圖:我們確定了v0點的最短距離是v0-v3是1,所以我們將final[3]=1

2.我們在第三處,for循環中,修正的最短距離,不是我們v3距離,而是我們v3的鄰接點的最短距離。

 原來我們的dist是:

現在我們的for循環將去修正他,修正的方法是:

因為v1未被標記,而且min(就是d(v0-v3))+d(v3-v1)=1+3大於原來的dist[1]=2,所以不予理會

因為v2未被標記,而且min(就是d(v0-v3))+d(v3-v2)=1+2小於原來的dist[2]=4,所以我們將他的距離修改,變為dist[2]=min+E(3,2),將他的路徑也做修正,他的是一個頂點變為v3,path[2]=3

修正后的dist數組是:

 

        for (j = 0; j < G.numVertexes;j++)    //修正當前最短路徑即距離
        {
            //修正方法就是循環k的每個鄰接點,我們作為三角形來看,若是兩邊之和小於第三邊,那我們原來找的那條直接的最短邊就失效了,用這兩條直接代替
            //所以我們將距離修改,路徑設置為他的上一步k,
            if (!final[j]&&(min+G.arc[k][j])<dist[j])
            {
                //說明找到了更短的路徑,修改dist和path數組
                dist[j] = min + G.arc[k][j];    //修改當前路徑長度
                path[j] = k;
            }
        }

最后:說一句

有向圖和無向圖無非就是矩陣不對稱而已,求最短路徑幾乎是一致的。所以不必考慮太多
Dijkstra算法解決了從某個頂點到其余各頂點的最短路徑。其時間復雜度是O(n*2)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM