一、Floyd算法——只有五行代碼的算法
算法思想:
從節點i到節點j的最短路徑只有2種可能:
1)直接從節點i到節點j
2)從節點i經過若干個節點k到節點j
假設dis(i,j)爲節點i到節點j的最短路徑的距離
對於每一個節點k,我們檢查dis(i,k)+dis(k,j) < dis(i,j)是否成立,
如果成立,證明從i到k再到j的路徑比i直接到j的路徑短.
於是dis(i,j) = dis(i,k) + dis(k,j),這樣一來,當我們遍歷完所有k,dis(i,j)中記錄的便是節點i到節點j的最短路徑的距離
缺點:時間複雜度太高 O(n^3)
模板:
void Floyd()
{
for(int k = 1; k <= N; k++)
for(int i = 1; i <= N; i++)
for(int j = 1; j < N; j++)
if(dis[i][k] + dis[k][j] < dis[i][j])
dis[i][j] = dis[i][k] + dis[k][j];
}
二、Dijkstra算法
如下圖:
我們還需要用一個一維數組 dis 來存儲 1 號頂點到其餘各個頂點的初始路程,如下:
既然是求 1 號頂點到其餘各個頂點的最短路程,那就先找一個離 1 號頂點最近的頂點。
通過數組 dis 可知當前離 1 號頂點最近是 2 號頂點。當選擇了 2 號頂點後,即 1 號頂點到 2 號頂點的最短路程就是dis[2]的值
既然選了 2 號頂點,接下來看 2 號頂點有 2-3 和 2-4 這兩條邊。
先思考通過 2->3 這條邊能否讓 1 號頂點到 3 號頂點的路程變短。也就是說現在來比較 dis[3]和 dis[2]+e[2][3]的大小。其中 dis[3]表示 1 號頂點到 3 號頂點的路程,dis[2]+e[2][3]中 dis[2]表示 1 號頂點到 2 號頂點的路程,e[2][3]表示 2-3 這條邊。所以 dis[2]+e[2][3]就表示從 1 號頂點先到 2 號頂點,再通過 2->3 這條邊,到達 3 號頂點的路程。
我們發現 dis[3]=12,dis[2]+e[2][3]=1+9=10,dis[3]>dis[2]+e[2][3],因此 dis[3]要更新爲 10,以此類推。
基本思想:每次找到離源點最近的一個頂點,然後以該頂點爲中心進行擴展,最終得到源點到其餘所有點的最短路徑。
時間複雜度:O(n^2)
代碼:
1.初始化
void mem()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(i==j)e[i][j]=0;
else e[i][j]=INF;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&v1,&v2,&w);
e[v1][v2]=w;
}
for(int i=1;i<=n;i++)dis[i]=e[1][i];
for(int i=1;i<=n;i++)vis[i]=0;
vis[1]=1;
}
2.算法核心
void Dijkstra()
{
for(int i=1;i<=n-1;i++)
{
int min_num=INF;
int min_index=0;
for(int k=1;k<=n;k++)
if(min_num>dis[k] && book[k]==0)
{
min_num=dis[k];
min_index=k;
}
book[min_index]=1;
for(int j=1;j<=n;j++)
if(e[min_index][j]<INF)
if(dis[j]>dis[min_index]+e[min_index][j])
dis[j]=dis[min_index]+e[min_index][j];
}
}
三、SPFA算法
算法優點:
1.時間複雜度比普通的Dijkstra和Ford低。
2.能夠計算負權圖問題。
實現方法:
1.存入圖。可以使用鏈式前向星或vector。
2.開一個隊列,先將開始的節點入隊。
3.每次從隊列中取出一個節點X,遍歷與X相通的Y節點,比對 b的長度 和 a的長度+ a與b的長度
如果a的長度+ a與b的長度 > b的長度——需要更新。
1)存入最短路。
2)由於改變了原有的長度,所以需要往後更新,與這個節點相連的最短路。(即:判斷下是否在隊列,在就不用重複,不在就加入隊列,等待更新)。
3)在這期間可以記錄這個節點的進隊次數,判斷是否存在負環。
4.直到隊空。
判斷有無負環:如果某個點進入隊列的次數超過N次則存在負環
代碼:
void SPFA()//核心代碼
{
queue <int> q;
memset(vis,inf,sizeof(vis));
memset(ven,0,sizeof(ven));
memset(nums,0,sizeof(nums));
vis[s]=0;
ven[s]=1;
q.push(s);
while(!q.empty())
{
int x=q.front();
q.pop();
ven[x]=0;
for(int i=pre[x]; ~i; i=a[i].next)
{
int y=a[i].y;
if(vis[y]>vis[x]+a[i].time)
{
vis[y]=vis[x]+a[i].time;
if(!ven[y])
{
q.push(y);
ven[y]=1;
}
}
}
}
}
存圖代碼:(鏈式前向星)
void add(int x,int y,int k)//鏈式前向星,加入節點
{
a[cent].y=y;
a[cent].time=k;
a[cent].next=pre[x];
pre[x]=cent++;
}
參考資料:
《啊哈,算法》
http://keyblog.cn/article-21.html