目录
一、图的存储
1、邻接矩阵
2、邻接表
二、连通图和强连通图
1、连通图(无向图)
2、强连通图(有向图)
三、图的判环
1、无向图判环
2、有向图判环(重点)
题目描述
输入格式
输出格式
输入输出样例
说明/提示
如果图的边比较密集(稠密图),或者图的顶点较少(小于1000),那么这个图一般用邻接矩阵来表示。空间复杂度O(V^2),其中V是顶点数目。
如果图的边比较稀疏(稀疏图),或者图的顶点较多(大于1000),那么这个图一般用邻接表来表示。空间复杂度O(V+E),其中V是顶点数目,E是边的数目。
如果无向图中任意两个顶点都有路径可达,那么这个图就是连通图。否则,这个图就是非连通图。非连通图中的极大连通子图,就是一个连通分量。
极大连通子图指的是,在无向图中,一个连通的子图,它包含尽可能多的顶点和边,使得在不增加其他顶点和边的情况下,该子图仍然是连通的。
如果有向图中任意两个顶点都有路径可达,那么这个图就是强连通图。否则,这个图就是非强连通图。非强连通图中的极大强连通子图,就是一个强连通分量。
极大强连通子图指的是,在有向图中,一个强连通的子图,在不增加其他顶点和边的情况下,无法再扩展为更大的强连通子图,那么这个子图就被称为极大强连通子图。
对于无向图,要判断图中有没有环,一般比较简单。具体有两种算法实现:
一是直接用DFS进行深搜,因为对于无向图来说可能有多个连通块,所以进行深搜时,要用数组b[maxn]设置顶点的访问状态。对所有的顶点进行遍历,每次对没有被访问过的顶点进行DFS,这样就可以实现对每个连通块进行DFS。对于每个连通块进行访问时,如果没有访问到处于已访问状态的顶点,那么说明这个无向图没有环,否则,说明这个无向图有环。注意,因为无向图的边可以理解为是双向可达的,即 lastx→x→nextx,其中在x进行访问nextx时,如果只对他进行是否可达判定和是否已访问判定,就做出判断说该图是否有环的话,是不准确的,因为按照DFS的顺序进行访问的话,lastx顶点访问完后,赋值b[lastx]=true,此时下一个要访问的顶点就是x,然后访问从x可达的顶点 。容易知道,遍历x可达的所有顶点时,一定会有x→lastx可达,且b[lastx]=true成立。所以这种情况下,无论如何判断都是会显示有环的。所以在访问nextx时,加上一个判断if(lastx!=nextx),则此时如果还满足可达和已访问,则一定有环。代码如下:
#include
using namespace std;
#define int long long
#define endl '\n'
#define pii pair
#define inf 0x3f3f3f3f
int n;//顶点个数
bool b[110];
int g[110][110];
bool flag=false;//赋值为true则为有环
void dfs(int lastx,int x){
b[x]=true;
for(int i=1;i<=n;i++){//遍历找nextx
if(i!=lastx&&g[x][i]!=inf){ //如果nextx不是lastx,且可达
if(!b[i]) dfs(x,i);// 如果没访问过
else { //如果访问过,则有环
flag=true;
return;
}
}
}
}
signed main(){
cin>>n;
init();//对边进行初始化
for(int i=1;i<=n;i++){
if(!b[i]) dfs(-1,i);
if(flag) {
cout<<"有环";
return 0;
}
}
cout<<"没有环";
return 0;
}
二是用并查集的板子进行判断,在对边初始化的时候,判断加边的两个顶点是否属于同一个集合。如果属于同一个集合,还加边,则成环。如果加了所有边都不存在这个情况,则没有环。代码如下:
#include
using namespace std;
#define int long long
#define endl '\n'
#define pii pair
#define inf 0x3f3f3f3f
int n;//顶点个数
int m;//边的个数
bool flag=false;//赋值为true则为有环
//并查集板子
int f[110];
int findfather(int x){
int z=x;
while(x!=f[x]){
x=f[x];
}
//压缩路径
while(z!=f[z]){
int t=f[z];
f[z]=x;
z=t;
}
return x;
}
void unionSet(int a,int b){
int fa=findfather(a);
int fb=findfather(b);
if(fa!=fb) f[fa]=fb;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++) f[i]=i;
cin>>m;
while(m--){
int a,b;
cin>>a>>b;
int fa=findfather(a);
int fb=findfather(b);
if(fa==fb) flag=true;//说明有环
else unionSet(a,b); //说明还没出现环
}
if(flag) cout<<"有环";
else cout<<"无环";
return 0;
}
有向图判环才是常考的重难点(本蒟蒻的重难点)。对有向图进行判环也有两种算法:
第一种算法依旧是DFS,对于有向图进行DFS要考虑的情况就要比无向图要复杂啦,不过对于有向图不需要考虑lastx。因为在有向图中,如果存在x→lastx且lastx→x,则说明有环。
什么情况下有向图内有环呢?我们全球旅游,什么时候我们的旅游路线是个环呢?我们从起点出发,在本次旅游路线中,如果再次回到了本次路线经过过的地点,那么就说明本次路线有环。注意判环条件是本次路线经过过。为什么是本次路线经过过呢?因为在我们常见的有向图中,大部分都不是强连通图,即每两个顶点都是相互可达的。所以从某一个顶点出发,一般并不能访问完所有的顶点。这里和无向图的区别是,无向图是没有边所以访问不到,有向图是没有相互可达的边,所以访问不到。这里举一个例子:假如有a,b,c三个顶点,有边c→b,b→a,c→a。我们一般从a开始访问,对a进行DFS时,发现哪里都去不了,再从b开始访问,b可以到a,然后a也哪里都去不了,最后从c开始访问,c到b,b到a。如果我们从b开始访问,到了a,发现a哪里都去不了是不是根本就不需要再对a进行访问了呢。如果我们从c开始访问,访问到a,发现哪里都去不了了,那么我们是不是就不需要对a、b进行访问了呢。答案是肯定的,所以我们令每次访问完一条路,如果没有环,那么这一条路上的点就不需要进行二次访问了。这是一个小剪枝。那么怎样才说明有环呢,假如我们从c开始,c→b,b→a,a→c(或者a→b)。这个时候就有环了。这就是我所说的经过这条路上经过过的点,如果有分叉路,注意在一条路走到头后,每回退一个单位,回退的单位就已经不是这条路上已经经过的点了。实现代码如下:
#include
using namespace std;
#define int long long
#define endl '\n'
#define pii pair
#define inf 0x3f3f3f3f
int n,m;
int g[110][110];
bool b[110];//用来判断这个点有没有被访问过
bool vis[110];//用来记录这条路上的点有没有被访问过
bool flag=false;
void dfs(int x){
b[x]=true;
vis[x]=true;
for(int i=0;i>n>>m;
while(m--){
int a,b;
cin>>a>>b;
g[a][b]=1;
}
for(int i=0;i
第二个是拓扑排序判环,这里直接出一个板子题来解释拓扑排序代码,具体实现思路过几天补。(上个学习给学弟讲过拓扑,特意出了拓扑板子题,写的板子,现在忘光光了)
P1347 排序 - 洛谷
一个不同的值的升序排序数列指的是一个从左到右元素依次增大的序列,例如,一个有序的数列 A,B,C,D 表示 A
输入格式
第一行有两个正整数 n,m,n 表示需要排序的元素数量,2≤n≤26,第 1 到 n 个元素将用大写的 A,B,C,D,… 表示。m 表示将给出的形如 A
接下来有 m 行,每行有 3 个字符,分别为一个大写字母,一个 <
符号,一个大写字母,表示两个元素之间的关系。
若根据前 x 个关系即可确定这 n 个元素的顺序 yyy..y
(如 ABC
),输出
Sorted sequence determined after xxx relations: yyy...y.
若根据前 x 个关系即发现存在矛盾(如 A
Inconsistency found after x relations.
若根据这 m 个关系无法确定这 n 个元素的顺序,输出
Sorted sequence cannot be determined.
(提示:确定 n 个元素的顺序后即可结束程序,可以不用考虑确定顺序之后出现矛盾的情况)
输入 #1复制
4 6 A输出 #1复制
Sorted sequence determined after 4 relations: ABCD.输入 #2复制
3 2 A输出 #2复制
Inconsistency found after 2 relations.输入 #3复制
26 1 A输出 #3复制
Sorted sequence cannot be determined.说明/提示
2≤n≤26,1≤m≤600。
#include
using namespace std; #define int long long #define endl '\n' //检查26个字母是否是有向无环图 //每加入一个关系看它有没有环,如果第x个关系加入构成了环,则关系存在矛盾 //如果第x个关系加入后刚好能够看出来他们的关系 ,则输出x和他们的关系 vector G[1000];//邻接表 图 int indegree[1000];//每个顶点的入度 char a='A'-1; int n,m; char ch[3];//用来存每一个的数据的临时数组 vector ans; //用拓扑排序的算法得到唯一的链 bool topSort(){ int id[1000]; bool b[1000]; queue q; // 只能有一个入度为0的点,否则直接返回false memset(b,0,sizeof(b)); memset(id,0,sizeof(id)); ans.clear();//清空答案数组 int cnt=0; for(int i=1;i<=n;i++){ id[i]=indegree[i]; if(id[i]==0){ cnt++; q.push(i); b[i]=true; } if(cnt>1) return false; } while(!q.empty()){ int p=q.front();//取出顶点 q.pop(); ans.push_back(p);//将顶点存入答案 for(int i=0;i 1) return false; } } return true; } //也可以直接暴力,这里拓扑判断有没有环 bool topcircle(){ int id[1000]; bool b[1000]; queue q; int cnt=0; memset(b,0,sizeof(b)); memset(id,0,sizeof(id)); for(int i=1;i<=n;i++){ id[i]=indegree[i]; if(id[i]==0){ q.push(i); b[i]=true; } } while(!q.empty()){ int p=q.front();//取出顶点 cnt++; q.pop(); for(int i=0;i >n>>m; for(int x=1;x<=m;x++){ for(int i=0;i<3;i++){ cin>>ch[i]; } //ch[0]->ch[2] indegree[ch[2]-a]++; G[ch[0]-a].push_back(ch[2]-a);//构成图 //拓扑判断有没有环 if(!topcircle()) { cout<<"Inconsistency found after "<