图论相关

你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往 上,下,左,右 四个方向之一移动,你想要找到耗费 体力 最小的一条路径。

一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。

请你返回从左上角走到右下角的最小 体力消耗值 。

  • 将每个格子抽象成图中的一个点;

  • 将每两个相邻的格子之间连接一条边,长度为这两个格子本身权值的差的绝对值;

  • 需要找到一条从左上角到右下角的「最短路径」,其中路径的长度定义为路径上所有边的权值的最大值。

/*
「二分答案」:我们可以对最短路径的长度进行二分。当我们二分枚举到的长度为 x 时,我们
只保留所有长度 ≤x 的边。随后从左上角开始进行搜索
(深度优先搜索、广度优先搜索)均可,只需要判断是否能够到达右下角即可。
*/
class Solution {
private:
    const int dirs[4][2] = {{-1, 0} , {1 ,0} , {0, 1} , {0, -1}};
public:

    int minimumEffortPath(vector<vector<int>>& heights) {
        int m = heights.size();
        int n = heights[0].size();
        int left = 0, right = 999999 , ans = 0;
        while(left <= right)
        {
            int mid = (left + right) / 2;
            queue<pair<int,int>> q;
            q.emplace(0,0);
            vector<int> seen(m * n);
            seen[0] = 1;
            while(!q.empty())
            {
                auto [ x, y ] =q.front();
                q.pop();
                for(auto [dx,dy] : dirs)
                {
                    int nx = x + dx ;
                    int ny = y + dy ;
                    if (nx >= 0 && nx < m && ny >= 0 && ny < n && !seen[nx * n + ny] && abs(heights[x][y] - heights[nx][ny]) <= mid) {
                        q.emplace(nx, ny);
                        seen[nx * n + ny] = 1;
                    }
                }
            }
            if(seen[m*n -1])
            {
                ans = mid;
                right = mid-1;
            }
            else
            {
                //ans = mid;
                left = mid +1;
            }
        }
        return ans;
    }
};
/*
「并查集」:我们可以将所有边按照长度进行排序并依次添加进并查集,直到左上角和右下角连通为止。
*/

class UnionFind {
public:
    std::vector<int> parent;  // 存储每个元素的父节点
    std::vector<int> size;    // 存储每个集合的大小
    int n;                    // 元素的数量
    int component_count;      // 当前连通分量的数量

public:
    // 构造函数,初始化并设置每个元素的父节点为自己,初始时每个集合的大小为1
    UnionFind(int num_elements) : n(num_elements), component_count(num_elements), 
                                  parent(num_elements), size(num_elements, 1) {
        // 每个元素的父节点初始化为它自己
        std::iota(parent.begin(), parent.end(), 0);
    }

    // 查找元素x所在集合的代表元素(根节点),并进行路径压缩优化
    int find(int x) {
        // 如果x是集合的代表元素(根节点),直接返回
        if (parent[x] == x) {
            return x;
        }
        // 否则递归找到x的根节点,并在回溯过程中进行路径压缩
        return parent[x] = find(parent[x]);
    }

    // 合并两个元素x和y所在的集合
    void unite(int x, int y) {
        // 找到x和y的代表元素
        int root_x = find(x);
        int root_y = find(y);

        // 如果x和y已经在同一个集合中,不需要合并
        if (root_x == root_y) {
            return;
        }

        // 合并时,确保较小的集合并入较大的集合
        if (size[root_x] < size[root_y]) {
            std::swap(root_x, root_y);
        }

        // 将root_y的根节点指向root_x
        parent[root_y] = root_x;
        // 更新合并后集合的大小
        size[root_x] += size[root_y];

        // 合并操作减少了一个连通分量
        --component_count;
    }

    // 判断x和y是否在同一个集合中
    bool connected(int x, int y) {
        return find(x) == find(y);
    }

    // 获取当前的连通分量数
    int getComponentCount() const {
        return component_count;
    }
};


struct Edge{
    int x,y,z;
    Edge(int _x , int _y , int _z): x(_x) , y(_y) ,z(_z) {}
    bool operator< (const Edge& that)const{
        return z < that.z;
    }
};

class Solution {
private:
    const int dirs[4][2] = {{-1, 0} , {1 ,0} , {0, 1} , {0, -1}};
public:
    int minimumEffortPath(vector<vector<int>>& heights) {
        int m = heights.size();
        int n = heights[0].size();
        vector<Edge> edges;
        for(int i = 0 ; i < m ; i ++)
        {
            for(int j = 0 ; j < n ; j ++)
            {
                int id = i * n + j;
                if(i > 0)
                    edges.emplace_back(id - n , id ,abs(heights[i][j] - heights[i-1][j]));
                if(j > 0)
                {
                    edges.emplace_back(id-1 , id , abs(heights[i][j] - heights[i][j-1]));
                }
            }
        }
        sort(edges.begin( ), edges.end());
        UnionFind uf(m * n);
        for(auto edge:edges)
        {
            uf.unite(edge.x , edge.y);
            if(uf.connected(0 , m*n-1))
            {
                return edge.z;
            }
        }
        return 0;
    }
};

有 n 个网络节点,标记为 1 到 n。

给你一个列表 times,表示信号经过 有向 边的传递时间。 times[i] = (ui, vi, wi),其中 ui 是源节点,vi 是目标节点, wi 是一个信号从源节点传递到目标节点的时间。

现在,从某个节点 K 发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1 。

适用于稠密图

class Solution {
public:
    int networkDelayTime(vector<vector<int>>& times, int n, int k) {
        vector<vector<int>> g(n , vector<int>(n ,INT_MAX/2));
        for(auto & e : times)
        {
            g[e[0] -1 ][e[1] -1] = e[2];
        }

        vector<int> dist(n , INT_MAX/2);
        dist[k-1] = 0;
        int ans = 0;
        vector<int> ok(n);
        while(1)
        {
            int x = -1 ;
            for(int i = 0 ; i < n ; i++)
            {
                if(!ok[i] && (x < 0 || dist[i] < dist[x]))
                    x = i;
            }
            if(x < 0)
            {
                return ans;
            }
            if(dist[x] == INT_MAX/2)
            {
                return -1;
            }
            ans = dist[x];
            ok[x] = 1;
            for(int i = 0 ; i < n ; i++)
            {
                dist[i] = min(dist[i] , dist[x] + g[x][i]);
            }
        }
    }
};

适用于稀疏图

class Solution {
public:
    int networkDelayTime(vector<vector<int>>& times, int n, int k) {
        vector<vector<pair<int, int>>> g(n); // 邻接表
        for (auto& t : times) {
            g[t[0] - 1].emplace_back(t[1] - 1, t[2]);
        }

        vector<int> dis(n, INT_MAX);
        dis[k - 1] = 0;
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
        pq.emplace(0, k - 1);
        while (!pq.empty()) {
            auto [dx, x] = pq.top();
            pq.pop();
            if (dx > dis[x]) { // x 之前出堆过
                continue;
            }
            for (auto &[y, d] : g[x]) {
                int new_dis = dx + d;
                if (new_dis < dis[y]) {
                    dis[y] = new_dis; // 更新 x 的邻居的最短路
                    pq.emplace(new_dis, y);
                }
            }
        }
        int mx = ranges::max(dis);
        return mx < INT_MAX ? mx : -1;
    }
};

数据结构中对于稀疏图的定义为:有很少条边或弧(边的条数|E|远小于|V|²)的图称为稀疏图(sparse graph), 反之边的条数|E|接近|V|²,称为稠密图(dense graph)。