【內功】基礎算法——圖論

1. 如何判斷一個圖是二分圖(染色問題)leetcode 886 possible bipartition

題解:咱們用一個color數組標記點的顏色,而後對每個點作bfs,若是兩個點間有邊,而且另一個點沒有被染色,就把另一個點染色成相反色,若是另一個點有顏色,並且顏色和當前結點相同,那麼確定不是二分圖。html

 1 class Solution {
 2 public:
 3     bool possibleBipartition(int N, vector<vector<int>>& dislikes) {
 4         N1 = N;
 5         const int edges = dislikes.size();
 6         if (edges == 0) {return true;}
 7         for (auto& p : dislikes) {
 8             stDis.insert(make_pair(p[0], p[1]));
 9         }
10         vector<int> color(N + 1, -1);
11         for (int i = 1; i < N+1; ++i) {
12             if (color[i] == -1 && !bfs(i, color)) {
13                 return false;
14             }
15         }
16         return true;
17     }
18     bool bfs(int p, vector<int>& color) {
19         queue<int> que;
20         que.push(p);
21         color[p] = 1;
22         while (!que.empty()) {
23             int node = que.front();
24             que.pop();
25             for (int i = 1; i <= N1; ++i) {
26                 if (i == node) { continue; }
27                 pair<int, int> e = make_pair(min(i, node), max(i, node));
28                 if (stDis.find(e) != stDis.end()) {
29                     if (color[i] == -1) {
30                         color[i] = 1 - color[node];
31                         que.push(i);
32                     } else if (color[i] == color[node]) {
33                         return false;
34                     }
35                 }
36             }
37         }
38         return true;
39     }
40     int N1;
41     set<pair<int, int>> stDis;
42 };
leetcode 886 bfs

這個解法巨慢, 1700+ms。看有沒有更快的方法。node

solution 裏面給了 dfs 的方法,80msios

就是先建圖,由於是DAG,注意雙邊。而後從一個沒有染過色的點開始,dfs染色,若是相鄰結點有染色,判斷是否衝突,若是相鄰結點沒有染色,就把它染成相反色。算法

 1 //dfs 方法 建圖的時候,注意是雙邊都要加,這是DAG
 2 class Solution {
 3 public:
 4     bool possibleBipartition(int N, vector<vector<int>>& dislikes) {
 5         vector<int> temp(N + 1, -1);
 6         color = temp;
 7         m = dislikes.size();
 8         edges.resize(N + 1);
 9         for (const auto& p : dislikes) {
10             edges[p[0]].push_back(p[1]);
11             edges[p[1]].push_back(p[0]);
12         }
13         for (int i = 1; i < N+1; ++i) {
14             if (color[i] == -1 && !dfs(i, 0)) {
15                 return false;
16             }
17         }
18         return true;
19     }
20     bool dfs(int node, int c) {
21         if (color[node] != -1) { return color[node] == c; }
22         color[node] = c;
23         for (const auto& p : edges[node]) {
24             if (!dfs(p, 1 - c)) {
25                 return false;
26             }
27         }
28         return true;
29     }
30     vector<int> color;
31     vector<vector<int>> edges;
32     int m;
33 };
leetcode 886 dfs

 從新寫了一個bfs,也能80ms過,看來是第一個方法很差,沒有雙向建圖數組

 1 //bfs 方法 建圖的時候,注意是雙邊都要加,這是DAG
 2 class Solution {
 3 public:
 4     bool possibleBipartition(int N, vector<vector<int>>& dislikes) {
 5         vector<int> temp(N + 1, -1);
 6         color = temp;
 7         m = dislikes.size();
 8         edges.resize(N + 1);
 9         for (const auto& p : dislikes) {
10             edges[p[0]].push_back(p[1]);
11             edges[p[1]].push_back(p[0]);
12         }
13         for (int i = 1; i < N+1; ++i) {
14             if (color[i] != -1) { continue; }
15             queue<int> que;
16             que.push(i);
17             color[i] = 0;
18             while (!que.empty()) {
19                 int node = que.front(); que.pop();
20                 for (auto& p : edges[node]) {
21                     if (color[p] == color[node]) {return false;}
22                     if (color[p] == -1) {
23                         color[p] = 1 - color[node];
24                         que.push(p);
25                     }
26                 }
27             }
28         }
29         return true;
30     }
31     vector<int> color;
32     vector<vector<int>> edges;
33     int m;
34 };
leetcode 886 bfs

 

2. 拓撲排序 (leetcode 207)(算法見《算法競賽入門指南》chp6 數據結構基礎 6.4.3 拓撲排序)

基本概念和定義:若是圖中存在有向環,則不存在拓撲排序,反之則存在。咱們把不包含有向環的有向圖稱爲有向無環圖(directed acyclic graph, DAG)。能夠藉助 dfs 完成拓撲排序:在訪問完一個結點以後把它放在當前拓撲排序的首部。(想一想爲啥不是尾部)數據結構

