拓扑排序
基于贪心和基于dfs实现的拓扑排序
拓扑排序的定义
对一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列(并非全序/线序),使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。通俗的说就是,一张有向无环图的拓扑排序可以使得任意的起点u,它的一个终点v,使得在序列中的顺序是u在前v在后
NOTE:并不是所有图都存在拓扑排序,拓扑排序在图中也不是唯一的.
不含环路的有向图必包含入度为零的顶点—>因此一定存在拓扑排序
废话少说,直接实战:P1137:旅行计划
思路:由于x城市要么在y东边,要么在其西边,所以这个图一定是一个有向无环图(Directed Acyclic Graph简称DAG)。
记得DP需要满足什么原则吗?无后效性。如果不是在拓扑序中进行DP,会完全破坏无后效性。正是因为拓扑序u在前,v在后的性质,这才选择使用拓扑排序。
这里我们选择用链式向前星来存储图:链式向前星
Kahn算法(贪心)实现(拓扑排序+DP):
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
/*
贪心实现拓扑排序的样例:P1137旅行计划
为了保证dp的无后效性,先拓扑排序再dp
先转化:只往东走,以第i个城市为终点最多能游览多少个城市 <==> 只往西走,以第i个城市为起点最多能浏览多少个城市
转移方程(已拓扑排序后): dp[u] = max(dp[u], dp[v]+1),如果存在一条从u到v的边
*/
queue<int>q1;
typedef struct qianlao
{
int u[200001], v[200001], w[200001]; //从u到v
int first[100001], next[200001];
int indu[100001]; //统计入度来判断起始点
int n, m;
}list; //创建符合题目的邻接表
list G;
int Topans[100001], t = 1;
void Topsort(void);
int main(void)
{
scanf("%d %d",&G.n, &G.m); //n个点,m个关系
for(int i = 1; i <= G.n; i++)
{
G.first[i] = -1;
G.indu[i] = 0; //邻接表初始化
}
for(int i = 1; i <= G.m; i++)
{
G.next[i] = -1; //邻接表初始化
}
for(int i = 1; i <= G.m; i++)
{
scanf("%d %d",&G.v[i], &G.u[i]); //注意这里逆向的读入DAG
G.w[i] = 1; //这里权值无用
G.next[i] = G.first[G.u[i]];
G.first[G.u[i]] = i;
G.indu[G.v[i]]++; //输入邻接表以及入度
}
Topsort(); //拓扑排序
int dp[G.n+1]; //dp[i]表示以第i个城市为起点最多能浏览多少个城市
for(int i = 1; i <= G.n; i++)
{
dp[i] = 1;
}
int k;
for(int i = G.n; i >= 1; i--) //进行简单dp
{
k = G.first[Topans[i]];
while(k!=-1)
{
dp[G.u[k]] = max(dp[G.u[k]], dp[G.v[k]]+1); //dp转移
k = G.next[k];
}
}
for(int i = 1; i <= G.n; i++)
{
printf("%d\n",dp[i]);
}
return 0;
}
void Topsort(void)
{
int k;
for(int i = 1; i <= G.n; i++) //注意这里是拓扑排序的逆序顺序
if(G.indu[i]==0) //初始点(入度为0的点)入队
q1.push(i);
while(!q1.empty())
{
Topans[t] = q1.front(); //贪心的取出队列中的点到Topans中
t++;
q1.pop(); //出列
k = G.first[Topans[t-1]];
while(k!=-1) //遍历所有由刚刚出列点指向的点
{
G.indu[G.v[k]]--;
if(G.indu[G.v[k]]==0) //如果删边后入度为0,则入队
q1.push(G.v[k]);
k = G.next[k];
}
}
}