代码1核心:拓扑排序
这里就体现了用数组模拟队列比stl队列的优势所在,能够储存被pop的元素
代码2的一个疑问的解答:有回路的图为什么答案记录数组的节点数会少?
要解答这个问题就需要思考什么样的点能入队?
这个问题的答案在第二段代码的总结部分给出了回答
再回头看原来的疑问,便可知道一旦存在回路,就会导致随着算法执行,回路外的点逐渐入队去边消失不见,而回路中的点由于入度始终不可能为0,就像一个无法进入的金刚环不可攻破导致回路上的点无法进入ans数组。
//#include <iostream>
#include<bits/stdc++.h>
//#include <algorithm>
//#include<cstring>
//#include<cmath>
//#include<cstdio>
using namespace std;
const int N=1e5+10;
int h[N];
int q[N];
int hh=0;
int tt=-1;
int e[N];
int ne[N];
int idx;
int d[N];
bool flag;
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int main() {
memset(h, -1, sizeof h);
int n,m;
cin>>n>>m;
while(m--){
int a,b;
cin>>a>>b;
add(a,b);
d[b]++;
}
for(int i=1;i<=n;i++){
if(d[i]==0)q[++tt]=i;
}
while(hh<=tt){
int t=q[hh++];
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
d[j]--;
if(d[j]==0)q[++tt]=j;
}
}
if(tt==n-1){
for(int i=0;i<n;i++)cout<<q[i]<<" ";
}
else cout<<-1;
return 0;
}
20230710第二次写,自己独立实现,并用stl中的queue实现,代价是需要多加一个从cnt计数器和ans数组记录答案
写的时候出现的一些问题,解决问题后得到的一些理解
1.只有没有回路的图才有拓扑序列,所以不需要用bool数组进行标记。在许多图论问题中进行bool数组标记是为了不重复回头走路,而本题不用考虑原因如下:
(1)没有回头路,bool数组不需要
(2)有回头路导致部分点反复出队入队,但只要有回路就没有拓扑序列,这种情况的判断将在下面提到。
2.什么样的点能够进入ans数组?
(1)初始入度就为0的点
(2)在不断减边遍历过程中入度变为0的点
开始我犯了错误,每次都让所有遍历到点的入队,即使这个点入度不为0
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10,M=1e5+10;
int a[N];
int n, m;
int d[N];
int h[N], e[M], ne[M], idx;
int cnt=1;
int ans[N];
void add(int a, int b) // 添加一条边a->b
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void topsort()
{
queue<int>q;
for(int i=1;i<=n;i++){
if(d[i]==0){
q.push(i);
}
}
while(q.size()){
auto u=q.front();
ans[cnt++]=u;
q.pop();
for(int i=h[u];i!=-1;i=ne[i]){
int j=e[i];
d[j]--;
if(d[j]==0)q.push(j);
}
}
}
int main() {
memset(h, -1, sizeof h);
cin>>n>>m;
for(int i=1;i<=m;i++){
int a,b;
cin>>a>>b;
add(a,b);
d[b]++;
}
topsort();
if(cnt!=n+1)cout<<-1;
//由于我的习惯数组下标从1开始使用,计数器cnt在使用时最后会落在n+1,因为最后一次的cnt++
else {
for(int i=1;i<=n;i++)cout<<ans[i]<<" ";
}
return 0;
}