最短路徑問題
 + 最小生成樹

y2sung

講者介紹

  • 宋元堯
  • WLSH5701
  • 交大電機 -> 交大資工
  • sungyuanyao@gmail.com
  • deror1869107 / Deror Sung / y2sung

最短路徑

從起點到終點、權重最小的路徑。

Data Structure

  • Adjacency Matrix
  • Adjacency List
  • 家安昨天應該講過了

Adjacency Matrix

const int INF = 0x7fffffff;
const int MAX = 1000 + 5;
int w[MAX][MAX];

存取權重速度較快,記憶體使用量較大(空間複雜度O(V^2))

適合場合:Vertices數較少之時候

1000個點僅佔4MB

10000個點就會佔400MB
 

Adjacency List

#include <iostream>
#include <vector>

struct Edge {
    int b, w; //b是另一個端點 w是權重
    Edge(int b, int w):b(b), w(w){}
    Edge(){}
};

const int MAX = 100000 + 5;
vector<Edge> e[MAX];

int main()
{
    int n;
    cin >> n;
    int a, b, w;
    for(int i = 0; i < n; ++i){
        cin >> a >> b >> w;
        e[a].push_back(Edge(b, w));
        e[b].push_back(Edge(a, w));//雙向
    }
}

記憶體使用量較小(空間複雜度O(E))

適合場合:Edge數遠小於V^2時

 

 

Floyd-Warshall

Floyd-Warshall

核心概念:DP

D(i, j, k) 為從 i j 的只以 ( 1.. k ) 集合中的節點為中間節點的最短路徑的長度。

D(i, j, k) = min(D(i, j, k - 1), D(i, k, k - 1) + D(k, j, k - 1))

                               (不經過k點)                (經過k點)

 

全局最短路徑問題

複雜度:O(V^3)

適用Vertices數<=100
Code簡單

Floyd-Warshall

for(int k = 0; k < n; ++k){
    for(int i = 0; i < n; ++i){
        for(int j = 0; j < n; ++j){
            if(w[i][k] + w[k][j] < w[i][j]){
                w[i][j] = w[i][k] + w[k][j];
            }
        }
    }
}

Bellman-Ford

Bellman-Ford

令w[a][b]是a點到b點的距離(即是邊的權重)。
令d[a]是起點到a點的最短路徑長度,起點設為零,其他點都設為無限大。

 

重複下面這件事V-1次:
窮舉邊ab:d[a] + w[a][b] < d[b]
以邊ab來修正起點到b點的最短路徑:d[b] = d[a] + w[a][b](relaxation)

複雜度:adjacency matrix : O(V^3) adjacency list : O(VE)

Bellman-Ford

const int MAX = 100000, INF = 1e9;
vector<Edge> e[MAX];
int d[MAX], parent[MAX];

void bellman_ford(int source, int V)
{
    for(int i = 0; i < V; ++i) d[i] = INF;
    d[source] = 0;
    parent[source] = source;
    for(int i = 0; i < V - 1; ++i){
        for(int a = 0; a < V; ++a){
            for(auto edge : e[a]){
                int b = edge.b;
                if(d[a] != INF && edge.w != INF){
                    if(d[a] + edge.w < d[b]){
                        d[b] = d[a] + edge.w;
                        parent[b] = a;
                    }
                }
            }
        }
    }
}

Dijkstra

Dijkstra

發音很重要,j不發音

 

重複下面這件事V次,以將所有點加入到最短路徑樹:
尋找一個目前未走訪過而且離起點最近的點
令剛剛加入的點為a點,找一個與a點相鄰的點b,把d[a]+w[a][b]存入到d[b]當中,儘可能紀錄越小

(relaxation)

 

複雜度:adjacency matrix or list 無 priority_queue : O(V^2)

adjacency list + priority_queue O(E + VlogV) 快!

Dijkstra

Dijkstra

#include <vector>
#include <queue>

struct Node {
    int b, d;
    Node(int b, int d):b(b), d(d){}
    Node(){}
};
bool operator<(const Node& n1, const Node& n2) {return n1.d > n2.d;}

const int MAX = 100000 + 5, INF = 1e9;
vector<Edge> e;
int d[MAX], parent[MAX];
bool visit[MAX];

