刷题 - 图论

news2024/12/24 3:16:37

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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

生信软件39 - GATK最佳实践流程重构,提高17倍分析速度的LUSH流程

1. LUSH流程简介 基因组测序通常用于分子诊断、分期和预后&#xff0c;而大量测序数据在分析时间方面提出了挑战。 对于从FASTQ到VCF的整个流程&#xff0c;LUSH流程在非GVCF和GVCF模式下都大大降低了运行时间&#xff0c;30 X WGS数据耗时不到2 h&#xff0c;从BAM到VCF约需…

力扣143:重排链表

给定一个单链表 L 的头节点 head &#xff0c;单链表 L 表示为&#xff1a; L0 → L1 → … → Ln - 1 → Ln请将其重新排列后变为&#xff1a; L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … 不能只是单纯的改变节点内部的值&#xff0c;而是需要实际的进行节点交换。 示…

【已解决】C# NPOI如何在Excel文本中增加下拉框

前言 上图&#xff01; 解决方法 直接上代码&#xff01;&#xff01;&#xff01;&#xff01;综合了各个大佬的自己修改了一下&#xff01;可以直接规定在任意单元格进行设置。 核心代码方法块 #region Excel增加下拉框/// <summary>/// 增加下拉框选项/// </s…

在 Ubuntu 上安装 OpenCV 3.2.0 的详细指南

以下步骤将指导您如何在 Ubuntu 系统上从源码编译并安装 OpenCV 3.2.0。 步骤 1&#xff1a;更新系统并安装必备工具 首先&#xff0c;更新您的系统并安装编译 OpenCV 所需的基本工具和库。 sudo apt-get update sudo apt-get upgrade sudo apt-get install build-essential…

docker 可用镜像服务地址(2024.10.25亲测可用)

1.错误 Error response from daemon: Get “https://registry-1.docker.io/v2/” 原因&#xff1a;镜像服务器地址不可用。 2.可用地址 编辑daemon.json&#xff1a; vi /etc/docker/daemon.json内容修改如下&#xff1a; {"registry-mirrors": ["https://…

C++进阶之路:日期类的实现、const成员(类与对象_中篇)

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

Qt之QCamera的简单使用

文章目录 一、相机操作相关示例1.摄像头操作内容使用示例2.摄像头信息展示使用示例3.摄像头设置切换、预览操作示例 二、相机使用个人操作理解1.相机类支持信息获取2.相机类曝光、焦点、图像处理控制信息获取3.快速启动相机设置&#xff08;各个设备处于理想状态&#xff09; 三…

内网穿透:如何借助Cloudflare连接没有公网的电脑的远程桌面(RDP)

内网穿透&#xff1a;如何借助Cloudflare连接没有公网的电脑的远程桌面(RDP)-含详细原理配置说明介绍 前言 远程桌面协议(RDP, Remote Desktop Protocol)可用于远程桌面连接&#xff0c;Windows系统&#xff08;家庭版除外&#xff09;也是支持这种协议的&#xff0c;无需安装…

基础数据结构——队列(链表实现,数组实现)

1.概述 计算机科学中&#xff0c;queue 是以顺序的方式维护的一组数据集合&#xff0c;在一端添加数据&#xff0c;从另一端移除数据。习惯来说&#xff0c;添加的一端称为尾&#xff0c;移除的一端称为头&#xff0c;就如同生活中的排队买商品 接口信息如下 public interfa…

Java如何实现PDF转高质量图片

大家好&#xff0c;我是 V 哥。在Java中&#xff0c;将PDF文件转换为高质量的图片可以使用不同的库&#xff0c;其中最常用的库之一是 Apache PDFBox。通过该库&#xff0c;你可以读取PDF文件&#xff0c;并将每一页转换为图像文件。为了提高图像的质量&#xff0c;你可以指定分…

卷积神经网络评价指标

1.评价指标的作用 1. 性能评估&#xff1a;评价指标提供了一种量化的方式来衡量CNN模型的性能。通过这些指标&#xff0c;我们可以了解模型在特定任务上的表现&#xff0c;比如图像分类、目标检测或图像分割等。 2. 模型比较&#xff1a;不同的模型架构或训练策略可能会产生不…

Pytest-Bdd-Playwright 系列教程(2):支持在多浏览器、多环境中执行测试

Pytest-Bdd-Playwright 系列教程&#xff08;2&#xff09;&#xff1a;支持在多浏览器、多环境中执行测试 前言一、 修改 conftest.py 文件二、创建配置文件三、修改search_steps.py文件四、运行测试 前言 学会本文教程内容后&#xff0c;你将掌握&#xff1a; 通过本文的学…

重构案例:将纯HTML/JS项目迁移到Webpack

我们已经了解了许多关于 Webpack 的知识&#xff0c;但要完全熟练掌握它并非易事。一个很好的学习方法是通过实际项目练习。当我们对 Webpack 的配置有了足够的理解后&#xff0c;就可以尝试重构一些项目。本次我选择了一个纯HTML/JS的PC项目进行重构&#xff0c;项目位于 GitH…

深度学习案例:带有一个隐藏层的平面数据分类

该案例来自吴恩达深度学习系列课程一《神经网络和深度学习》第三周编程作业&#xff0c;作业内容是设计带有一个隐藏层的平面数据分类。作业提供的资料包括测试实例&#xff08;testCases.py&#xff09;和任务功能包&#xff08;planar_utils.py&#xff09;&#xff0c;下载请…

Jetpack架构组件_LiveData组件

1.LiveData初识 LiveData:ViewModel管理要展示的数据&#xff08;VM层类似于原MVP中的P层&#xff09;&#xff0c;处理业务逻辑&#xff0c;比如调用服务器的登陆接口业务。通过LiveData观察者模式&#xff0c;只要数据的值发生了改变&#xff0c;就会自动通知VIEW层&#xf…

Flutter TextField和Button组件开发登录页面案例

In this section, we’ll go through building a basic login screen using the Button and TextField widgets. We’ll follow a step-bystep approach, allowing you to code along and understand each part of the process. Let’s get started! 在本节中&#xff0c;我们…

【Python爬虫系列】_031.Scrapy_模拟登陆中间件

课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)教程合集 👈👈…

ArcGIS001:ArcGIS10.2安装教程

摘要&#xff1a;本文详细介绍arcgis10.2的安装、破解、汉化过程。 一、软件下载 安装包链接&#xff1a;https://pan.baidu.com/s/1T3UJ7t_ELZ73TH2wGOcfpg?pwd08zk 提取码&#xff1a;08zk 二、安装NET Framework 3.5 双击打开控制面板&#xff0c;点击【卸载程序】&…

World of Warcraft [CLASSIC][80][the Ulduar]

Ulduar 奥杜尔副本介绍 奥杜尔共计14个BOSS&#xff0c;通常说的10H就是10个苦难模式就是全通&#xff0c;9H就是除了【观察者奥尔加隆】&#xff0c;特别说明开启【观察者奥尔加隆】&#xff0c;是需要打掉困难模式4个守护者的。 所以人们经常说的类似“10H 观察者”、“10H…

Python开发日记 -- 实现bin文件的签名

目录 1.数据的不同表现形式签名值不一样&#xff1f; 2.Binascii模块简介 3.问题定位 4.问题总结 1.数据的不同表现形式签名值不一样&#xff1f; Happy Muscle试运行了一段时间&#xff0c;组内同事再一次提出了新的需求&#xff1a;需要对bin文件签名。 PS&#xff1a;服…