刷题 - 图论

1 | bfs/dfs | 网格染色

200. 岛屿数量

  • 访问到马上就染色(将visited标为 true)
  • auto [cur_x, cur_y] = que.front(); 结构化绑定(C++17)
  • 也可以不使用 visited数组,直接修改原始数组
  • 时间复杂度: O(n * m),最多将 visited 数组全部标为 true
  • 空间复杂度:考虑visited 数组的话就是 O(n*m), 原地修改就是 O(min(n, m)) 只考虑递归栈深度(DFS)或队列最大深度(BFS),栈深度或队列最多为网格的一边大小

在这里插入图片描述

[模板][dfs][染色]

class Solution {
public:
    const int direction[4][2] = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};
    // 为什么 dfs 看起来好像没有终止条件?因为已经通过代码保证传入的节点都是合法节点
    void dfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y) {
        for (int i = 0; i < 4; ++i) {
            int next_x = x + direction[i][0], next_y = y + direction[i][1];
            if (next_x < 0 || next_x >= grid.size() || \
                next_y < 0 || next_y >= grid[0].size() || \
                grid[next_x][next_y] == '0' || \
                visited[next_x][next_y] == true) {
                continue;
            }
            visited[next_x][next_y] = true;
            dfs(grid, visited, next_x, next_y);
        }
    }
    int numIslands(vector<vector<char>>& grid) {
        int n = grid.size(), m = grid[0].size();
        vector<vector<bool>> visited(n, vector<bool>(m, false));
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (grid[i][j] == '1' && visited[i][j] == false) {
                    ++ans;
                    visited[i][j] = true;
                    dfs(grid, visited, i, j);
                }
            }
        }
        return ans;
    }
};
class Solution {
public:
    const int direction[4][2] = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};
    // 不管节点是否合法,上来就dfs,然后在终止条件的地方进行判断
    // 效率会略低一些
    void dfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y) {
        if (grid[x][y] == '0' || visited[x][y]) return;
        visited[x][y] = true;
        for (int i = 0; i < 4; ++i) {
            int next_x = x + direction[i][0], next_y = y + direction[i][1];
            if (next_x < 0 || next_x >= grid.size() || \
                next_y < 0 || next_y >= grid[0].size()) {
                continue;
            }
            dfs(grid, visited, next_x, next_y);
        }
    }
    int numIslands(vector<vector<char>>& grid) {
        int n = grid.size(), m = grid[0].size();
        vector<vector<bool>> visited(n, vector<bool>(m, false));
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (grid[i][j] == '1' && visited[i][j] == false) {
                    ++ans;
                    dfs(grid, visited, i, j);
                }
            }
        }
        return ans;
    }
};

[模板][bfs][染色]

class Solution {
public:
    const int direction[4][2] = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};
    void bfs(vector<vector<char>>& grid, vector<vector<bool>>& visited, int x, int y) {
        queue<pair<int,int>> que;
        visited[x][y] = true;   // 在加入队列之前将其标为访问过
        que.push(make_pair(x, y));
        while (!que.empty()) {
            auto [cur_x, cur_y] = que.front();  // 结构化绑定
            que.pop();
            for (int i = 0; i < 4; ++i) {
                int next_x = cur_x + direction[i][0], next_y = cur_y + direction[i][1];
                if (next_x < 0 || next_x >= grid.size() || \
                    next_y < 0 || next_y >= grid[0].size() || \
                    grid[next_x][next_y] == '0' || \
                    visited[next_x][next_y] == true) {
                    continue;
                }
                visited[next_x][next_y] = true;
                que.push(make_pair(next_x, next_y));
            }
        }
    }
    int numIslands(vector<vector<char>>& grid) {
        int n = grid.size(), m = grid[0].size();
        vector<vector<bool>> visited(n, vector<bool>(m, false));
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (grid[i][j] == '1' && visited[i][j] == false) {
                    ++ans;
                    bfs(grid, visited, i, j);
                }
            }
        }
        return ans;
    }
};

在这里插入图片描述

类似题目

695. 岛屿的最大面积

在 染色的时候统计面积即可

visited[next_x][next_y] = true;
++area;

130. 被围绕的区域

