最短路算法

與最小生成樹有些不一樣.在這里提出三種算法.
dijkstra算法,是最普通也是最簡單的.與prim算法有些類似,但適用范圍又不一樣,有打印路徑和不打印路徑的小分法.注意不能用于權值有負的圖!!!(而且可以用優先隊列優化,可以查一下模板,也比較簡單).
首先是不打印路徑的dijkstra,類似于DP.(打印單源路徑,即某一特定點到其他點的距離)

最近發現一個坑點,初始化地圖時最好放在輸入前面,然后輸入信息時要求小于對應地圖的位置時才存入地圖中,因為我們要求的是最短路,必須保證地圖中是兩點的最短距離!!!若之后又輸入了某點的信息后距離是比之前的更長的則不能替換的,否則求出來的就不是最短路了.!!!

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxp = 2005;
const int INF= 1000000;
int edge[maxp][maxp];
int near[maxp];
int low[maxp];
int n;

void dijkstra(int u)//從u點開始找出距離所有的點最短距離.
{   memset(low,0,sizeof(low));
    for(int i=0;i<n;i++)
        near[i]=edge[u][i];
    low[u]=1;
    for(int i=0;i<n;i++){
        int minn=INF;
        int v=-1;
        for(int j=0;j<n;j++){
            if(!low[j] && near[j]<minn)
                v=j,minn=near[j];
        }
        low[v]=1;
        for(int j=0;j<n;j++)
            near[j]=min(near[j],edge[v][j]+near[v]);//這樣寫更簡單,不用那個if也行,因為不會影響原先那個最短的.
            //加那個if時間更快點而已,也體現了這個算法不能使用與權值有負的邊.
            //如果要打印路徑,則這樣就不行.好好想想
    }

    for(int i=0;i<n;i++){
        printf("%d ",near[i]);
    }
    printf("\n");
}

int main()//輸入哪里怎么改才好輸入就是自己的事.
{
    int i,j;
    int u,v,w;
    scanf("%d",&n);
    for(i=0;i<n;i++){
        for(j=0;j<n;j++){
            if(i==j)  edge[i][j]=0;
            else  edge[i][j]=INF;
        }
    }
    while(1)
    {
        scanf("%d %d %d",&u,&v,&w); //讀入邊的起點和終點
        if(u==-1 && v==-1 && w==-1) break;
        if(w<edge[u][v])  //這樣就可以保證圖中存的是兩點間的最短距離.
           edge[u][v]=w;   //構造鄰接矩陣(這里的輸入也說明了它是單向的,既有向)//對應的無向圖改成雙向的就行了.
    }
    dijkstra(0);//從0開始到所有點的最短距離.(要求那個點,就傳那個點進去就行了)
/*--------打印臨接矩陣------
    for(int i = 0 ; i <  n ; i++)
    {
        for(int j = 0 ; j < n ; j++)
        {
            printf("%6d ",edge[i][j]);
        }
        printf("\n");
    }
--------------------------*/
}

打印路徑的.

#include<cstdio>
#include<cstring>
#define INF 1000000 //無窮大
#define maxn 20   //頂點個數的最大值
int n;//頂點個數
int Edge[maxn][maxn];
int s[maxn];
int dist[maxn];
int path[maxn];//因為要打印路徑,所以需要一個數組保存這條邊來自于那一條邊,這樣回溯輸出便可輸出路徑.

void dijkstra(int v0)//求v0到其他點的最短路徑.
 {  int i,j,k;
    for(i=0;i<n;i++)
    {
        dist[i]=Edge[v0][i];
        s[i]=0;//初始化s數組,用于保存找過的點.
        if(i!=v0 && dist[i]<INF)  path[i]=v0;
        else   path[i]=-1;
    }
    s[v0]=1;//頂點v0加入到頂點集合s中.
    for(int i=1;i<n;i++)
    {   int mini=INF,u=v0;//選擇當前集合t中具有最短路徑的頂點u;
        for(int j=0;j<n;j++){
            if(!s[j] && dist[j] < mini){  //選擇下一次到已知頂點最短的點。
                u=j,mini=dist[j];
            }
        }
        s[u]=1;//將頂點u加入到集合s,表示他的最短路徑已求到.
        //修改t集合中頂點dist和path數組元素值.
        for(j=0;j<n;j++){
            if(!s[j] && dist[u] + Edge[u][j] < dist[j]){  //未被標記且比已知的短,可更新
                dist[j]=dist[u] + Edge[u][j] ;
                path[j]=u;
            }
        }
    }
 }

