图中的最长环
给你一个 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;
}
};