在这里插入图片描述

两个步骤:

  • 从地图周边出发,将 ‘O’ 都做上标记,visited = true
  • 然后再遍历一遍地图,遇到 ‘O’ 且没做过标记的,那么都是地图中间的 ‘O’ ,全部改成 ‘X’ 就行

417. 太平洋大西洋水流问题

在这里插入图片描述

class Solution {
public:
    // 太平洋:i == 0 || j == 0
    // 大西洋:i == m-1 || j == n-1
    // 方式1:从每个节点出发,如果可以到达太平洋且大西洋,记录结果
    // 方式2:从四周出发,逆向找可达节点

    int direction[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1};
    // array<array<int, 2>, 4> direction = {-1, 0, 0, -1, 1, 0, 0, 1};
    void dfs(const vector<vector<int>>& heights, vector<vector<bool>>& visited, int x, int y) {
        for (int i = 0; i < 4; ++i) {
            int next_x = x + direction[i][0], next_y = y + direction[i][1];
            if (next_x < 0 || next_x >= heights.size() || \
                next_y < 0 || next_y >= heights[0].size() || \
                visited[next_x][next_y] == true ||
                heights[next_x][next_y] < heights[x][y]) {
                    continue;
            }
            visited[next_x][next_y] = true;
            dfs(heights, visited, next_x, next_y);
        }
    }

    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
        int m = heights.size(), n = heights[0].size();
        vector<vector<bool>> pacific_visited(m, vector<bool>(n, false));
        vector<vector<bool>> atlantic_visited(m, vector<bool>(n, false));
        
        for (int i = 0; i < n; ++i) {
            if (pacific_visited[0][i] == false) {
                pacific_visited[0][i] = true;
                dfs(heights, pacific_visited, 0, i);
            }
        }
        for (int i = 1; i < m; ++i) {
            if (pacific_visited[i][0] == false) {
                pacific_visited[i][0] = true;
                dfs(heights, pacific_visited, i, 0);
            }
        }

        for (int i = 0; i < n; ++i) {
            if (atlantic_visited[m-1][i] == false) {
                atlantic_visited[m-1][i] = true;
                dfs(heights, atlantic_visited, m-1, i);
            }
        }
        for (int i = 0; i < m - 1; ++i) {
            if (atlantic_visited[i][n-1] == false) {
                atlantic_visited[i][n-1] = true;
                dfs(heights, atlantic_visited, i, n-1);
            }
        }

        vector<vector<int>> result;
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (pacific_visited[i][j] && atlantic_visited[i][j]) {
                    result.push_back({i,j});
                }
            }
        }

        return result;
    }
};

建造最大工岛

在这里插入图片描述

2 | bfs | 无权最短路径

  • 如果只需要路径长度,每次先获取队列的长度然后再往外扩展,或者可以采用使用 int 类型的 visited 数组来记录路径长度
  • 如果需要输出路径,需要记录节点的 前驱节点,最后从终点到起点获取前驱节点从而输出路径

[模板] 获取最短路径长度 - 记录steps

int bfs_shortest_path_length(const vector<vector<int>>& graph, int start, int goal) {
    int n = graph.size(); // 节点的总数
    queue<int> q; // 队列用于BFS
    vector<bool> visited(n, false); // 记录已经访问过的节点
    q.push(start);
    visited[start] = true;
    int steps = 0; // 用于记录步数
    while (!q.empty()) {
        int que_size = q.size();
        while (que_size--) {
            int node = q.front();
            q.pop();
            if (node == goal) {
                return steps;
            }
            for (int neighbor : graph[node]) {
                if (!visited[neighbor]) {
                    visited[neighbor] = true;
                    q.push(neighbor);
                }
            }
        }
        ++steps;
    }
    return -1;
}

[模板] 获取最短路径 - 记录前驱节点

