最短路徑—Dijkstra演算法(C#)
前言 :
因為之前剛寫過最小生成樹演算法,剛開始看到Dijkstra演算法的時候,因為要求各點到源點的最短距離,會想著直接從最小生成樹的也是每找到最短的距離點加進來,但是後面仔細想了下,又翻了下定義,得到 最短路徑是一個圖中2個點的最短距離。 最小生成樹是連線所有的點的路徑最短,但是不一定是任意兩點的距離最小,例如:最小生成樹是:A->B->C AC距離是2+2=4 但是AC的最短距離是3
一 最短路徑問題
最短路徑問題是圖論研究中的一個經典演算法問題,旨在尋找圖(由結點和路徑組成的)中兩結點之間的最短路徑。演算法具體的形式包括:
1.確定起點的最短路徑問題:即已知起始結點,求最短路徑的問題。適合使用Dijkstra演算法。
2.確定終點的最短路徑問題:與確定起點的問題相反,該問題是已知終結結點,求最短路徑的問題。在無向圖中該問題與確定起點的問題完全等同,在有向圖中該問題等同於把所有路徑方向反轉的確定起點的問題。
3.確定起點終點的最短路徑問題:即已知起點和終點,求兩結點之間的最短路徑。
4.全域性最短路徑問題:求圖中所有的最短路徑。適合使用Floyd演算法。
這裡我們在這篇中只分析第一種,第四種在下一篇部落格將會給出。
二 Dijkstra定義概述
Dijkstra(迪傑斯特拉)演算法是典型的最短路徑路由演算法,用於計算一個節點到其他所有節點的最短路徑。主要特點是以起始點為中心向外層層擴充套件,直到擴充套件到終點為止(BFS、prime演算法都有類似思想)。Dijkstra演算法能得出最短路徑的最優解,但由於它遍歷計算的節點很多,所以效率低。時間複雜度為O(n^2)。
三演算法描述
3.1 演算法思想
設G=(V,E)是一個帶權有向圖,把圖中頂點集合V分成兩組,第一組為已求出最短路徑的頂點集合(用S表示,初始時S中只有一個源點,以後每求得一條最短路徑 , 就將加入到集合S中,直到全部頂點都加入到S中,演算法就結束了),第二組為其餘未確定最短路徑的頂點集合(用U表示),按最短路徑長度的遞增次序依次把第二組的頂點加入S中。在加入的過程中,總保持從源點v到S中各頂點的最短路徑長度不大於從源點v到U中任何頂點的最短路徑長度。此外,每個頂點對應一個距離,S中的頂點的距離就是從v到此頂點的最短路徑長度,U中的頂點的距離,是從v到此頂點只包括S中的頂點為中間頂點的當前最短路徑長度。
3.2 演算法步驟:
a.初始時,S只包含源點,即S={v},v的距離為0。U包含除v外的其他頂點,即:U={其餘頂點},若v與U中頂點u有邊,則<u,v>正常有權值,若u不是v的出邊鄰接點,則<u,v>權值為∞。
b.從U中選取一個距離v最小的頂點k,把k,加入S中(該選定的距離就是v到k的最短路徑長度)。
c.以k為新考慮的中間點,修改U中各頂點的距離;若從源點v到頂點u的距離(經過頂點k)比原來距離(不經過頂點k)短,則修改頂點u的距離值,修改後的距離值的頂點k的距離加上邊上的權。
d.重複步驟b和c直到所有頂點都包含在S中。
3.3 具體演算法實現:
3.1和3.2的內容都是書上所描述的,這裡之所以寫到,是想讓大家先理解源點到各點的最短路徑的演算法思想大致是怎樣的?但是對於如何從U中選取一個距離v最小的點是沒有具體方案的,具體用演算法實現的時候,這裡我是巧妙的利用了鄰接矩陣來做的。大致思路:首先我們用鄰接矩陣來存圖,我們的最終目的是要求V1(源點)到其他點的最小距離,體現在鄰接矩陣中,V0(源點)到其他點的初始距離就是第一行的元素值,如果我們想通過各種變換,運算,改變第一行的元素值使其是V0(源點)到其他點的最小距離,這樣就很簡單了,即通過3.1 3.2的演算法思路,來進行鄰接矩陣上的操作,使其第一行的元素值就是V0(源點)到其他點的最小距離。。
四 算例
有向圖如下:
鄰接矩陣為graph:
V0 |
V1 |
V2 |
V3 |
V4 |
V5 |
|
V0 |
10000 |
10000 |
10 |
10000 |
30 |
100 |
V1 |
10000 |
10000 |
5 |
10000 |
10000 |
10000 |
V2 |
10000 |
10000 |
10000 |
50 |
10000 |
10000 |
V3 |
10000 |
10000 |
10000 |
10000 |
10000 |
10 |
V4 |
10000 |
10000 |
10000 |
10000 |
20 |
60 |
V5 |
10000 |
10000 |
10000 |
10000 |
10000 |
1000 |
4.1 變數介紹:
鄰接矩陣陣列:graph,
下一個點:next,最小值min,中間路線圖陣列(6個點的)mid
4.2 整體思路:
1)首先將集合V0新增入S集合,遍歷第一行(graph[0][j]),找到最小的值對應的列值點j.
2)記錄資料:將下一個點next =j 新增入S集合,V0-->Vj最小值min=graph[0][j]。
3)重新初始第一行的值:迴圈graph[next][j],判斷(graph[next][j]+min)是否小於graph[0][j](j不在S集合中),若是,則替換後者並記錄過程 mid[j] = mid[next] + "->V" + next;
4)迴圈1),2),3)步驟,直至迴圈次數為點的個數-1,即6-1=5。
4.3 圖形化步驟如下:
(1) 找到第一行的最小值min=10,得到下一個點Next=2,S={0,2},路徑為:V0->V2
(2)將V2行的值加上min=10,與第一行V0,V2內的元素值對比,若小於,則替換.
(替換了graph[0][4]=60)。變換為下面矩陣,然後繼續找第一行除V0,V2外的最小值min為30,得到下一個點Next=4,S={0,2,4},路徑為:V0->V4
V0 |
V1 |
V2 |
V3 |
V4 |
V5 |
|
V0 |
10000 |
10000 |
10 |
60 |
30 |
100 |
V1 |
10000 |
10000 |
5 |
10000 |
10000 |
10000 |
V2 |
10000 |
10000 |
10000 |
50 |
10000 |
10000 |
V3 |
10000 |
10000 |
10000 |
10000 |
10000 |
10 |
V4 |
10000 |
10000 |
10000 |
10000 |
20 |
60 |
V5 |
10000 |
10000 |
10000 |
10000 |
10000 |
1000 |
(3)將V4行的值加上min=30,與第一行除V0,V2,V4內的元素值對比,若小於,則替換.
(替換了graph[0][3]=20+30=50,graph[0][5]=60+30=90)。變換為下面矩陣,然後繼續找第一行除V0,V2,V4外的最小值min為50,得到下一個點Next=3,S={0,2,4,3},路徑為:V0->V4->V3
V0 |
V1 |
V2 |
V3 |
V4 |
V5 |
|
V0 |
10000 |
10000 |
10 |
50 |
30 |
90 |
V1 |
10000 |
10000 |
5 |
10000 |
10000 |
10000 |
V2 |
10000 |
10000 |
10000 |
50 |
10000 |
10000 |
V3 |
10000 |
10000 |
10000 |
10000 |
10000 |
10 |
V4 |
10000 |
10000 |
10000 |
10000 |
20 |
60 |
V5 |
10000 |
10000 |
10000 |
10000 |
10000 |
10000 |
(4)將V3行的值加上min=50,與第一行除V0,V2,V4,V3內的元素值對比,若小於,則替換.
(替換了graph[0][5]=graph[3][5]+50=10+50=60)。變換為下面矩陣,然後繼續找第一行除V0,V2,V4,V3外的最小值min為60,得到下一個點Next=5,S={0,2,4,3,5},路徑為:V0->V4->V3->V5
V0 |
V1 |
V2 |
V3 |
V4 |
V5 |
|
V0 |
10000 |
10000 |
10 |
50 |
30 |
60 |
V1 |
10000 |
10000 |
5 |
10000 |
10000 |
10000 |
V2 |
10000 |
10000 |
10000 |
50 |
10000 |
10000 |
V3 |
10000 |
10000 |
10000 |
10000 |
10000 |
10 |
V4 |
10000 |
10000 |
10000 |
10000 |
20 |
60 |
V5 |
10000 |
10000 |
10000 |
10000 |
10000 |
10000 |
(沒有替換)。矩陣不變,迴圈5次結束。
大家走過看過,給個贊吧撒~長得帥的都有這習慣呢·~~
4.4 執行結果圖如下:
五 演算法應用
源點到各點的最短距離: 後面完善完整程式碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace shortestpath
{
class Program
{
static int[,] graph = new int[6, 6] { { 10000, 10000, 10, 10000, 30, 100 }, { 10000, 10000, 5, 10000, 10000, 10000 }, { 10000, 10000, 10000, 50, 10000, 10000 }, { 10000, 10000, 10000, 10000, 10000, 10 }, { 10000, 10000, 10000, 20, 10000, 60 }, { 10000, 10000, 10000, 10000, 10000, 10000 } };
static int[] S = new int[6] { 0, 0, 0, 0, 0, 0 };//最短路徑的頂點集合
static string[] mid = new string[6]{"","","","","",""};//點的路線
public static int IsContain(int m)//判斷元素是否在mst中
{
int index = -1;
for (int i = 1; i < 6; i++)
{
if (S[i] == m)
{
index = i;
}
}
return index;
}
/// <summary>
/// Dijkstrah實現最短路演算法
/// </summary>
static void ShortestPathByDijkstra()
{
int min;
int next;
for (int f = 5; f > 0;f--)
{
//置為初始值
min = 1000;
next = 0;//第一行最小的元素所在的列 next點
//找出第一行最小的列值
for (int j = 1; j < 6; j++)//迴圈第0行的列
{
if ((IsContain(j) == -1) && (graph[0, j] < min))//不在S中,找出第一行最小的元素所在的列
{
min = graph[0, j];
next = j;
}
}
//將下一個點加入S
S[next] = next;
//輸出最短距離和路徑
if (min == 1000)
{
Console.WriteLine("V0到V{0}的最短路徑為:無", next);
}
else
{
Console.WriteLine("V0到V{0}的最短路徑為:{1},路徑為:V0{2}->V{0}", next, min, mid[next]);
}
// 重新初始0行所有列值
for (int j = 1; j < 6; j++)//迴圈第0行的列
{
if (IsContain(j) == -1)//初始化除包含在S中的
{
if ((graph[next, j] + min) < graph[0, j])//如果小於原來的值就替換
{
graph[0, j] = graph[next, j] + min;
mid[j] = mid[next] + "->V" + next;//記錄過程點
}
}
}
}
}
static void Main(string[] args)
{
ShortestPathByDijkstra();
}
}
}