• 图的最大环最长链


    这是图中很基本的问题,很多图的问题可以转化为求图中的最大环或最长链。
    例如Leetcode 5970. 参加会议的最多员工数,等价于求有向图最长环,和长度为2的环加上其外链。

    有向图

    最大环

    有多种方法:

    • 一种是先用拓扑排序将外链去掉,再dfs每一个环
    • 另一种是从某一点出发,记录途径的点,如果遇到已经访问过的点,说明找到了环的入口。减去起始点到入口的距离,就是环的长度。
    • 还有一种有并查集,对于x->y,如果xy同属于一个集合,说明形成了一个环。

    求最小环的方式是类似的。

    假设favorite[i]=v 表示从ii有一条边,这里采用第二种方法

        int DirectMaxCycle(vector<int>& favorite) {
            int n = favorite.size();
            vector<bool> vis(n, false);
            int max_cycle = 0;
            for(int i = 0;i < n;i++) {
                if(vis[i]) continue;
                int cur = i;
                vector<int> cycle;
                while(!vis[cur]) {
                    vis[cur] = true;
                    cycle.push_back(cur);
                    cur = favorite[cur];
                }
                for(int j = 0;j < cycle.size();j++) {
                    if(cycle[j] == cur) {
                        int len = cycle.size() - j;
                        if(len > max_cycle) max_cycle = len;
                        break;
                    }
                }
            }
            return max_cycle;
        }
    

    有向无环图:最长链

    这里有一个很重要的问题,有环怎么办?
    有环的情况下,求最长链是没有意义的。要么保证无环,要么是求连接到环上的链的长度。
    例如求连接到环上的链的长度,需要从入度为0的节点开始,递推计算,于是采用拓扑序。

        int TopologicalSort(vector<int>& favorite) {
            int n = favorite.size();
            vector<bool> vis(n, false);
            vector<int>in(n, 0);
            vector<int>dp(n, 1);
            queue<int> q;
            for(int i = 0;i < n;i++)  in[favorite[i]]++;
            for(int i = 0;i < n;i++) {
                if(in[i] == 0) q.push(i);
            }
            while(!q.empty()) {
                int cur = q.front();
                q.pop();
                // cout << cur << " ";
                dp[favorite[cur]] = max(dp[favorite[cur]], dp[cur] + 1);
                if(--in[favorite[cur]] == 0)  q.push(favorite[cur]);
            }
            // dp[i] 表示到达i的最长链的长度
            int two_point_sum = 0;   // 题目相关部分
            for(int i = 0;i < n;i++) {
                if(i == favorite[favorite[i]]) two_point_sum += dp[i];
            }
            return two_point_sum;
        }
    

    无向图

    最大环

    和有向图类似,略

    无向无环图:最长链

    因为是无环图,求最长链也就是求树的直径

    • 也可以和有向图一样,拓扑序+dp
    • 还有一种有趣的方法,两次dfs。可以证明,从任一点出发,dfs能走到的最远点一定是"直径"的一个端点,然后从这个端点出发,dfs得到另一个端点。

    例如Leetcode310最小数高度,等价于求树的直径
    第一次dfs找到一个端点,再从这个端点出发dfs找到另一个端点,最后在写个dfs得到路径

    class Solution {
    public:
        static const int maxn = 20000+10;
        vector<int>graph[maxn];
        bool vis[maxn];
        int end[2], max_dis=-1;
        void dfs(int s, int dis, int flag) {
            vis[s] = true;
            if(dis >= max_dis) {max_dis = dis; end[flag] = s;}
            for(int i = 0; i < graph[s].size(); i++) {
                int t = graph[s][i];
                if(!vis[t]) {
                    dfs(t, dis+1, flag);
                }
            }
        }
        vector<int>ans;
        void path_dfs(int s, int dis, vector<int>& path) {
            if(s == end[1]) {
                int n = path.size();
    
                // cout << "path: ";
                // for(int i = 0; i < n; i++) {
                //     cout << path[i] << " ";
                // }
                // cout << endl;
    
                if(n%2 == 0)  ans = {path[n/2-1], path[n/2]};
                else ans = {path[n/2]};
                return;
            }
            vis[s] = true;
            for(int i = 0; i < graph[s].size(); i++) {
                int t = graph[s][i];
                if(!vis[t]) {
                    path.push_back(t);
                    path_dfs(t, dis+1, path);
                    path.pop_back();
                }
            }
        }
    
        vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
            
            for(auto& edge : edges) {
                graph[edge[0]].push_back(edge[1]);
                graph[edge[1]].push_back(edge[0]);
            }
            memset(vis, 0, sizeof(vis));
            dfs(0, 0, 0);  // end[0] is rightmost node
            memset(vis, 0, sizeof(vis));
            dfs(end[0], 0, 1);  // end[1] is leftmost node
    
            // cout << end[0] << " " << end[1] << endl;
    
            vector<int>path = {end[0]};
            memset(vis, 0, sizeof(vis));
            path_dfs(end[0], 0, path);
            return ans;
        }
    };
    
    

    也可以双BFS写法,而且相比前面DFS,BFS可以在求最远点的时候得到路径

        int bfs(int s){  // 返回距s的最远点
            memset(vis, 0, sizeof(vis));
            queue<int>q;
            q.push(s);
            vis[s] = true;
            int u;
            while(!q.empty()){
                u = q.front();
                q.pop();
                for(int i = 0; i < graph[u].size(); i++){
                    int v = graph[u][i];
                    if(!vis[v]){
                        vis[v] = true;
                        q.push(v);
                    }
                }
            }
            return u;
        }
    
        int pre[maxn];
        vector<int> path_bfs(int s) {  // 返回s到end的路径
            memset(vis, 0, sizeof(vis));
            memset(pre, -1, sizeof(pre));
            queue<int>q;
            q.push(s);
            vis[s] = true;
            int u;
            while(!q.empty()){
                u = q.front();
                q.pop();
                for(int i = 0; i < graph[u].size(); i++){
                    int v = graph[u][i];
                    if(!vis[v]){
                        vis[v] = true;
                        q.push(v);
                        pre[v] = u;
                    }
                }
            }
            vector<int>path;
            while(u != -1){
                path.push_back(u);
                u = pre[u];
            }
            return path;
        }
    

    注意

    有环图中,双dfs/bfs这种方法是错误的,很容易找到反例:


    图片来自The time complexity of finding the diameter of a graph
    上述方法得到的结果可能是4,而实际是5。

    参考链接

    1. 蓝桥杯--小朋友崇拜圈(有向图求最大环)
    2. 019牛客多校第四场A meeting——树的直径
    3. 洛谷-P2661 信息传递——有向图中的最小环
    4. Leetcode有向图最长环+拓扑排序
    5. Leetcode求树的直径
    个性签名:时间会解决一切
  • 相关阅读:
    GIT基础详解
    JS进阶解析
    JS基础解析
    CSS布局模型解析
    02.CentOS Linux 7.7 系统配置文档
    docker 创建bridge网络和修改默认网段
    selenium浏览器自动化测试工具 进阶使用
    前端导出Excel和打印介绍
    stm32使用gmtime()转换timestamp为日期,出的结果是乱的,不符合预期。改为localtime正常输出
    .net core api action 不能用作 httpget注释的参数名
  • 原文地址:https://www.cnblogs.com/lfri/p/15758120.html
Copyright © 2020-2023  润新知