图算法
Dijkstra算法
输入是一个图graph
,返回是一个最短路径权重的数组。
思路:将dis
的初始值出start
之外设置为INT_MAX
,start
设置为0
,然后将start
加入到优先级队列中,依次将优先级队列中到start
距离最小的节点cur
弹出,看是否可以更新cur
相邻节点next_node
的dis值,如果出现下面next_cost < dis[edge.first]
的情况,那么就表示next_node
的值可以被更新,且新的最短距离是经过cur
的,同时将next_node
加入到优先级队列中。重复这个过程,最终得到的dis
数组就是start
到其他节点的距离。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
vector<int> Dijkstra(vector<vector<pair<int, int>>> &graph, int start, int n) { vector<int> dis(n + 1, INT_MAX); auto cmp = [](pair<int, int> &p1, pair<int, int> &p2) {return p1.second < p2.second;}; priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> pq(cmp); dis[start] = 0; pq.emplace(make_pair(start, 0)); while(!pq.empty()) { auto [id, cost] = pq.top(); pq.pop(); if(cost > dis[id]) continue; for(auto edge : graph[id]) { int next_cost = edge.second + dis[id]; if(next_cost < dis[edge.first]) { dis[edge.first] = next_cost; pq.emplace(make_pair(edge.first, next_cost)); } } } return dis; }
|
Floyd算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include <iostream> #include <vector> #include <algorithm>
using namespace std;
const int INF = 1e9;
void floydWarshall(vector<vector<int>>& graph) { int V = graph.size(); vector<vector<int>> distances = graph; for (int k = 0; k < V; ++k) { for (int i = 0; i < V; ++i) { for (int j = 0; j < V; ++j) { if (distances[i][k] < INF && distances[k][j] < INF) { distances[i][j] = min(distances[i][j], distances[i][k] + distances[k][j]); } } } } for (int i = 0; i < V; ++i) { for (int j = 0; j < V; ++j) { if (distances[i][j] == INF) { cout << "INF "; } else { cout << distances[i][j] << " "; } } cout << "\n"; } }
int main() { std::vector<std::vector<int>> graph = { {0, 5, INF, 10}, {5, 0, 3, INF}, {INF, 3, 0, 1}, {10, INF, 1, 0} };
floydWarshall(graph); return 0; }
|
Kruskal 最小生成树算法
思想:从图graph
中每次选择一条最短的边e
,若加入e
之后不构成环,将e
加入到最小生成树的结果中;重复上述步骤,一直到有n - 1
条边为止(n
为图的节点个数,因为n
个节点不存在环的连通图中必定有n - 1
条边)。
Kruskal算法步骤:
- 将所有边按照权重从小到大排序
- 初始化一个集合,并且把所有节点分别加入集合,每个节点都是单独一个集合。
- 遍历排序后的边,将它们加入树中(如果边的两个节点不在同一个集合中)。
- 最后得到的tree就是最小生成树。
这里实现KRUSKAL使用了并查集,并查集的概念与代码见并查集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| int Kruskal(int n, vector<vector<int>>& graph) { int count = 0; vector<vector<int>> res; sort(graph.begin(), graph.end(), [](const vector<int> &v1, const vector<int> v2) -> bool {return v1[2] < v2[2];}); UF my_uf(n + 1); for(auto edge : graph) { int x = edge[0], y = edge[1], cost = edge[2]; if(my_uf.isConnected(x, y)) continue; ++count; my_uf.unionNode(x, y); res.emplace_back(edge); } return count == n - 1 ? res : -1; }
|
Prim 最下生成树算法
Prim
算法与
Kruskal
的区别在于,Kruskal
算法是每次选择一条最短的且不构成环的边,而Prim
算法是使用BFS
算法思想和visited
数组避免成环,保证选出来的边一定是一棵树。
Prim
算法步骤:
- 从任意一个节点开始,将该节点加入到已访问节点集合中。
- 在未访问节点中找到与已访问节点集合中节点权值最小的边(也就是跨越已访问节点集合和未访问节点中某个节点的边),将该边和对应的节点加入到已访问节点集合中。
- 重复第二步操作,直到所有节点都被加入到已访问节点集合中为止。
Prim
算法也是一种贪心算法,它的时间复杂度为\(O(n^2)\),其中n
为节点个数。在实际应用中,Prim
算法通常用于处理稠密图中的最小生成树问题。
这里Prim
算法的实现使用了优先级队列priority_queue
,实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| int Prim(int n, vector<vector<int>>& graph, int start) { if(graph[start].empty()) return -1; visited[start] = true;
auto cmp = [](pair<int, int> &p1, pair<int, int> &p2) -> bool {return p1.second > p2.second;}; priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> pq(cmp); vector<bool> visited = vector<bool>(n + 1, false);
for(auto &edge : graph[start]) { pq.emplace(make_pair(edge.first, edge.second)); } int res = 0, count = 0; while(!pq.empty()) { auto cur = pq.top(); pq.pop(); if(visited[cur.first]) continue; visited[cur.first] = true; res += cur.second; ++count; if(count == n - 1) return res; for(auto &edge : graph[cur.first]) { if(!visited[edge.first]) pq.emplace(make_pair(edge.first, edge.second)); } } return -1; }
|
拓扑排序算法
Leetcode上的 210.
课程表 II 便是一道典型的拓扑排序题,问题如下:
现在你总共有 numCourses
门课需要选,记为 0
到 numCourses - 1
。给你一个数组 prerequisites
,其中 prerequisites[i] = [ai, bi]
,表示在选修课程
ai
前 必须 先选修 bi
。
例如,想要学习课程 0
,你需要先完成课程 1
,我们用一个匹配来表示:[0,1]
。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回任意一种就可以了。如果不可能完成所有课程,返回一个空数组。
拓扑排序分为DFS
版本和BFS
版本。
DFS版本
步骤:
- 首先,需要先建立一个 DAG(有向无环图),包含顶点和边。
- 初始化入度为 0 的顶点队列,将初始顶点加入队列中。
- 从队列头部开始处理。
- 遍历该顶点的出边,将出边对应的顶点的入度减 1。
- 如果入度为 0,则将该顶点加入队列末尾。
- 重复步骤
3-5
直到队列为空。
DFS
版本实现类似二叉树的前序遍历或者树的DFS
遍历,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| class Solution { public: vector<int> visited; vector<int> inedges; vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) { vector<vector<int>> graph(numCourses, vector<int>()); visited = vector<int>(numCourses, 0); inedges = vector<int>(numCourses, 0); buildGraph(numCourses, prerequisites, graph, inedges); vector<int> path; for(int i = 0; i < numCourses; ++i) { if(inedges[i] == 0) dfs(graph, path, i, numCourses); } return (int)path.size() == numCourses ? path : vector<int>(); } void dfs(vector<vector<int>> &graph, vector<int> &path,int s, int numCourses) { if(visited[s]) return ; visited[s] = 1; path.emplace_back(s); if((int)path.size() == numCourses) { return ; } for(int &i : graph[s]) { --inedges[i]; if(inedges[i] == 0) dfs(graph, path, i, numCourses); } } void buildGraph(int numCourses, vector<vector<int>> &prerequisites, vector<vector<int>> &graph, vector<int> &inedges) { for(const auto &p : prerequisites) { int from = p[1], to = p[0]; graph[from].emplace_back(to); ++inedges[to]; } } };
|
BFS版本
BFS
版本和DFS
版本的思路其实是一样的,BFS
和DFS
的区别类似树的遍历,无非是在遍历节点的时候以不同的顺序访问其邻接节点。DFS
的实现用到了递归而BFS
的实现用到了队列,类似二叉树的层序遍历或树的BFS
遍历。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| class Solution { public: vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) { vector<vector<int>> graph(numCourses, vector<int>()); vector<int> visited(numCourses, 0); vector<int> inedges(numCourses, 0); buildGraph(numCourses, prerequisites, graph, inedges); queue<int> q; for(int i = 0; i < numCourses;++i) { if(inedges[i] == 0) q.emplace(i); } int count = 0; vector<int> path; while(!q.empty()) { int cur = q.front(); q.pop(); visited[cur] = 1; path.emplace_back(cur); for(int i : graph[cur]) { --inedges[i]; if(inedges[i] == 0) q.emplace(i); } } return (int)path.size() == numCourses? path : vector<int>(); }
void buildGraph(int numCourses, vector<vector<int>> &prerequisites, vector<vector<int>> &graph, vector<int> &inedges) { for(const auto &p : prerequisites) { int from = p[1], to = p[0]; graph[from].emplace_back(to); ++inedges[to]; } } };
|