// 使用BFS寻找从start到goal的最短路径,并使用前驱数组记录路径
vector<int> bfs_shortest_path(const vector<vector<int>>& graph, int start, int goal) {
    int n = graph.size(); // 节点的总数
    vector<int> prev(n, -1); // 用于记录前驱节点,初始化为-1表示未访问
    queue<int> q; // 队列用于BFS
    unordered_set<int> visited; // 记录已经访问过的节点
    // 初始化BFS
    q.push(start);
    visited.insert(start);
    // 开始BFS
    while (!q.empty()) {
        int node = q.front();
        q.pop();
        // 如果找到了目标节点,回溯路径
        if (node == goal) {
            vector<int> path;
            for (int at = goal; at != -1; at = prev[at]) {
                path.push_back(at);
            }
            reverse(path.begin(), path.end());
            return path; // 返回从start到goal的路径
        }
        // 遍历当前节点的所有邻居
        for (int neighbor : graph[node]) {
            if (visited.find(neighbor) == visited.end()) {
                // 如果邻居节点未被访问,标记为已访问并记录前驱节点
                visited.insert(neighbor);
                prev[neighbor] = node;
                q.push(neighbor);
            }
        }
    }

    // 如果没有找到路径,返回空路径
    return {};
}

类似题目

[最短路径长度]1926. 迷宫中离入口最近的出口

在这里插入图片描述

class Solution {
public:
    int nearestExit(vector<vector<char>>& maze, vector<int>& entrance) {
        int m = maze.size(), n = maze[0].size();
        int x = entrance[0], y = entrance[1];
        queue<pair<int, int>> que;
        maze[x][y] = '+';
        que.push({x, y});
        // 定义四个方向:上、下、左、右
        const int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        int steps = 0;
        // 广度优先搜索
        while (!que.empty()) {
            int q_size = que.size();
            for (int i = 0; i < q_size; ++i) {
                auto [cur_x, cur_y] = que.front();
                que.pop();
                for (int d = 0; d < 4; ++d) {
                    int next_x = cur_x + dir[d][0];
                    int next_y = cur_y + dir[d][1];
                    // 检查边界条件
                    if (next_x < 0 || next_x >= m || \
                        next_y < 0 || next_y >= n || \
                        maze[next_x][next_y] == '+') {
                        continue;
                    }
                    // 如果到达边界并且不是入口,返回步数
                    if (next_x == 0 || next_x == m - 1 ||\
                         next_y == 0 || next_y == n - 1) {
                        return steps + 1;
                    }
                    // 标记为已访问,并将其加入队列
                    maze[next_x][next_y] = '+';
                    que.push({next_x, next_y});
                }
            }
            ++steps;
        }
        // 如果没有找到出口,返回 -1
        return -1;
    }
};

[最短路径长度] 433. 最小基因变化

在这里插入图片描述

class Solution {
public:
    bool canConvert(const string& s1, const string& s2) {
        int diffCount = 0;
        for (int i = 0; i < s1.size(); ++i) {
            if (s1[i] != s2[i]) {
                if (++diffCount > 1) return false;
            }
        }
        return true;
    }

    int minMutation(string startGene, string endGene, vector<string>& bank) {
        if (startGene == endGene) return 0;
        int n = bank.size();
        int startIndex = -1, endIndex = -1;
        // 查找 startGene 和 endGene 在 bank 中的位置
        for (int i = 0; i < n; ++i) {
            if (bank[i] == endGene) endIndex = i;
            else if (bank[i] == startGene) startIndex = i;
        }
        // 如果终止基因不在 bank 中,直接返回 -1
        if (endIndex == -1) return -1;
        // 如果起始基因不在 bank 中,则将其作为附加节点处理
        if (startIndex == -1) startIndex = n;
        // 构建邻接表
        vector<list<int>> adj(n + 1);
        // 如果 startGene 不在 bank 中,构造与它相邻的节点
        if (startIndex == n) {
            for (int i = 0; i < n; ++i) {
                if (canConvert(startGene, bank[i])) {
                    adj[n].push_back(i);
                }
            }
        }
        // 构造 bank 中基因的邻接关系
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                if (canConvert(bank[i], bank[j])) {
                    adj[i].push_back(j);
                    adj[j].push_back(i);
                }
            }
        }
        // BFS 进行最短路径搜索
        queue<int> q;
        vector<bool> visited(n + 1, false);
        q.push(startIndex);
        visited[startIndex] = true;
        int steps = 0;
        while (!q.empty()) {
            int que_size = q.size();
            while (que_size--) {
                int node = q.front();
                q.pop();
                // 找到终止基因,返回步数
                if (node == endIndex) return steps;
                // 遍历邻居节点
                for (int neighbor : adj[node]) {
                    if (!visited[neighbor]) {
                        visited[neighbor] = true;
                        q.push(neighbor);
                    }
                }
            }
            ++steps; // 每一轮BFS增加一步
        }
        // 如果找不到转换路径,返回 -1
        return -1;
    }
};