int main()
{
    int i,j;//循環變量.
    int u,v,w;//邊的起點與終點及權值.
    scanf("%d",&n);//頂點個數
    for(i=0;i<n;i++){
       for(j=0;j<n;j++){
            if(i==j)  edge[i][j]=0;
            else  edge[i][j]=INF;
        }
    }
    while(1)
    {
        scanf("%d %d %d",&u,&v,&w); //讀入邊的起點和終點
        if(u==-1 && v==-1 && w==-1) break;
       if(w<mapp[u][v])  //這樣就可以保證圖中存的是兩點間的最短距離.
           Edge[u][v]=w;   //構造鄰接矩陣(這里的輸入也說明了它是單向的,既有向)//對應的無向圖改下就行了.
    }
    dijkstra( 0 );//求頂點0到其他路徑的最短路徑.//求單源路勁最短.
    int shortest[maxn];//輸出最短路徑上的各個頂點時存放各個頂點的序號.
    for(i=1;i<n;i++){
        printf("%d\t",dist[i]);//輸出頂點0到頂點i的最短路徑長度.
        memset(shortest ,0,sizeof(shortest));//path數組的作用在這里,因為后面要輸出,所以必須要一個數組來保存路徑.
        int k=0;
        shortest[k]=i;
        /*for(int i=0;i<n;i++){
            printf("%d ",path[i]);
        }
        putchar('\n');*/
        while(path[shortest[k]]!=0)//這里的作用就是回溯輸出路徑.不懂就寫出來看下就可以了.
        {
            k++;
            shortest[k]=path[shortest[k-1]];
        }
        k++;
        shortest[k]=0;
        for(j=k;j>0;j--)
            printf("%d-->",shortest[j]);
        printf("%d\n",shortest[0]);
    }
}

第二種 最暴力 的算法,floyd(適用于點數比較少的情況,允許有權值為負的邊)(可以打印任意兩點間的最短距離)(復雜度飛常高,不是一定要用,則盡量不要用)

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=1e4+5;
const int inf=1e9+7;
int edge[maxn][maxn];//點i到j的距離.
int n;
void floyd()//可以找到任意兩點的最短距離.
{
    for(int k=0;k<n;k++){
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                edge[i][j]=min(edge[i][j],edge[i][k]+edge[k][j]);
                //暴力每一點到每一點所有的可能性,三重循環的純暴力方法.
                //如果不是很明白,則寫出來看看就可以了.
                //循環順序不能變,否則會WA,現在不是太懂,等懂了再來解釋,現在先記住順序必須是這樣的!
//因為必須要固定中間那個點, 去枚舉起點和終點, 這樣對于每一條邊都會去松弛, .
//而交換了順序后是中間那個點在不斷變換, 所以對于有些邊更新時還是初始值之間進行更新,這樣就是錯的.
            }
        }
    }
}
/*核心思想為開始允許可以從第一個點中轉,然后允許才能夠1,2中轉,到最后1~n中轉,找到每兩個點之間的最短路.*/

int main()
{
    int i,j;//循環變量.
    int u,v,w;//邊的起點與終點及權值.
    scanf("%d",&n);//頂點個數
    for(i=0;i<n;i++){
       for(j=0;j<n;j++){
            if(i==j)  edge[i][j]=0;
            else  edge[i][j]=INF;
        }
    }
    while(1)
    {
        scanf("%d %d %d",&u,&v,&w); //讀入邊的起點和終點
        if(u==-1 && v==-1 && w==-1) break;
        if(w<mapp[u][v])  //這樣就可以保證圖中存的是兩點間的最短距離.
            edge[u][v]=w;   //構造鄰接矩陣(這里的輸入也說明了它是單向的,既有向)//對應的無向圖改下就行了.
    }
    /*for(i=0;i<n;i++)//打印鄰接矩陣.
    {
        for(j=0;j<n;j++)
        {
            printf("%d ",edge[i][j]);
        }
        printf("\n");
    }*/
    floyd();
    for(int i=0;i<=5;i++){
        printf("%d ",edge[1][i]);//打印1到其余點的最短路徑.//可以打印任何點到任一點的距離.
    }
    printf("\n");
}

第三種,SPFA算法,這個算法是最好的,也可以適用于權值有負的邊,時間復雜度也是最低的.(也是可以優化的+SLF,可以去網上搜搜模板)(求源點到其余點的最短距離)