這裏用到了一個 c 數組, c[u] = 0 表示歷來沒有訪問過(歷來沒有調用過 dfs(u) );c[u] = 1 表示已經訪問過,而且還遞歸訪問過它的全部子孫(即dfs(u)曾經被調用過,並已經返回);c[u] = -1 說明正在訪問(即遞歸調用dfs(u)正在棧幀中,還沒有返回)。 ide

 1 #include <iostream>
 2 #include <cstring>
 3 #include <cstdio>
 4 #include <cstdlib>
 5 
 6 using namespace std;
 7 
 8 int c[100] = {0};
 9 int topo[100] = {0}, t;
10 int g[105][105];
11 int n; //n個結點
12 
13 bool dfs(int u) {
14     c[u] = -1;
15     for (int v = 0; v < n; ++v) {
16         if (g[u][v]) {
17             if (c[v] < 0) { return false; } //說明有環
18             else if (!c[v] && !dfs(v)) {return false;}
19         }
20     }
21     c[u] = 1;
22     topo[--t] = u;
23     return true;
24 }
25 
26 bool toposort() {
27     t = n;
28     memset(c, 0, sizeof(c));
29     for (int u = 0; u < n; ++u) {
30         if (!c[u]) {
31             if (!dfs(u)) {
32                 return false;
33             }
34         }
35     }
36     return true;
37 }
38 
39 int main(int argc, char *argv[]) {
40     n = 5;
41     memset(g, 0 , sizeof(g));
42     g[0][1] = 1;
43     g[0][3] = 1;
44     g[3][4] = 1; 
45     //g[4][3] = 1; 檢測環
46     bool ret = toposort();
47     if (ret) {
48         for (int i = 0; i < n; ++i) {
49             printf("%d ", topo[i]);
50         }
51         printf("\n");
52     }
53     if (!ret) {
54         printf("no ans\n");
55     }
56     return 0;    
57 }
View Code

 額外補充:如何判斷toposort的惟一性,看是否多於一個結點的入度爲 0。ui

2019年1月20日,補充topologic sort 的 bfs 版本。算法流程以下:spa

(1)建圖(鄰接鏈表) (2)計算入度 (3)找出全部入度爲 0 的點加入隊列 (4)開始bfs3d

 1 class Solution {
 2 public:
 3     vector<int> findOrder(int numCourses, vector<pair<int, int>>& prerequisites) {
 4         vector<vector<int>> g = initGraph(numCourses, prerequisites);
 5         vector<int> degree = calDegree(g);
 6         vector<int> ret(numCourses, 0);
 7         queue<int> que;
 8         for (int i = 0; i < degree.size(); ++i) {
 9             if (degree[i] == 0) {
10                 que.push(i);
11             }
12         }
13         for (int i = 0; i < numCourses; ++i) {
14             if (que.empty()) {
15                 return vector<int>();
16             }
17             int cur = que.front(); que.pop();
18             ret[i] = cur;
19             for (auto e : g[cur]) {
20                 if (--degree[e] == 0) {
21                     que.push(e);
22                 }
23             }
24         }
25         return ret;
26     }
27 private:
28     vector<vector<int>> initGraph(int n, vector<pair<int, int>>& pre) {
29         vector<vector<int>> g(n, vector<int>());
30         for (auto e : pre) {
31             int start = e.second, end = e.first;
32             g[start].push_back(end);
33         }
34         return g;
35     }
36     vector<int> calDegree(vector<vector<int>>& g) {
37         vector<int> d(g.size(), 0);
38         const int n = g.size();
39         for (auto e : g) {
40             for (auto v : e) {
41                 d[v]++;
42             }
43         }
44         return d;
45     }
46 };
View Code

 

3. 最小生成樹

http://www.javashuo.com/article/p-mlxupbrq-ms.html

 

4. 最短路

http://www.javashuo.com/article/p-mlxupbrq-ms.html

 

5. 如何在一個無向圖上找環上的全部結點。(檢測環能夠直接Union Find)

這個思路是從kickstart 2018 Round C 的 Problem A 裏面來的。

咱們能夠用bfs作,O(N)的算法複雜度,Kahn的算法思想。咱們先把圖上全部度爲 1 的結點放進隊列裏面,而後從圖上刪除這個結點(就是從隊列裏面pop出來的時候,這個結點的度設置爲 0)。刪除了當前結點以後,咱們遍歷當前結點的沒有訪問過的相鄰結點(此時相鄰結點的度也應該減一),減一後看他們的度是否爲 1,若是度爲1,就把相鄰結點放進隊列裏面。最後隊列空了的時候,度不爲 0 的點就全在環上。

相關文章
相關標籤/搜索