目录
基于邻接表的拓扑排序
基本思想
遍历indegree[]数组找到入度为0的点,由于这种点可能不止一个,所以需要用容器存起来(栈和队列都可以),然后每次从容器中删除一个入度为0的点,并且删除和该点邻接的边(和该点邻接的点的indegree[]数值-1),直到图的所有顶点都被删除,删除点的顺序就是拓扑排序,但是如果有点没有被删除,说明该图存在环。
具体代码
#include<bits/stdc++.h>
#include<stack>
using namespace std;
const int MAXSIZE=100010;
const int N=100010;
int indegree[N]; //记录每个点的入度
typedef struct ANode
{
int data;
struct ANode *nextarc;
}ANode; //边
typedef struct VNode
{
int data;
ANode *firstarc;
}VNode,AdjList[MAXSIZE]; //点表
typedef struct
{
AdjList vertices;
int vexnum;
int arcnum;
}ALGraph; //邻接表
ALGraph Q;
stack<int> s; //用栈存放顶点
int cnt;
int print[N];
bool TopoSort()
{
for(int i=1;i<=Q.vexnum;i++)
{
if(indegree[i]==0)
{
s.push(i);
}
}
while(!s.empty())
{
int q=s.top();
s.pop();
print[++cnt]=q; //思路很好---暂存拓扑排序
for(ANode* t=Q.vertices[q].firstarc;t;t=t->nextarc)
{
if(!(--indegree[t->data])) s.push(t->data); //如果入度为0,则加入到栈中
}
}
if(cnt<Q.vexnum) return false;
return true;
}
int main()
{
freopen("A.txt","r",stdin);
scanf("%d%d",&Q.vexnum,&Q.arcnum);
for(int i=1;i<=Q.vexnum;i++) //初始化
{
Q.vertices[i].data=i;
Q.vertices[i].firstarc=NULL; //将所有的边表初始化为NULL
}
for(int i=1;i<=Q.arcnum;i++) //邻接表的初始化
{
int a,b;
scanf("%d%d",&a,&b);
ANode *newedge=new ANode;
newedge->data=b;
newedge->nextarc=Q.vertices[a].firstarc;
Q.vertices[a].firstarc=newedge; //头插法将边插入
indegree[b]++; //更新入度
}
// for(int i=1;i<=Q.vexnum;i++)
// {
// cout<<indegree[i]<<" ";
// }
// cout<<endl;
if(!TopoSort())
{
cout<<"该图存在环";
}
else
{
for(int i=1;i<=Q.vexnum;i++)
{
cout<<print[i]<<" ";
}
}
return 0;
}
基于DFS的逆拓扑排序
基本思想
DFS遍历时,祖先节点的访问一定在孩子节点之后,所以在DFS的每次递归退出之前用一个变量来记录该顶点是第几个被访问的,这样就可以得到逆拓扑排序序列。
注意,逆拓扑排序序列和拓扑排序序列不是单纯的翻转关系。另外,代码中用st[]来辅助判断这个有向图中是否存在环,如果有环,则不存在拓扑排序和逆拓扑排序。
具体代码
#include<bits/stdc++.h>
using namespace std;
const int MAXSIZE=100010;
const int N=100010;
int indegree[N]; //记录每个点的入度
typedef struct ANode
{
int data;
struct ANode *nextarc;
}ANode; //边
typedef struct VNode
{
int data;
ANode *firstarc;
}VNode,AdjList[MAXSIZE]; //点表
typedef struct
{
AdjList vertices;
int vexnum;
int arcnum;
}ALGraph; //邻接表
ALGraph Q;
stack<int> s; //用栈存放顶点
int cnt;
int print[N];
int st[N];
int res;
/*
0:代表未访问
-1:代表访问完毕
1:代表是这一阶段正在访问的(这一阶段指的是两个元素在同一个递归中)。
*/
bool DFS(int x)
{
st[x]=1;
for(ANode* i=Q.vertices[x].firstarc;i;i=i->nextarc)
{
//如果遇到了正在访问的结点,那么说明有环
if(st[i->data]==1) return true;
//如果v这个结点没有访问过,递归查找v结点是否在环中
else if(st[i->data]==0&&DFS(i->data)) return true;
}
st[x]=-1;
print[++cnt]=x;
return false;
}
int main()
{
freopen("B.txt","r",stdin);
scanf("%d%d",&Q.vexnum,&Q.arcnum);
for(int i=1;i<=Q.vexnum;i++) //初始化
{
Q.vertices[i].data=i;
Q.vertices[i].firstarc=NULL; //将所有的边表初始化为NULL
}
for(int i=1;i<=Q.arcnum;i++) //邻接表的初始化
{
int a,b;
scanf("%d%d",&a,&b);
ANode *newedge=new ANode;
newedge->data=b;
newedge->nextarc=Q.vertices[a].firstarc;
Q.vertices[a].firstarc=newedge; //头插法将边插入
}
for(int i=1;i<=Q.vexnum;i++)
{
if(!st[i])
{
DFS(i);
}
}
for(int i=1;i<=Q.vexnum;i++)
{
cout<<print[i]<<" ";
}
return 0;
}
补充概念
有向图强连通(考虑边的方向方向)必定有环,无向图连通可以没有环(树)。
有向图中的强连通分量必定有环,另外,有向图中单个顶点也是强连通分量,如果一个有向图的顶点不能排成一个拓扑序列,则这个有向图一定含有顶点数大于1的强连通分量。如果是无向图,则一定含有环。
判断一个图是否有环的方法:拓扑排序,DFS。