[最短路径长度]127. 单词接龙

在这里插入图片描述

和最小基因变化基本没区别

3 | dfs | 回溯 | 所有可能路径

[模板] 797. 所有可能的路径

  • 本题为有向无环图,如果存在环的话需要加一个 visited 数组
  • 时间复杂度:O(2^n),最坏情况下可能有 2 的 n 次方条路径,其中 n 是图的节点数。
  • 空间复杂度:O(n),递归深度最深为 n(即从 0 到目标节点的最长路径长度),以及存储路径所需的空间。
// 有向无环图求所有可行路径
// 如果存在环的话需要加一个 visited 数组
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void dfs(vector<vector<int>>& graph, int start) {
        if (start == graph.size() - 1) {
            result.push_back(path);
        }
        for (auto& next : graph[start]) {
            path.push_back(next);
            dfs(graph, next);
            path.pop_back();
        }
    }

    vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
        path.push_back(0);
        dfs(graph, 0);
        return result; 
    }
};

4 | 并查集 | 判断节点相连

[模板][有向图][无向图]

  • 并查集初始化:将所有节点的根节点初始化为节点本身
  • 在查询父节点的时候要执行路径压缩

有向边: 按方向合并

    struct UnionSet {
        vector<int> root;

        UnionSet(int n) : root(n, 0) {
            for (int i = 0; i < n; ++i) {
                root[i] = i;
            }
        }

        int Find(int u) {
            if (u == root[u]) return u;
            root[u] = Find(root[u]); // 路径压缩
            return root[u];
        }

        bool IsConnected(int u, int v) {
            return Find(u) == Find(v);
        }

        void Join(int u, int v) {
            int root_u = Find(u);
            int root_v = Find(v);
            root[root_v] = root_u;
        }
    };

无向边:按秩合并

  • 在并查集(Union-Find)的按秩合并中,通常用于表示树的高度(或深度)
  • 插入新连接的时候选择秩小的作为根节点
    在这里插入图片描述
  • 如果二者秩相同,秩需要加一
    在这里插入图片描述
struct UnionSet{
    vector<int> root;
    vector<int> rank;

    UnionSet(int n) : root(n, 0), rank(n, 1) {
        for (int i = 0; i < n; ++i) {
            root[i] = i;
        }
    }

    int Find(int u) {
        if (u == root[u]) return u;
        root[u] = Find(root[u]); // 路径压缩
        return root[u];
    }

    bool IsConnected(int u, int v) {
        return Find(u) == Find(v);
    }

    void Join(int u, int v) {
        int root_u = Find(u);
        int root_v = Find(v);
        if (rank[root_u] > rank[root_v]) {
            root[root_v] = root_u;
        } else if (rank[root_u] < rank[root_v]) {
            root[root_u] = root_v;
        } else {
            root[root_u] = root_v;
            rank[root_v]++;
        }
    }
};

类似题目

[无向边]684. 冗余连接

在这里插入图片描述

如果边的两个节点已经出现在同一个集合里,说明这条边的两个节点已经连在一起了,再加入这条边一定就出现环了。

    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        int n = edges.size();
        UnionSet union_set(n + 1);
        for (auto& edge : edges) {
            int u = edge[0], v = edge[1];
            if (union_set.IsConnected(u, v)) {
                return {u, v};
            } else {
                union_set.Join(u, v);
            }
        }
        return {};
    }

[有向边]685. 冗余连接 II

参考链接:【困难题简单做】分类讨论+并查集,击破hard
在这里插入图片描述

class Solution {
public:
    // 查找冗余的有向边
    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
        int n = edges.size();
        vector<int> parent(n + 1, 0);
        vector<int> edge_to_remove1, edge_to_remove2;
        int two_in_node = -1;

