最短路-樸素版Dijkstra算法&堆優化版的Dijkstra

樸素版Dijkstra

目標

找到從一個點到其餘點的最短距離c++

思路

①初始化距離dist數組,將起點dist距離設爲0,其餘點的距離設爲無窮(就是很大的值)算法

②for循環遍歷n次,每層循環裏找出不在S集合中,且距離最近的點,而後用該點去更新其餘點的距離,算法複雜度是O(n2),適合稠密圖數組

實例練習

題目:https://www.acwing.com/problem/content/851/優化

代碼:spa

#include<bits/stdc++.h>
using namespace std;
const int maxn=550;

int n,m;
int g[maxn][maxn];
int dist[maxn];
bool st[maxn];

int Dijkstra()
{
    int i,j;
    //初始化距離爲無窮大
    memset(dist,0x3f,sizeof(dist));
    //起點距離爲1
    dist[1]=0;
    //循環n次,就能夠將全部點都加入到集合裏
    for(i=1;i<=n;i++)
    {
        //用來記錄,不在S集合中,距離最近的點
        int t=-1;
        for(j=1;j<=n;j++)
        {
            if(!st[j]&&(t==-1||dist[t]>dist[j]))
                t=j;
        }
        //加入到集合S中
        st[t]=true;
        //更新新加入的點,到其餘點的距離
        for(j=1;j<=n;j++)
        {
            dist[j]=min(dist[j],dist[t]+g[t][j]);
        }

    }
    //若是仍是無窮大,表明從起點走不到n
    if(dist[n]==0x3f3f3f3f)
        return -1;
    else
        return dist[n];
}

int main()
{
    int i,j;
    cin>>n>>m;
    //初始化權值
    memset(g,0x3f,sizeof(g));
    while(m--)
    {
        int x,y,z;
        cin>>x>>y>>z;
        //由於會有重邊存在,只保留最小的便可
        g[x][y]=min(g[x][y],z);
    }
    int ans=Dijkstra();
    cout<<ans;
    return 0;
}

若是是稀疏圖,點的個數比較多,1e5個點,用O(n2)就會爆掉,所以咱們引入堆優化版的Dijstra指針

堆優化版的Dijkstra

原理

將找尋不在S中,且距離最近的點的方法進行優化,採用堆(優先隊列的方法),時間複雜度爲mlog(n),就能夠解決code

思路

對於稀疏圖,咱們能夠用鄰接表結構來進行存儲blog

先上數據,以下。排序

4 5
1 4 9
4 3 8
1 2 5
2 4 6
1 3 7

第一行兩個整數n m。n表示頂點個數(頂點編號爲1~n),m表示邊的條數。接下來m行表示,每行有3個數x y z,表示頂點x到頂點y的邊的權值爲z。下圖就是一種使用鏈表來實現鄰接表的方法。隊列

 

上面這種實現方法爲圖中的每個頂點(左邊部分)都創建了一個單鏈表(右邊部分)。這樣咱們就能夠經過遍歷每一個頂點的鏈表,從而獲得該頂點全部的邊了。使用鏈表來實現鄰接表對於痛恨指針的的朋友來講,這簡直就是噩夢。

這裏我將爲你們介紹另外一種使用數組來實現的鄰接表,這是一種在實際應用中很是容易實現的方法。這種方法爲每一個頂點i(i從1~n)也都保存了一個相似「鏈表」的東西,裏面保存的是從頂點i出發的全部的邊,具體以下。

首先咱們按照讀入的順序爲每一條邊進行編號(1~m)。好比第一條邊「1 4 9」的編號就是1,「1 3 7」這條邊的編號是5。

