拓扑序列:
定义:将图中顶点以线性方式进行排序,对于任何使得从顶点u到顶点v的有向边,顶点u总是排在顶点v的前边,这样得到的序列叫拓扑序列。
显然能瞒住拓扑序列的图都是有向无环图简称DAG。
所有DAG存在一个或多个拓扑序列。
图例:
这是一个有向无环图(DAG),我们可以根据拓扑序列定义得到几组拓扑序列
1、3、2、5、4、6、7
3、1、5、2、4、7、6
为什么会出现多组拓扑序列呢?
我们观察上图可以发现1、3这两个结点没有任何关系,不存在指向关系,所以他们两个是并列关系,我们可以将1放在前,也可以将3放在前面,同理(6、7),(2、5),(2、4)(4、5)这两对结点也是满足任意先后顺序!
拓扑排序
定义:由AOV网构造拓扑序列的过程叫做拓扑排序。
AOV网是一个有向无环图。
拓扑排序方法
一、Kahn(卡恩算法)
二、dfs算法
卡恩算法
步骤:
1、把入度为0的点入队列
2、把队首元素出队并输出,从图中删除此点以及它的边
3、重复上述两步直到不存在入度为0的点
图例:(以上图DAG图为例)
最后得到的拓扑序列
1、3、2、5、4、7、6
代码实现:
void topsort()
{
queue<int> q;
//先找出入度为0的点,入队
for(int i=1; i<=n; i++)
if(ru[i]==0)
{
q.push(i);
ts[++tot]=i;//拓扑序列记录在ts数组
}
while(!q.empty())
{
int u=q.front();
q.pop();
//将度清0
for(int i=head[u]; i; i=edge[i].next)
{
int v=edge[i].to;
ru[v]--;
//入度为0则push进队列
if(ru[v]==0)
{
q.push(v);
ts[++tot]=v;//记录在ts数组中
}
}
}
}
卡恩算法复杂度分析
由于我们要入队n个元素,以及删除所有的边,所以复杂度是O(V+E)
一道例题(需要一点dp知识)
旅行计划洛谷
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
//ts存放拓扑序列,head存放u相关的边的最后条边的编号,dp存放最优解
int ts[maxn],head[maxn],dp[maxn],ru[maxn];//ru存放结点入度
int n,m,ecnt,tot;//ecnt存放边数
struct edge
{
int to,next;//边的指向结点to,next下一条边的编号
} edge[maxn<<1];
void add(int u,int v)
{
edge[++ecnt].to=v;
edge[ecnt].next=head[u];//指向上一条边
head[u]=ecnt;//下一条边该指向当前边
}
void topsort()
{
queue<int> q;
for(int i=1; i<=n; i++) if(ru[i]==0)
{
q.push(i);
ts[++tot]=i;
}
while(!q.empty())
{
int u=q.front();
q.pop();
//将度清0
for(int i=head[u]; i; i=edge[i].next)
{
int v=edge[i].to;
ru[v]--;
if(ru[v]==0)
{
q.push(v);
ts[++tot]=v;
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
//作图
for(int i=1; i<=m; i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
ru[v]++;//v的入度+1
}
topsort();
//动态规划
fill(dp+1,dp+n,1);
//最外层遍历拓扑排序的后的前n个结点
for(int i=1; i<=n; i++)
{
int u=ts[i];
//遍历所有相关联的边
for(int j=head[u]; j; j=edge[j].next)
{
int v=edge[j].to;
dp[v]=max(dp[v],dp[u]+1);//动态转移方程
}
}
for(int i=1; i<=n; i++)
{
printf("%d\n",dp[i]);
}
return 0;
}
DFS解决拓扑排序
利用dfs从一个结点一直往下搜索,一直标记直到一个结点的出度为0,也就是这个结点没有边可以遍历,就停止遍历,记录在拓扑序列数组上然后返回继续dfs。最后我们倒叙输出得到的拓扑序列即可!
dfs状态树图解
这颗dfs状态树是,最开始的DAG图,从3号结点出发的状态树
同理然后返回4号结点,在遍历7号结点标记7,7记录在拓扑序列中,最后返回4,记录4进入拓扑序列**(4的状态树遍历完成所以4进入)**,返回2,进入2号结点指向的7,由于7号已经标记不能再遍历以此类推…我们通过3号状态树可以得到拓扑序列6、7、4、2、5、3,在dfs(1)得到最终的拓扑序列6、7、4、2、5、3、1,我们在逆序得到真正的拓扑序列1、3、5、2、4、7、6。
代码实现:(这是用dfs得到拓扑序列解决上一道例题)
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int head[maxn],dp[maxn],ts[maxn],vis[maxn];
int sum,tot;
int n,m,u,v;
struct edge{
int to,next;
}edge[maxn<<1];
void add(int u,int v)
{
edge[++sum].to=v;
edge[sum].next=head[u];
head[u]=sum;
}
//dfs
void dfs(int u)
{
//遍历所有的边
for(int i=head[u];i;i=edge[i].next)
{
v=edge[i].to;
if(!vis[v])
{
vis[v]=1;//标记
dfs(v);
}
}
ts[++tot]=u;//记录拓扑序列
}
int main(){
scanf("%d%d",&n,&m);
fill(dp+1,dp+1+n,1);//初始化dp数组将其中所有值变为1
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
add(u,v);
}
for(int i=1;i<=n;i++)
{
if(!vis[i])
{
vis[i]=1;
dfs(i);
}
}
//dp
for(int i=n;i>=1;i--)
{
int u=ts[i];
for(int j=head[u];j;j=edge[j].next)
{
int v=edge[j].to;
dp[v]=max(dp[v],dp[u]+1);
}
}
for(int i=1;i<=n;i++)
{
printf("%d\n",dp[i]);
}
return 0;
}
dfs复杂度分析
由于要遍历V个结点以及E条边所以为O(V+E)与卡恩算法相同。
感谢观看,如果对你有帮助,多多点赞支持!