        // 检测入度为2的情况
        for (auto& edge : edges) {
            int u = edge[0], v = edge[1];
            if (parent[v] == 0) {
                parent[v] = u;
            } else {
                // 发现入度为2的节点 v
                two_in_node = v;
                edge_to_remove1 = {parent[v], v}; // 之前的边
                edge_to_remove2 = {u, v}; // 当前的边
                break;
            }
        }

        // 重建并查集,处理有环的情况
        UnionSet union_set(n + 1);
        for (auto& edge : edges) {
            int u = edge[0], v = edge[1];
            if (edge == edge_to_remove2) {
                // 跳过第二条边,先验证第一条边
                continue;
            }
            if (union_set.IsConnected(u, v)) {
                // 出现环
                //  + 如果不存在入度为2的节点,只需要考虑环的问题
                //  + 说明正是[u,v]导致环的出现,删除 [u, v] 即可    
                if (two_in_node == -1) return edge;

                //  + 存在入度为 2 的节点
                //  + edge_to_remove1 必然属于这个环的一部分
                //  + 否则就会出现两条不合法的边了,不合题意
                //  + 因此我们删除两个不合法的交集,也即 edge_to_remove1
                return edge_to_remove1;
            }
            union_set.Join(u, v);
        }

        // 循环正常结束,说明没有出现环
        // 说明异常情况为:存在入度为 2 的节点
        // 删除最后一条导致入度为 2 的边,也即 edge_to_remove2
        return edge_to_remove2;
    }
};

5 | 拓扑排序 | 有向图 | 判断是否有环 | 无环图线性排序

[模板]207. 课程表

  • 遍历所有边,记录所有节点的入度,将入度为 0 的节点加入队列和结果数组
  • 当队列不为空时,从队列中弹出节点,遍历节点的所有邻居,将邻居的入度减1,如果此时邻居的入度为0,则将该邻居加入队列和结果数组
  • 队列为空,若结果数组的长度不等于节点个数,说明存在环,否则返回结果数组
class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> indegrees(numCourses, 0);
        vector<list<int>> adj(numCourses);
        for (const auto& pre : prerequisites) {
            ++indegrees[pre[0]];
            adj[pre[1]].push_back(pre[0]);
        }
        int count = 0;
        queue<int> que;
        for (int i = 0; i < numCourses; ++i) {
            if (indegrees[i] == 0) {
                que.push(i);
                ++count;
            }
        }
        while (!que.empty()) {
            int cur = que.front();
            que.pop();
            for (auto next : adj[cur]) {
                if (--indegrees[next] == 0) {
                    que.push(next);
                    ++count;
                }
            }
        }
        return (count == numCourses);
    }
};

6 | Dijistra | 带正权图最短路径

[模板]参加科学大会

  • 使用 堆来存储当前最短路径节点 {节点到起点的距离, 节点索引}
  • 每次从优先队列中选择当前距离最小的未处理节点
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
#include <list>
using namespace std;

int dijkstra(vector<list<pair<int, int>>>& adj_list) {
    int n = adj_list.size() - 1; 
    int start = 1, end = n;
    vector<int> costs(n + 1, INT_MAX);	// 记录距离
    vector<int> visited(n + 1, false);	// 标记是否找到最短路		
    // 堆存储 {节点到起点的距离,节点索引}
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
    // 初始化
    costs[start] = 0;
    pq.push({0, start});

    while (!pq.empty()) {
    	// 每次从优先队列中选择当前距离最小的未处理节点
        int cost = pq.top().first;
        int cur = pq.top().second;
        pq.pop();
        if (visited[cur]) continue;	// 节点已经访问过,跳过
        visited[cur] = true;			// 未访问过,标记为访问过
        if (cur == end) return costs[end];	// 到达终点
        // 遍历相邻节点
        for (auto& [next, time] : adj_list[cur]) {
            if (!visited[next] && costs[cur] + time < costs[next]) {
            	// 找到更短的路径了   
                costs[next] = costs[cur] + time;
                pq.push({costs[next], next});
            }
        }
    }
    return -1;  // 如果没有路径到达终点
}