(這個是單純求最短路的,下面還有用這個算法去求該圖有無負環的)
(判斷負環就是對每一個入隊點數進行標記,當入隊次數超過了一定的范圍是,可以判斷該圖中存在負環可以上網搜搜(一般圖中為2次,具體怎么證明的也不是很清楚))
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<queue>
#define CLR(x) memset(x,0,sizeof(x))
using namespace std;
const int maxn=1e6;
const int maxp=1e4+5;
const int eps=1e-6;
const int inf=1e9;      //這個inf必須足夠大,否則會影響后面的判斷的.(int 不夠 就用 ll  )
int cas=1;
int edge[maxp][maxp];
int vis[maxp],dis[maxp];
queue<int>q;
int n,m;
void spfa(int u)
{
    CLR(vis);
    vis[u]=1;
    q.push(u);
    dis[u]=0;
    while(!q.empty())
    {
        int tmp=q.front();
        q.pop();
        vis[tmp]=0;//消除標記.
        for(int i=1;i<=n;i++){
            if(dis[i] > edge[tmp][i]+dis[tmp]){
                dis[i]=edge[tmp][i]+dis[tmp];
                if(!vis[i]){
                    vis[i]=1;//進行入隊標記.
                    q.push(i);
                }
            }
        }
    }
}
int main()//水題是hdu2544(三種方法都適用)
{
    while(~scanf("%d %d",&n,&m)){
        if(n==0 && m==0)
            break;
       for(int i=1;i<=n;i++){
            dis[i]=inf;
            for(int j=1;j<=n;j++){
                if(i==j)   edge[i][j]=0;
                else  edge[i][j]=inf;
            }
        }
        for(int i=0;i<m;i++){
            int u,v,w;
            scanf("%d %d %d",&u,&v,&w);
             if(w<edge[u][v])  //這樣就可以保證圖中存的是兩點間的最短距離.
                 edge[u][v]=edge[v][u]=w;
        }
        /*for(int i=1;i<=n;i++){//打印臨接表出來看下.
            for(int j=1;j<=n;j++){
                printf("%d ",edge[i][j]);
            }
            printf("\n");
        }*/
        while(!q.empty())
        {
            q.pop();
        }
        spfa(1);//從1到其他任何點的最短距離.
        printf("%d\n",dis[n]);
    }
}

所以題目要是要求求任意兩點間的最短距離,則用floyd算法

spfa算法的棧寫法(其實跟隊列是基本一樣的,就是把隊列的地方改成棧就可以了,就是可能內存或運行效率有點不同,看下知道有這種寫法就可以了)

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<stack>
#define CLR(x) memset(x,0,sizeof(x))
using namespace std;
const int maxn=1e6;
const int maxp=1e4+5;
const int eps=1e-6;
const int inf=1e9;    //這個inf必須足夠大,否則會影響后面的判斷的.(int 不夠 就用 ll  )
int cas=1;
int edge[maxp][maxp];
int vis[maxp],dis[maxp];
stack<int>s;
int n,m;
void spfa_dfs(int u)
{
    CLR(vis);
    vis[u]=1;
    s.push(u);
    dis[u]=0;
    while(!s.empty())
    {
        int tmp=s.top();
        s.pop();
        vis[tmp]=0;
        for(int i=1;i<=n;i++){
            if(dis[i] > edge[tmp][i]+dis[tmp]){
                dis[i]=edge[tmp][i]+dis[tmp];
                if(!vis[i]){
                    vis[i]=1;
                    s.push(i);
                }
            }
        }
    }
}
int main()//水題是hdu2544(三種方法都適用)
{
    while(~scanf("%d %d",&n,&m)){
        if(n==0 && m==0)
            break;
       for(int i=1;i<=n;i++){
            dis[i]=inf;
            for(int j=1;j<=n;j++){
                if(i==j)   edge[i][j]=0;
                else  edge[i][j]=inf;
            }
        }
        for(int i=0;i<m;i++){
            int u,v,w;
            scanf("%d %d %d",&u,&v,&w);
            if(w<edge[u][v])  //這樣就可以保證圖中存的是兩點間的最短距離.
                edge[u][v]=edge[v][u]=w;
        }
        /*for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                printf("%d ",edge[i][j]);
            }
            printf("\n");
        }*/
        while(!s.empty())
            s.pop();
        spfa_dfs(1);//從1到其他任何點的最短距離.
        printf("%d\n",dis[n]);
    }
}
//對比也能發現根本就沒什么兩樣.

