圖論——迪傑斯特拉算法和最小生成樹

前言

複習一下迪傑斯特拉算法,因爲最小生成樹的Prim算法與迪傑斯特拉算法極其相似,再順便複習下最小生成樹,順便找兩道水題驗證代碼正確性。node

迪傑斯特拉算法

目的

該算法用於單源最短路,求一個圖中,從起點S,到終點E的最短路徑c++

思路

算法基於貪心思想,簡單來說就是兩步:算法

  • 找出起點距離其餘點的最短距離中的最小的那個
  • 用最小的來更新其餘點的最短距離,更新完後捨棄

依我所見,迪傑斯特拉相似於排序,假設從起點到其餘點的路徑爲邊。優化

  • 選出最短的邊,經過最短的邊來更新其餘的邊。
  • 再經過第二短的邊,更新其餘的邊。
  • 整個過程就是從小到大依次找出從起點到其餘點的最短邊

題目

牛客網:https://ac.nowcoder.com/acm/problem/17511spa

普通方法

先遍歷頂點,再遍歷該頂點到其餘頂點的邊,時間複雜度:\(O(n^2)\)code

#include <bits/stdc++.h>
#define ll long long
#define MAX 1005
using namespace std;
 
int mp[MAX][MAX],ans[MAX],n,m,s,t;
bool used[MAX];
 
void init(){
    scanf("%d%d%d%d",&n,&m,&s,&t);
    memset(mp,0x3f,sizeof(mp));
    memset(ans,0x3f,sizeof(ans));
    memset(used,false,sizeof(used));
    ans[s] = 0;
    for(int i = 1;i <= m;i++){
        int x,y,v;
        scanf("%d%d%d",&x,&y,&v);
        mp[x][y] = mp[y][x] = min(mp[y][x],v);
        if (x == s) ans[y] = mp[x][y];
        else if (y == s) ans[x] = mp[x][y];
    }
}
int dijkstra(int start,int end){
 
    while(true){
        int min_edge = 0;
        for(int i = 1;i <= n;i++)//尋找從起點到其餘點的路線中的最短路線
            if (!used[i]&&(!min_edge || ans[i] < ans[min_edge]))
                min_edge = i;
        //當找到終點時,可提早退出
        if (min_edge == end || !min_edge) break;
        used[min_edge] = true;
 
        for(int i = 1;i <= n;i++)
            ans[i] = min(ans[i],ans[min_edge]+mp[min_edge][i]);
    }
 
    return ans[end]==0x3f3f3f3f?-1:ans[end];
}
 
int main(){
    init();
    printf("%d\n",dijkstra(s,t));
    return 0;
}

優先隊列(堆)

m爲邊數,n爲頂點數
先上結論,時間複雜度\(O(m*logn)\)排序

for(int i = 1;i <= n;i++)//尋找從起點到其餘點的路線中的最短路線
    if (!used[i]&&(!min_edge || ans[i] < ans[min_edge]))
        min_edge = i;

顯然,對於上面代碼,可使用優先隊列(堆)來使得時間複雜度降爲\(O(logn)\),可是!!!下面還有個for循環,因此自己時間複雜度仍是\(O(n^2)\)隊列

for(int i = 1;i <= n;i++)
    ans[i] = min(ans[i],ans[min_edge]+mp[min_edge][i]);

那麼,能否把這裏也改下呢?顯然,咱們不須要遍歷全部的頂點,由於點到點不必定有邊,這時候咱們只須要遍歷邊便可,也就是把邊存儲起來,即便用鄰接表。get

總結一下,整個過程每一個頂點可能遍歷了屢次,但其中只有一次須要遍歷鄰接的邊,即全部邊也只須要遍歷一次(無向邊就是兩次),因爲使用了堆,因此還須要加上用堆的時間複雜度,因此總的時間複雜度爲\(O(m*logn)\)it

下面是代碼

#include <bits/stdc++.h>
#define ll long long
#define MAX 1005
using namespace std;