int main(int argc, char* argv[]) {
    if (argc == 1) return -1;
    freopen(argv[1], "r", stdin);
    int n, m;
    cin >> n >> m;  // n 个 车站 m 条公路
    vector<list<pair<int, int>>> adj_list(n + 1);
    for (int i = 0; i < m; ++i) {   // 输入 m 条边:起点 终点 和 时间
        int start, end, time;
        cin >> start >> end >> time;
        adj_list[start].push_back({end, time});
    }

    cout << dijkstra(adj_list) << endl;
    return 0;
}

7 | Floyd | 多源最短路 | 动态规划 | 适合稠密图且源点较多

[模板]97. 小明逛公园

在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <vector>
#include <list>
#include <climits>
using namespace std;

int main() {
    int n, m, p1, p2, val;
    cin >> n >> m;

    vector<vector<vector<int>>> grid(n + 1, vector<vector<int>>(n + 1, vector<int>(n + 1, INT_MAX)));  // 因为边的最大距离是10^4
    for(int i = 0; i < m; i++){
        cin >> p1 >> p2 >> val;
        grid[p1][p2][0] = val;
        grid[p2][p1][0] = val; // 注意这里是双向图

    }
    // 开始 floyd
    for (int k = 1; k <= n; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (grid[i][k][k-1] == INT_MAX || grid[k][j][k-1] == INT_MAX) {
                    grid[i][j][k] = grid[i][j][k-1];
                } else {
                    grid[i][j][k] = min(grid[i][j][k-1], grid[i][k][k-1] + grid[k][j][k-1]);
                }      
            }
        }
    }
    // 输出结果
    int z, start, end;
    cin >> z;
    while (z--) {
        cin >> start >> end;
        if (grid[start][end][n] == INT_MAX) cout << -1 << endl;
        else cout << grid[start][end][n] << endl;
    }
}

空间优化

#include <iostream>
#include <vector>
#include <list>
#include <climits>
using namespace std;

int main() {
    int n, m, p1, p2, val;
    cin >> n >> m;

    vector<vector<int>> grid(n + 1, vector<int>(n + 1, INT_MAX));  // 因为边的最大距离是10^4
    for(int i = 0; i < m; i++){
        cin >> p1 >> p2 >> val;
        grid[p1][p2] = val;
        grid[p2][p1] = val; // 注意这里是双向图

    }
    // 开始 floyd
    for (int k = 1; k <= n; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (grid[i][k] != INT_MAX && grid[k][j] != INT_MAX) {
                    grid[i][j] = min(grid[i][j], grid[i][k] + grid[k][j]);
                }      
            }
        }
    }
    // 输出结果
    int z, start, end;
    cin >> z;
    while (z--) {
        cin >> start >> end;
        if (grid[start][end] == INT_MAX) cout << -1 << endl;
        else cout << grid[start][end] << endl;
    }
}

8 | Prim | 贪心 | 最小生成树 | 花最小的代价连通所有的点

[模板] 53. 寻宝(第七期模拟笔试)

图-最小生成树-Prim(普里姆)算法和Kruskal(克鲁斯卡尔)算法

#include<iostream>
#include<vector>
#include <climits>

using namespace std;

