關於有向圖的判環
1.只有正(負)邊權
拓撲排序一遍就好了
記住,圖不一定是聯通時,一定要把每個入度爲零的節點跑一遍!!!
核心代碼如下:
int in[N],timeq=0,n,m;//timeq 時間戳
int vis[N];//標記數組
inline bool dfs(int u){//u 當前節點
bool flag=false;//flag 標記
for(int i=head[u];i;i=e[i].next){//領接表遍歷圖
int v=e[i].to;
if(timeq==vis[v]) return true;//當前邊是返祖邊,則有環
else if(vis[v]) return false;//上次遍歷過了且不是這次遍歷產生的,一定沒有環
flag=dfs(v);//遍歷
if(flag) return true;
}
return false;
}
int main(){
scanf("%d%d",&n,&m);//n 節點數 m 邊數
for(int i=1;i<=m;i++){
int x,y,val;
scanf("%d%d%d",&x,&y,&val);// x->y的邊權值爲val
add(x,y,val);//鄰接表存邊
in[y]++;//統計入度
}
for(int i=1;i<=n;i++)//順次訪問入度爲0的節點
if(!in[i]){
vis[i]=++timeq;//標記訪問次數
if(dfs(i)){//遍歷
printf("Yes");//有環
return 0;
}
}
printf("No");//無環
}
2.既有正權邊又有負權邊
這時,我們就要用SPFA(此處所講的是dfs版),其實可以用堆優化,但是作者懶,在這不想寫
其實也麼什麼好講的,主要看毒瘤出題人有沒有給重邊和自環代碼實現
下面的代碼包含處理重邊和自環的情況:
#include<stdio.h>
#define N 2007
using namespace std;
#define INF 0x3f3f3f3f
struct E{
int next,to,dis;
}e[N<<2];
int head[N],cnt=0,n,m,dis[N],st;
bool vis[N];
inline void read(int &x){
x=0;int flag=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') flag=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-48;c=getchar();}
x*=flag;
}
inline void add(int id,int to,int dis){//存圖
cnt++;
e[cnt].next=head[id];
e[cnt].to=to;
e[cnt].dis=dis;
head[id]=cnt;
}
inline bool dfs(int u){
vis[u]=true;//標記已訪問過
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(u==v&&e[i].dis<0) return true;//如果是自環且dis小於0,此環定是負環
if(dis[v]>dis[u]+e[i].dis){//更新
if(vis[v]) return true;//如果訪問過且這次又更新了,一定有負環
dis[v]=dis[u]+e[i].dis;
if(dfs(v)) return true;//往下遍歷
}
}
vis[u]=false;//回溯
return false;//返回沒有環
}
int main(){
read(n);read(m);read(st);//st 源點
for(int i=1;i<=n;i++)
dis[i]=INF,vis[i]=false;//初始化 dis 到源點的最短路距離 vis 標記數組
for(int i=1;i<=m;i++){
int x,y,dis;
read(x);read(y);read(dis);
add(x,y,dis);
}
dis[st]=0;//初始源點到自身距離爲0
bool flag=dfs(st);
if(flag) printf("Yes\n");
else printf("No\n");
}
關於無向圖的判環
無向圖就更簡單了
直接遍歷一遍,如果遇到返祖邊則判斷一下當前記錄的dis[u]+e[i].dis是否爲負
如果是,則一定有負環,否則當前沒有負環,回溯
其餘情況直接往下遍歷就是了,沒有過多技巧,這裏就不再贅述