图论问题汇总

时间:2024-11-21 07:27:22

一、最短路径问题

1、单源最短路径

深度或广度优先算法,从起点开始访问所有深度遍历路径或广度优先路径,则到达终点节点的路径有多条,取其中路径权值最短的一条则为最短路径。

  1. void dfs(int cur, int dst){
  2. if(minPath < dst) return;//当前走过路径大于之前最短路径,没必要再走下去
  3. if(cur == n){//临界条件
  4. if(minPath > dst) minPath = dst;
  5. return;
  6. }
  7. else{
  8. int i;
  9. for(i = 1; i <= n; i++){
  10. //保证节点间存在路径,且没有被访问过,不存在的节点路径设置为无穷大inf
  11. if(edge[cur][i] != inf && edge[cur][i] != 0 && mark[i] == 0){
  12. mark[i] = 1;
  13. dfs(i, dst+edge[cur][i]);
  14. mark[i] = 0;
  15. }
  16. }
  17. return;
  18. }
  19. }

2、多源最短路径

弗洛伊德(Floyd-Warshall)算法,基本思想:最开始只允许经过1号顶点进行中转,接下来只允许经过1号和2号顶点进行中转......允许经过1~n号所有顶点进行中转,来不断动态更新任意两点之间的最短路程。即求从i号顶点到j号顶点只经过前k号点的最短路程。

  1. //使用弗洛伊德算法
  2. int k; //依次加入节点k
  3. for(k = 1; k <= n; k++){
  4. for(i = 1; i <= n; i++){
  5. for(j = 1; j <= n; j++){
  6. //保证边存在,不存在的边权值设为无穷大inf,以及存在更短路径
  7. if(edge[i][k] < inf && edge[k][j] < inf && edge[i][j] > edge[i][k] + edge[k][j])
  8. edge[i][j] = edge[i][k] + edge[k][j];
  9. }
  10. }
  11. }

二、判断图中是否有环

深度或广度优先算法,在搜索过程中判断是否遇到重复搜索的节点。

思路:邻接表建图,判断图中是否有环,有环的话就不满足题意

  1. class Solution {
  2. public:
  3. bool dfs(int i,vector<vector<int>>&adjacent,vector<int>&flag){
  4. if(flag[i]==-1) return true; //当前节点已被访问过,无需重复搜索
  5. if(flag[i]==1) return false; //一轮dfs中重复,即有环
  6. flag[i]=1;
  7. for(auto j:adjacent[i]){
  8. if(!dfs(j,adjacent,flag))
  9. return false;
  10. }
  11. flag[i]=-1;
  12. return true;
  13. }
  14. bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
  15. vector<vector<int>>adjacent(numCourses);
  16. vector<int>flag(numCourses);
  17. //邻接表建图
  18. for(int i=0;i<prerequisites.size();i++){
  19. adjacent[prerequisites[i][0]].push_back(prerequisites[i][1]);
  20. }
  21. for(int i=0;i<numCourses;i++) {
  22. if(!dfs(i,adjacent,flag))
  23. return false;
  24. }
  25. return true;
  26. }
  27. };

 

三、并查集

解决连通性问题。

  1. class DisjointSetUnion {
  2. private:
  3. //f即保存节点的父数组,rank为了平衡插入的节点
  4. vector<int> f, rank;
  5. int n;
  6. public:
  7. //初始化
  8. DisjointSetUnion(int _n) {
  9. n = _n;
  10. (n, 1);
  11. (n);
  12. for (int i = 0; i < n; i++) {
  13. f[i] = i;
  14. }
  15. }
  16. //查找
  17. int find(int x) {
  18. return f[x] == x ? x : f[x] = find(f[x]);
  19. }
  20. //并操作
  21. void unionSet(int x, int y) {
  22. int fx = find(x), fy = find(y);
  23. if (fx == fy) {
  24. return;
  25. }
  26. //让节点数少的加入到节点数多的并查集中
  27. if (rank[fx] < rank[fy]) {
  28. swap(fx, fy);
  29. }
  30. rank[fx] += rank[fy];
  31. f[fy] = fx;
  32. }
  33. };

四、最小生成树问题

1、Prim(普利姆)算法

基本思想:普里姆算法在找最小生成树时,将顶点分为两类,一类是在查找的过程中已经包含在树中的(假设为 A 类),剩下的是另一类(假设为 B 类)。对于给定的连通网,起始状态全部顶点都归为 B 类。在找最小生成树时,选定任意一个顶点作为起始点,并将之从 B 类移至 A 类;然后找出 B 类中到 A 类中的顶点之间权值最小的顶点(计算出来A、B所有顶点间的全部边),将之从 B 类移至 A 类,如此重复,直到 B 类中没有顶点为止。所走过的顶点和边就是该连通图的最小生成树。

2、Kruskal(克鲁斯卡尔)算法和并查集

思路: 将所有距离排序,然后扫描,根据扫描到的边是否在连通块中进行更新。

  1. class DisjointSetUnion {
  2. private:
  3. vector<int> f, rank;
  4. int n;
  5. public:
  6. DisjointSetUnion(int _n) {
  7. n = _n;
  8. (n, 1);
  9. (n);
  10. for (int i = 0; i < n; i++) {
  11. f[i] = i;
  12. }
  13. }
  14. int find(int x) {
  15. return f[x] == x ? x : f[x] = find(f[x]);
  16. }
  17. bool unionSet(int x, int y) {
  18. int fx = find(x), fy = find(y);
  19. if (fx == fy) {
  20. return false;
  21. }
  22. if (rank[fx] < rank[fy]) {
  23. swap(fx, fy);
  24. }
  25. rank[fx] += rank[fy];
  26. f[fy] = fx;
  27. return true;
  28. }
  29. };
  30. struct Edge {
  31. int len, x, y;
  32. Edge(int len, int x, int y) : len(len), x(x), y(y) {
  33. }
  34. };
  35. class Solution {
  36. public:
  37. int minCostConnectPoints(vector<vector<int>>& points) {
  38. auto dist = [&](int x, int y) -> int {
  39. return abs(points[x][0] - points[y][0]) + abs(points[x][1] - points[y][1]);
  40. };
  41. int n = points.size();
  42. DisjointSetUnion dsu(n);
  43. vector<Edge> edges;
  44. for (int i = 0; i < n; i++) {
  45. for (int j = i + 1; j < n; j++) {
  46. edges.emplace_back(dist(i, j), i, j);
  47. }
  48. }
  49. sort((), edges.end(), [](Edge a, Edge b) -> int { return < ; });
  50. int ret = 0, num = 1;
  51. for (auto& [len, x, y] : edges) {
  52. if ((x, y)) {
  53. ret += len;
  54. num++;
  55. if (num == n) {
  56. break;
  57. }
  58. }
  59. }
  60. return ret;
  61. }
  62. };