附上有臨接鏈表寫的spfa,因位大部分都是用的這種方法寫的,據說是更快,更省內存.(如果要寫spfa算法的話,就用這種方法寫,就不要用上面兩種方法了,避免超時!)
這是水題 poj --- 3013 題目在此的spfa寫法.
(對于最短路中,dij要T的就一般用spfa了,即對于點和路比較多的那種用spfa,點比較少的用普通的dij和floyd就行.)

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<queue>
#include<cstdlib>
#define CLR(x) memset(x,0,sizeof(x))
#define ll long long int
using namespace std;
const int maxn=1e6+5;
const ll inf=1e15;      //這個inf必須足夠大才行,否則會一直WA.!!!這是坑點,坑了我好久.
int head[maxn];
int weight[maxn];
ll dis[maxn],vis[maxn];
int n,m,ans;
struct node
{
    int to;
    int v;
    int next;
}edge[maxn];    //邊跟點的范圍要分清楚,不要開成一樣的了,否則會WA!!!

void add(int a,int b,int v)
{
    edge[ans].to=b;   //a可以到b點.
    edge[ans].v=v;    //v是兩點間的距離.
    edge[ans].next=head[a];    //結束標記,表示沒有再于a點相連的點了.
    head[a]=ans++;
}

void spfa(int u)
{
    queue<int >q;
    dis[u]=0;
    vis[u]=1;
    q.push(u);
    while(!q.empty()){
        int k=q.front();   //從不斷入隊中的元素中不斷進行循環的那個步驟,直到隊列為空.
        //printf("%d\n",k);
        q.pop();
        vis[k]=0;
        for(int i=head[k];i!=-1;i=edge[i].next)//懂不起寫出來就可以了.
        {          //headx表示提取與x直接相連的點的信息.然后直到提取到head為-1時(初始化值),表示提取完畢,即結束.(然后進行下一個開頭的提取.)
            int m=edge[i].to;
            if(dis[m]>dis[k]+edge[i].v){//意思是u點到m點的距離如果大于u點到k點再到m點的距離的話.
                dis[m]=dis[k]+edge[i].v;
                if(!vis[m]){
                    vis[m]=1;
                    q.push(m);
                }
            }
        }
    }
}

void solve()
{
    CLR(vis);
    memset(head,-1,sizeof(head));
    ans=0;
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++){
        dis[i]=inf;
        scanf("%d",&weight[i]);
    }
    for(int i=0;i<m;i++){    //因為這個
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);(保證兩個點之間不會有多條路).
        add(u,v,w);//因為是無向圖,所以要加兩次.
        add(v,u,w);
    }
    spfa(1);
    ll ans=0;
    int flag=1;
    for(int i=2;i<=n;i++){
        if(dis[i]==inf){
            flag=0;
            break;
        }
        ans+=weight[i]*dis[i];//為什么這樣算也可以得出正確答案,是推出來的.請看下面
        printf("%I64d\n",ans);
    }
    if(flag)
        printf("%I64d\n",ans);
    else
        printf("No Answer\n");//不能將所有點都連接起來.

}
int main()  //用的臨接鏈表做的.速度快,內存小.(用的數組模擬的)
{
    int t;
    scanf("%d",&t);
    while(t--)
        solve();
}
/*
第二個樣例中的計算方法
4*40+3*50+2*60+3*(20+40+50+60)+2*30+1*(10+20+30+40+50+60)
=10*1+20*(1+3)+30*(2+1)+40*(4+1+3)+50*(3+1+3)+60*(1+2+3)
=10+80+90+320+350+360
=1210
所以得出來的公式.(ans+=weight[i]*dis[i]).
*/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • [編程題]比較重量小明陪小紅去看鉆石,他們從一堆鉆石中隨機抽取兩顆并比較她們的重量。這些鉆石的重量各不相同。在他們...
    駭客與畫家閱讀 839評論 0 0
  • Floyd算法 【坐在馬桶上看算法】算法6:只有五行的Floyd最短路算法最短路徑—Dijkstra算法和Floy...
    deactivateuser閱讀 332評論 0 0
  • 最短路算法算是基礎算法, 我還是總是忘。。維基有個動圖很好,比較直觀,可是還不夠友好,于是自己做了點筆記,僅供參考...
    treelake閱讀 5,839評論 2 19
  • 題意:第一個點是青蛙的坐標,第二個是青蛙妹子的坐標,其他的點是石頭的坐標,現在要問青蛙到青蛙妹子的地方,至少需要跳...
    Anxdada閱讀 916評論 0 1
  • 企劃書是臺灣和日本的叫法,其實也就是商業計劃書。做任何一個項目之前,許多公司都會先做一個商業計劃書,就是把項目或活...
    金魚爸爸閱讀 1,968評論 0 8