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

一:最短路徑問題

(一)定義

在網絡中,求兩個不一樣頂點之間的全部路徑中,邊的權值之和最小的那條路徑

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)
相關文章
相關標籤/搜索