家谱树
题目描述
核心思路
这是一道很裸的拓扑排序,直接套拓扑排序模版即可。
问题:如何输出字典序最小的拓扑排序?
将队列换成优先队列,优先队列取出的元素是当前字典序最小的编号,在出队的时候记录出队的编号即为当前字典序最小的拓扑序,此方法的时间复杂度是 O ( l o g n ∗ ( n + m ) ) O(logn*(n+m)) O(logn∗(n+m))
由于是有向无环图,对于第1个节点来说,最多与后面的 n − 1 n-1 n−1个节点有边;对于第2个节点来说,最多与后面的 n − 2 n-2 n−2个节点有边; ⋯ \cdots ⋯;对于第 n n n个节点来说,有0个连边。因此最多有 n ( n − 1 ) 2 \dfrac {n(n-1)}{2} 2n(n−1)条边。由于点的个数最多是 N N N,所以边的个数最多是 N ( N − 1 ) 2 \dfrac {N(N-1)}{2} 2N(N−1)
由于这道题目中肯定不会存在环(家庭伦理问题…),因此一定可以得到拓扑序列。
代码
手写队列:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110,M=N*N/2;
int n;
int h[N],e[M],ne[M],idx;
int d[N];
int q[N];
void add(int a,int b)
{
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
void topsort()
{
int hh=0,tt=-1;
for(int i=1;i<=n;i++)
if(!d[i])
q[++tt]=i;
while(hh<=tt)
{
int t=q[hh++];
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(--d[j]==0)
q[++tt]=j;
}
}
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int son;
while(scanf("%d",&son),son)
{
//从i向son连一条有向边
add(i,son);
d[son]++;
}
}
//进行一遍拓扑排序
topsort();
//输出得到的拓扑序列
for(int i=0;i<n;i++)
printf("%d ",q[i]);
return 0;
}
运用STL中的队列
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int N=110,M=N*N/2;
int n;
int h[N],e[M],ne[M],idx;
int d[N];
//seq数组用来存储得到的拓扑序列,cnt是这个数组的下标
int seq[N],cnt;
void add(int a,int b)
{
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
void topsort()
{
queue<int>q; //运用STL中的队列
for(int i=1;i<=n;i++)
{
if(!d[i])
{
q.push(i);
}
}
while(q.size())
{
int t=q.front();
//记录此时的拓扑序列的节点是t
seq[cnt++]=t;
q.pop();
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(--d[j]==0)
{
q.push(j);
}
}
}
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int son;
while(scanf("%d",&son),son)
{
add(i,son);
d[son]++;
}
}
topsort();
//输出得到的拓扑序列
for(int i=0;i<n;i++)
printf("%d ",seq[i]);
return 0;
}