int main() {
    int v, e;   // 顶点数和边数
    cin >> v >> e;
    
    vector<vector<int>> grid(v + 1, vector<int>(v + 1, INT_MAX));
    
    int x, y, k;    // 起点,终点和权值
    while (e--) {
        cin >> x >> y >> k;
        grid[x][y] = k;
        grid[y][x] = k;   // 无向图
    }
    
    // Prim算法部分
    vector<int> minDist(v + 1, INT_MAX);        // 所有节点到最小生成树的最小距离
    vector<bool> isInTree(v + 1, false);        // 是否已经在最小生成树中
    minDist[1] = 0;                             // 从第1个节点开始
    
    int result = 0;
    
    for (int i = 1; i <= v; i++) {
        int cur = -1;
        int minVal = INT_MAX;

        // 找到距离当前生成树最近的节点
        for (int j = 1; j <= v; j++) {
            if (!isInTree[j] && minDist[j] < minVal) {
                minVal = minDist[j];
                cur = j;
            }
        }

        // 如果没有找到合适的节点,则图不连通
        if (cur == -1) {
            cout << "Graph is not connected" << endl;
            return -1;
        }

        isInTree[cur] = true;
        result += minVal;  // 把该点与生成树相连的最小边的权值加入结果

        // 更新其他节点到生成树的最短距离
        for (int j = 1; j <= v; j++) {
            if (!isInTree[j] && grid[cur][j] != INT_MAX && grid[cur][j] < minDist[j]) {
                minDist[j] = grid[cur][j];
            }
        }
    }
    
    // 输出最小生成树的权重
    cout << result << endl;

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/901250.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

英语语法笔记

内容源于b站英语兔 目录 一、综述 动作&#xff08;动词&#xff09;&#xff1a; 1.可以独立完成的动作&#xff1a;主语加不及物动词 2.有1个动作的承受者&#xff1a;主语单及物动词宾语 3.有2个承受者&#xff1a;主语双及物动词间接宾语直接宾语 4.只有1个动作承受…

从零开始:构建一个高效的开源管理系统——使用 React 和 Ruoyi-Vue-Plus 的实战指南

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

Kafka-代码示例

一、构建开发环境 File > New > Project 选择一个最简单的模板 项目和坐标命名 配置maven路径 添加maven依赖 <dependencies><!-- https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients --><dependency><groupId>org.apache.kaf…

学习笔记——动态路由——OSPF(距离矢量协议)OSPF路由类型

OSPF路由类型 在OSPF中&#xff0c;路由类型指的是不同种类的路由&#xff0c;用于描述网络中不同的路由信息及其传输方式。 1、Intra Area路由(区域内路由) Intra Area路由(区域内路由/本地路由/内部路由)是OSPF协议中的一种路由类型&#xff0c;用于描述在同一个OSPF区域内…

【论文阅读】ESRGAN

学习资料 论文题目&#xff1a;增强型超分辨率生成对抗网络&#xff08;ESRGAN: Enhanced Super-Resolution Generative Adversarial Networks&#xff09;论文地址&#xff1a;[1809.00219] ESRGAN&#xff1a;增强型超分辨率生成对抗网络代码&#xff1a;xinntao / ESRGAN&am…

牛客周赛 Round 64(博弈论、思维、构造、LCA、换根DP)

文章目录 牛客周赛 Round 64(博弈论、思维、构造、LCA、换根DP)A. 小红的对错判断B. 小红的幂表达C. 小红的前缀询问D. 小红和小紫的博弈游戏&#xff08;博弈论&#xff09;E. 小红的字符串重排&#xff08;思维、构造&#xff09;F&G. 小红的树上路径查询&#xff08;LCA…

LabVIEW共享变量通信故障

问题概述&#xff1a; 在LabVIEW项目中&#xff0c;使用IO服务器创建共享变量&#xff0c;并通过LabVIEW作为从站进行数据通信。通讯在最初运行时正常&#xff0c;但在经过一段时间或几个小时后&#xff0c;VI前面板出现错误输出&#xff0c;导致数据传输失败。虽然“分布式系统…

equals方法重写--自写Person类

1.Object类的equals方法&#xff08;源码&#xff09; public boolean equals(Object obj) {return (this obj);//判断如果比较的两个对象是同一个对象&#xff0c;则返回true} 2.String类重写Object类的equals方法&#xff08;源码&#xff09; public boolean equals(Obje…

Git的初次使用

一、下载git 找淘宝的镜像去下载比较快 点击这里 二、配置git 1.打开git命令框 2.设置配置 git config --global user.name "你的用名"git config --global user.email "你的邮箱qq.com" 3.制作本地仓库 新建一个文件夹即可&#xff0c;然后在文件夹…

网络一些相关术语

目录 网络一些相关术语 转发平面效率 可扩展性 控制平面 网络拓扑 服务质量&#xff08;QoS&#xff09; 网络协议 网络带宽 网络拥塞 网络安全 网络冗余 网络切片 网络延迟 网络地址转换&#xff08;NAT&#xff09; 虚拟专用网络&#xff08;VPN&#xff09; …

尚硅谷-react教程-求和案例-优化2-Provider组件的使用-笔记

在这篇文章的基础上&#xff0c;https://blog.csdn.net/weixin_41987016/article/details/143257435?spm1001.2014.3001.5501 继续优化&#xff0c; 借助Provider批量的给整个应用里面的所有的容器组件的添加store 原来的,src/index.js import React from "react&quo…

从0开始深度学习(17)——数值稳定性和模型初始化

在每次训练之前&#xff0c;都会对模型的参数进行初始化&#xff0c;初始化方案的选择在神经网络学习中起着举足轻重的作用&#xff0c; 它对保持数值稳定性至关重要。 我们选择哪个函数以及如何初始化参数可以决定优化算法收敛的速度有多快。 糟糕选择可能会导致我们在训练时遇…

云电脑的真实使用体验

最近这几年&#xff0c;关于云电脑的宣传越来越多。 小枣君之前曾经给大家介绍过云电脑&#xff08;链接&#xff09;。简单来说&#xff0c;它属于云计算的一个应用。通过在云端虚拟出一些虚拟电脑&#xff0c;然后让用户可以远程使用&#xff08;仍然需要借助本地电脑&#x…

jupyter notebook改变默认启动路径

安装好Anaconda 3以后,就可以使用Jupyter notebook了,但是我们打开Jupyter notebook后,发现界面是一个默认的目录,这个目录在哪里?如果想把自己写的程序文件保存在自己新建的一个文件夹里,修改默认目录到自建的文件夹下,该如何做呢! 先看一下Jupyter notebook的默认界…

【ubuntu18.04】ubuntu18.04升级cmake-3.29.8及还原系统自带cmake操作说明

参考链接 cmake升级、更新&#xff08;ubuntu18.04&#xff09;-CSDN博客 升级cmake操作说明 下载链接 Download CMake 下载版本 下载软件包 cmake-3.30.3-linux-x86_64.tar.gz 拷贝软件包到虚拟机 cp /var/run/vmblock-fuse/blockdir/jrY8KS/cmake-3.29.8-linux-x86_64…

【华为路由】OSPF多区域配置

网络拓扑 设备接口地址 设备 端口 IP地址 RTA Loopback 0 1.1.1.1/32 G0/0/0 10.1.1.1/24 RTB Loopback 0 2.2.2.2/32 G0/0/0 10.1.1.2/24 G0/0/1 10.1.2.1/24 RTC Loopback 0 3.3.3.3/32 G0/0/0 10.1.2.2/24 G0/0/1 10.1.3.1/24 RTD Loopback 0 4.4.4…

大模型Transformer笔记:KV缓存

1 MHA&#xff08;Multi-Head Attention&#xff09; 最经典的多头注意力 等价于多个独立的单头注意力的拼接 对于LLM来说&#xff0c;一般都是自回归地一个一个token的输出&#xff0c;也就相当于只有Transformer的decoder input在变化&#xff0c;之前作为prompt部分的是不变…

java智能物流管理系统源码(springboot)

项目简介 智能物流管理系统实现了以下功能&#xff1a; 智能物流管理系统的主要使用者分为管理员&#xff0c;顾客&#xff0c;员工&#xff0c;店主。功能有个人中心&#xff0c;顾客管理&#xff0c;员工管理&#xff0c;店主管理&#xff0c;门店信息管理&#xff0c;门店…

【制造业&电子产品】电脑电子元件检测系统源码&数据集全套:改进yolo11-TADDH

改进yolo11-SCConv等200全套创新点大全&#xff1a;电脑电子元件检测系统源码&#xff06;数据集全套 1.图片效果展示 项目来源 人工智能促进会 2024.10.24 注意&#xff1a;由于项目一直在更新迭代&#xff0c;上面“1.图片效果展示”和“2.视频效果展示”展示的系统图片或者…

蓝桥杯题目理解

1. 一维差分 1.1. 小蓝的操作 1.1.1. 题目解析&#xff1a; 这道题提到了对于“区间”进行操作&#xff0c;而差分数列就是对于区间进行操作的好方法。 观察差分数列&#xff1a; 给定数列&#xff1a;1 3 5 2 7 1 差分数列&#xff1a;1 2 2 -3 5 6 题目要求把原数组全部…