有向无环图
如果一个有向图的任一个点都无法通过一些有向边回到自身,则称这个图为 有向无环图 DAG。
拓扑排序
如果存在边(u,v)
,则拓扑排序中u
一定在v
的前面。
具体实现
- 定义一个队列,把所有入度为 0 的结点加入到队列。
- 取队首结点,输出。删去所有从他出发的边,并令这些边的另一端的结点入度 - 1。如果某个顶点的入度减为 0,则将其入队。
- 反复执行,直到队列为空。
- 如果输出的结点数恰好为 n,则拓扑排序成功,图 G 为有向无环图 DAG,反之,图 G 中有环。
【注】邻接表实现比较好。
cpp
// 邻接表实现
vector<int> g[maxn];
int n,m,inD[maxn];
bool topologicalSort()
{
int num=0;
queue<int> q;
vector<int> inD(n, 0);
vector<vector<int> > g(n + 1);
for(auto x:edges) // 邻接表存储边(不是邻接矩阵!)
{
g[x[1]].emplace_back(x[0]);
inD[x[0]]++;
}
for(int i = 0; i < n; i++)
if(inD[i] == 0) //入度为 0 的顶点入队列
q.push(i);
while(!q.empty())
{
int u = q.front();
q.pop();
num++; // 每取出一个入度为 0 的点,num++
for(int i = 0; i < g[u].size(); i++) // 遍历邻接点,修改邻接点的入度
{
int v = g[u][i];
inD[v]--; //入度--
if(inD[v] == 0) // 出现了新的入度为 0 的顶点,入队
q.push(v);
}
// g[u].clear(); //清空顶点u出发的所有边,可忽略
}
if(num == n) return true;
return false;
}
//邻接矩阵实现
bool topologicalSort(){
vector<vector<int> > g(n + 1, vector<int>(n + 1, 0));
vector<int> in(n + 1, 0);
vector<int> vis(n + 1, 0); // 额外建立一个数组记录顶点的访问情况
for(auto x:edges) // 邻接矩阵存储边
{
g[x[1]][x[0]] = 1;
in[x[0]]++;
}
int cnt = 0;
while(cnt < n) // 当获取 n 个入度为 0 的顶点时,结束循环
{
int u = -1;
for(int i = 0; i < n; i++)
{
if(in[i] == 0 && vis[i] == 0) // 入度为 0 且未被访问过
{
u = i;
vis[u] = 1;
break;
}
}
if(u == -1) return false;
for(int i = 0; i < numCourses; i++)
{
if(g[u][i] == 1) //存在边,入度--
in[i]--;
}
cnt++;
}
return true;
}
};
【注】 如果要求有多个入度为 0 的顶点时选择序号最小的,把queue
改成priority_queue
,或者set
也可以。
【注】 邻接表 + 队列 实现时,出现的入度为 0 的顶点一定是之前未被访问过的 (因为被遍历到的邻接点 v 至少存在边 (u, v) ,即,入度至少为1),所以不需要 vis
数组;而邻接矩阵实现时,每一次都会遍历所有顶点,所以需要记录那些入度为 0 的顶点已经被计算过了,所以需要 vis
数组,进而防止重复计算。