void dijkstra(int source, int V)
{
    for(int i = 0; i < MAX; ++i) visit[i] = false;
    for(int i = 0; i < MAX; ++i) d[i] = INF;

    priority_queue<Node> pq;
    d[source] = 0;
    parent[source] = source;
    pq.push(Node(source, d[source]));

    for(int i = 0; i < V; ++i){
        int a = -1;
        while(!pq.empty() && visit[a = pq.top()]) pq.pop();
       
        if(a == -1) break;
        visit[a] = true;

        for(auto edge : e[a]){
            int b = edge.b;
            if(!visit[b] && d[a] + edge.w < d[b]){
                d[b] = d[a] + edge.w;
                parent[b] = a;
                pq.push(Node(b, d[b]));
            }
        }
    }
}

Minimal Spanning Tree

Minimal Spanning Tree

Spanning Tree:

一個無向圖G = (V,E),其中|V| = N而且|E| ≥ N-1。

用圖形中的N-1個邊建立一棵可以連接所有頂點的樹。

 

Minimal (Cost) Spanning Tree:

所有生成樹中,權重和最小的就是MST

Prim Algorithm

Prim Algorithm

基於Dijkstra的框架

S={起點}

將距離S最近的點加入S

直到所有點都已在S中

 

Adjacency Matrix: O(V^2)、Fibonacci Heap: O(E+VlogV)

適用時機:稠密圖

Prim Algorithm

#include <vector>
#include <queue>

struct Edge {
    int a, b, w;
    Edge(int a, int b, int w):a(a), b(b), w(w){}
    Edge(){}
};

bool cmp(const Edge& lhs, const Edge& rhs)
{
    return lhs.w < rhs.w;
}

const int MAX = 100000 + 5, INF = 1e9;
vector<Edge> e[MAX], mst;
int d[MAX], parent[MAX];
bool visit[MAX];

int prim(int V)
{
    for(int i = 0; i < MAX; ++i) visit[i] = false;
    for(int i = 0; i < MAX; ++i) d[i] = INF;

    priority_queue<int> pq;
    d[0] = 0; // zero-based
    parent[0] = 0;
    pq.push(0);

    for(int i = 0; i < V - 1; ++i){
        int a = -1;
        while(!pq.empty() && visit[a = pq.top()]) pq.pop();
       
        if(a == -1) break;
        visit[a] = true;

        for(auto edge : e[a]){
            int b = edge.b;
            if(!visit[b] && edge.w < d[b]){
                d[b] = edge.w;
                parent[b] = a;
                pq.push(b);
                mst.push_back(edge);
            }
        }
    }
}

Kruskal Algorithm

Kruskal Algorithm

應用Disjoint Set

將所有邊由小到大排序

連接不在同一塊的兩端點

直到所有點都已連在一起

 

O(ElogV)

適用時機:稀疏圖

Kruskal Algorithm

補充Disjoint Set

Disjoint Set「互斥集」的意思是一堆集合們,大家擁有的元素都不相同,也就是說這些集合們之間都沒有交集。

初始化:每個人自己和自己一組

操作:

find : 找出一個人在哪一團

union : 將兩個不同團體合併為一團

Kruskal Algorithm

續Disjoint Set

 

int d[MAX + 1];

void init()
{
    for(int i = 1; i <= MAX; ++i) {
        d[i] = i; 
    }
}

int find(int n)
{
    return (d[n] == n) ? d[n] : d[n] = find(d[n]);
}

bool Union(int a, int b)
{
    int x = find(a);
    int y = find(b);
    if(x != y) d[x] = y;
    return x != y; // for Kruskal
}

Kruskal Algorithm

續Disjoint Set

 

#include <vector>
#include <algorithm>

struct Edge {
    int a, b, w;
    Edge(int a, int b, int w):a(a), b(b), w(w){}
    Edge(){}
};

bool cmp(const Edge& lhs, const Edge& rhs)
{
    return lhs.w < rhs.w;
}

vector<E> v, mst;
void kruskal()
{
    init(); // initialize disjoint set
    sort(v.begin, v.end, cmp); // sort edges by cost
    for(auto e: v){
        if(Union(e.a, e.b)){
            mst.push_back(e);  // put selected edge in MST
        }
    }
}

2016武陵電資推廣暑訓(Shortest Path + MST)

By deror1869107

2016武陵電資推廣暑訓(Shortest Path + MST)

  • 178