int ans[MAX],n,m,s,t;//ans爲起點到某一點的最短路線
vector<pair<int,int>> mp[MAX*10];//鄰接表存儲邊,mp下標表示起點,pair第一個值表示長度,第二個值表示終點
void init(){
    scanf("%d%d%d%d",&n,&m,&s,&t);
    memset(ans,0x3f,sizeof(ans));
    ans[s] = 0;
    for(int i = 1;i <= m;i++){
        int x,y,v;
        scanf("%d%d%d",&x,&y,&v);
        mp[x].push_back(make_pair(v,y));
        mp[y].push_back(make_pair(v,x));
    }
}
int dijkstra(int start,int end){

    priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>> > p_que;
    p_que.push(make_pair(ans[start],start));//pair第一個值表示長度,第二個值表示頂點

    while(!p_que.empty()){
        pair<int,int> node = p_que.top();
        p_que.pop();
        if (node.first > ans[node.second]) continue;
        for(int i = mp[node.second].size()-1;i >= 0;i--){
            pair<int,int> temp = mp[node.second][i];
            if (ans[temp.second] > ans[node.second]+temp.first){
                ans[temp.second] = ans[node.second]+temp.first;
                p_que.push(make_pair(ans[temp.second],temp.second));
            }
        }
    }

    return ans[end]==0x3f3f3f3f?-1:ans[end];
}

int main(){
    init();
    printf("%d\n",dijkstra(s,t));
    return 0;
}

最小生成樹

目的

給定一個無向圖

  • 生成樹:一個子圖,其任意兩點都能互通,且是棵樹
  • 最小生成樹:邊上有值,且全部邊的和最小的生成樹

思路

假設迪傑斯特拉算法是以一個點爲起點,求該起點到其餘全部點的最小值,那麼,最小生成樹的Prim算法則是以一個集合(有多個點)爲起點,求該集合到其餘全部點的最小值,並求和,步驟以下:

  • 每次循環,找出集合與非集合的點中的最小邊,假設這個點爲\(x\)
  • 集合加入\(x\)點,並遍歷\(x\)與其餘點的邊,假設有一條邊\(edge[x][y]\) < 集合到 \(y\) 的距離,則更新集合到\(y\)的距離
  • 回到第一步

題目

牛客網:https://ac.nowcoder.com/acm/problem/15108

代碼

顯然,能夠跟迪傑斯特拉同樣,使用堆進行優化,因爲時間問題,只給出普通代碼。

#include <bits/stdc++.h>
#define ll long long
#define MAX 1005
using namespace std;
 
int mp[MAX][MAX],ans[MAX],c,n,m;
bool used[MAX];

int Prim(int start){//幾乎和迪傑斯特拉算法如出一轍
    int len = 0;
    while(true){
        int min_edge = 0;
        for(int i = 1;i <= n;i++)//尋找從集合到其餘點的路線中的最短路線
            if (!used[i]&&(!min_edge || ans[i] < ans[min_edge]))
                min_edge = i;
        //len >= 0x3f3f3f3f說明沒法生成最小生成樹,即圖不連通
        if (!min_edge || len >= 0x3f3f3f3f) break;
        used[min_edge] = true;
        len += ans[min_edge];//加上最小值
        for(int i = 1;i <= n;i++)
            ans[i] = min(ans[i],mp[min_edge][i]);//注意這裏和迪傑斯特拉不一樣,也幾乎是惟一的不一樣點
    }
 
    return len;
}
 
void init(int start){
    while(~scanf("%d%d%d",&c,&m,&n)){
        memset(mp,0x3f,sizeof(mp));
        memset(ans,0x3f,sizeof(ans));
        memset(used,false,sizeof(used));
        ans[start] = 0;
        for(int i = 1;i <= m;i++){
            int x,y,v;
            scanf("%d%d%d",&x,&y,&v);
            mp[x][y] = mp[y][x] = min(mp[y][x],v);
        }
        printf("%s\n", Prim(1) <= c ?"Yes":"No");
    }
}
 
int main(){
    init(1);
    return 0;
}
相關文章
相關標籤/搜索