這裏用u、v和w三個數組用來記錄每條邊的具體信息,即u[i]、v[i]和w[i]表示第i條邊是從第u[i]號頂點到v[i]號頂點(u[i]àv[i]),且權值爲w[i]

  

  再用一個first數組來存儲每一個頂點其中一條邊的編號。以便待會咱們來枚舉每一個頂點全部的邊(你可能會問:存儲其中一條邊的編號就能夠了?不可能吧,每一個頂點都須要存儲其全部邊的編號才行吧!甭着急,繼續往下看)。好比1號頂點有一條邊是 「1 4 9」(該條邊的編號是1),那麼就將first[1]的值設爲1。若是某個頂點i沒有以該頂點爲起始點的邊,則將first[i]的值設爲-1。如今咱們來看看具體如何操做,初始狀態以下。

 

  咦?上圖中怎麼多了一個next數組,有什麼做用呢?不着急,待會再解釋,如今先讀入第一條邊「1 4 9」。

  讀入第1條邊(1 4 9),將這條邊的信息存儲到u[1]、v[1]和w[1]中。同時爲這條邊賦予一個編號,由於這條邊是最早讀入的,存儲在u、v和w數組下標爲1的單元格中,所以編號就是1。這條邊的起始點是1號頂點,所以將first[1]的值設爲1。

  另外這條「編號爲1的邊」是以1號頂點(即u[1])爲起始點的第一條邊,因此要將next[1]的值設爲-1。也就是說,若是當前這條「編號爲i的邊」,是咱們發現的以u[i]爲起始點的第一條邊,就將next[i]的值設爲-1(貌似的這個next數組很神祕啊⊙_⊙)。

 

  

  讀入第2條邊(4 3 8),將這條邊的信息存儲到u[2]、v[2]和w[2]中,這條邊的編號爲2。這條邊的起始頂點是4號頂點,所以將first[4]的值設爲2。另外這條「編號爲2的邊」是咱們發現以4號頂點爲起始點的第一條邊,因此將next[2]的值設爲-1。

 

  

  讀入第3條邊(1 2 5),將這條邊的信息存儲到u[3]、v[3]和w[3]中,這條邊的編號爲3,起始頂點是1號頂點。咱們發現1號頂點已經有一條「編號爲1 的邊」了,若是此時將first[1]的值設爲3,那「編號爲1的邊」豈不是就丟失了?我有辦法,此時只需將next[3]的值設爲1便可。如今你知道next數組是用來作什麼的吧。next[i]存儲的是「編號爲i的邊」的「前一條邊」的編號。(注:next數組的大小由邊的數目決定,first數組的大小由頂點的個數來決定

 

 

  讀入第4條邊(2 4 6),將這條邊的信息存儲到u[4]、v[4]和w[4]中,這條邊的編號爲4,起始頂點是2號頂點,所以將first[2]的值設爲4。另外這條「編號爲4的邊」是咱們發現以2號頂點爲起始點的第一條邊,因此將next[4]的值設爲-1。

 

 

讀入第5條邊(1 3 7),將這條邊的信息存儲到u[5]、v[5]和w[5]中,這條邊的編號爲5,起始頂點又是1號頂點。此時須要將first[1]的值設爲5,並將next[5]的值改成3(編號爲5的邊的前一條邊的編號爲3)。

 

 

  此時,若是咱們想遍歷1號頂點的每一條邊就很簡單了。1號頂點的其中一條邊的編號存儲在first[1]中。其他的邊則能夠經過next數組尋找到。請看下圖。

 

   細心的同窗會發現,此時遍歷邊某個頂點邊的時候的遍歷順序正好與讀入時候的順序相反。由於在爲每一個頂點插入邊的時候都直接插入「鏈表」的首部而不是尾部。不過這並不會產生任何問題,這正是這種方法的其妙之處。

實例練習

題目:https://www.acwing.com/problem/content/description/852/

代碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
typedef long long ll;
ll n,m;
typedef pair<int, int> PII;
int h[maxn],e[maxn],w[maxn],ne[maxn],idx;
int dist[maxn];
bool st[maxn];

void add(int x,int y,int c)
{
    //權值記錄
    w[idx]=c;
    //終點邊記錄
    e[idx]=y;
    //存儲編號爲idx的邊的前一條邊的編號
    ne[idx]=h[x];
    //表明以x爲起點的邊的編號,這個值會發生變化
    h[x]=idx++;
}


ll Dijkstra()
{
    ll i,j;
    memset(dist,0x3f,sizeof(dist));
    dist[1]=0;
    //尋找出非S集合外,距離最小的頂點,並將其加入,更新他到其餘邊的最短距離
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    //push的集合中:距離,頂點(這裏爲起點1)
    //這裏爲啥距離在前,由於會先按第一個值排序,咱們要找出距離最小的邊
    heap.push({0,1});
    while(heap.size())
    {
        auto t=heap.top();
        heap.pop();
        //得到頂點和他到起點的距離
        int ver=t.second,distance=t.first;
        //判斷是否加入集合裏
        if(st[ver])
            continue;
        //加入到集合
        st[ver]=true;
        //遍歷他所鏈接的全部邊
        for(int i=h[ver];i!=-1;i=ne[i])
        {
            //跟ver鏈接的終點j
            int j=e[i];
            //判斷j點離原點的距離 > ver點離原點的距離 + ver和j點的距離,哪一個近
            if(dist[j]>dist[ver]+w[i])
            {
                //更新距離
                dist[j]=dist[ver]+w[i];
                //將更新後的距離,和對應的終點j,加入到裏面
                heap.push({dist[j],j});
            }
        }
    }
    //若是說原點到終點n的距離仍是無窮,則表明到達不了
    if(dist[n]==0x3f3f3f3f)
        return -1;
    else
        return dist[n];
}

int main()
{
    ll i,j;
    cin>>n>>m;
    //初始化h數組爲-1,目的是爲ne數組賦值
    memset(h,-1,sizeof(h));
    while(m--)
    {
        int x,y,z;
        cin>>x>>y>>z;
        //加邊
        add(x,y,z);
    }
    //堆優化版的Dijkstra
    ll ans=Dijkstra();
    cout<<ans;
    return 0;
}
相關文章
相關標籤/搜索