图中的最长环

给你一个 n 个节点的 有向图 ,节点编号为 0 到 n - 1 ,其中每个节点 至多 有一条出边。
图用一个大小为 n 下标从 0 开始的数组 edges 表示,节点 i 到节点 edges[i] 之间有一条有向边。
如果节点 i 没有出边,那么 edges[i] == -1 。

1. 深度优先搜索

采用局部时间戳和延迟修改
class Solution {
public:
    int longestCycle(vector<int>& edges) {
        int res = -1;
        int n = edges.size();
        vector<int> pathval(n);
        for(int i=0;i<n;i++){//遍历所有节点找环
            if(edges[i]<0) continue;//没有出边,或已经遍历过
            int cur = i; int val = 1;
            vector<int> visit;
            while(cur>=0){//递归到没有下一个顶点
                visit.push_back(cur);//加入已遍历节点中
                if(edges[cur]<0) break;//下一节点已遍历,跳出循环,剪枝
                if(pathval[cur]>0){
                    res = max(res,val-pathval[cur]);//计算环的长度
                    break;//这次循环遍历过
                }
                pathval[cur] = val;//顶点赋权值
                val++;//权值增加
                cur= edges[cur];//移动到下一节点
            }
            for(int index:visit)
                edges[index] = -1;//将已遍历的置为-1
        }
        return res;
    }
};

2. 更优雅的写法

运用全局时间戳,减少判断

class Solution {
public:
    int longestCycle(vector<int> &edges) {
        int n = edges.size();
        int res = -1;
        vector<int> time(n);
        for (int i = 0, clock = 1; i < n; i++) {//遍历n个顶点
            if (time[i]) continue; //已经遍历过跳过
            for (int x = i, start_time = clock; x >= 0; x = edges[x]) {//递归到没有下一个顶点
                if (time[x]) { // 重复访问
                    if (time[x] >= start_time) // 找到了一个新的环,关键语句,全局时间戳,避免将原来遍历过的视为环
                        res = max(res, clock - time[x]);
                    break;
                }
                time[x] = clock++;//标记
            }
        }
        return res;
    }
};

3. 拓扑排序

先删掉非环节点
再对每一个环计算环的长度,每个环长度计算更为清晰

入度表(同时充当访问位)

class Solution {
public:
    int longestCycle(vector<int>& edges) {
        //拓扑排序
        int size=edges.size();
        vector<int>cnt(size,0);//储存每个点的入度
        for(int i=0;i<size;i++){//记录每个点的入度
            if(edges[i]!=-1)cnt[edges[i]]++;
        }
        queue<int>q;//队列 用来储存入度为0的点
        for(int i=0;i<size;i++){//将入度为0的点先入队列q
            if(cnt[i]==0)q.emplace(i);
        }
        while(q.size()){//跑一遍拓扑排序
            int top=q.front();q.pop();
            if(edges[top]!=-1&&--cnt[edges[top]]==0) //若下一节点存在,下一节点入度削减,若为0,则入队
                q.push(edges[top]);
        }
        int ans=-1;//返回值ans默认为-1
        //跑完拓扑排序后,cnt[i]!=0 的都是环里面的元素
        for(int i=0;i<size;i++){
            if(cnt[i]){//入度大于0说明有环
                int count=0,cur=i;//count记录环的大小,cur遍历环中每个元素
                while(cnt[cur]){//跑一遍环后退出while循环
                    count++;
                    cnt[cur]--; //删掉前面的边
                    cur=edges[cur]; //移动到下一节点
                }
                ans=max(ans,count);//记录最大的环
            }
        }
        return ans;
    }
};

4. tarjan板子

Tarjan算法是用来求有向图的强连通分量的
基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。
搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量

class Solution {
public:
    int longestCycle(vector<int>& edges) {
        int n = edges.size();
        vector<int> g[n];
        stack<int> st;//需要用到一个栈
        vector<bool> in_st(n);//标识某一点是否在栈中
        vector<int> dfn(n);//dfn时间戳
        vector<int> low(n);//向上能回溯到dfn最小的值
        int t = 0;//全局时间戳
        int result = -1;
        //初始化图
        for (int i=0;i<n;i++){
            if (edges[i]==-1) continue;
            g[i].push_back(edges[i]);
        }
        function<void(int)> tarjan=[&](int root){
            dfn[root]=low[root]=++t;//初始化当前点dfn和low
            in_st[root] = true; //顶点入栈
            st.push(root); //顶点入栈
            for (int i:g[root]){ //遍历相邻节点
                if (!dfn[i]){//如果相连的结点没有计算,那么去计算它
                    tarjan(i);
                    low[root]=min(low[root],low[i]);//更新自身的dfn值
                }else if (in_st[i])
                    low[root]=min(low[root],dfn[i]);//如果在栈中说明能回溯到dfn较小的点,进行更新
            }
            if (dfn[root]==low[root]){//说明当前点是该强连通分量的起始点,从该点到栈顶的点都是此强联通分量中的点
                int tmp=st.top();
                st.pop();
                int ans=1;
                while (tmp!=root){
                    ans++;
                    tmp=st.top();
                    st.pop();
                }
                if (ans>1)//必须要大于一个点才能成环
                    result=max(result,ans);
            }
        };
        for (int i=0;i<n;i++) 
            if (!dfn[i]) tarjan(i);
        return result;

    }
};
posted @ 2023-06-09 02:41  失控D大白兔  阅读(25)  评论(0编辑  收藏  举报