《算法竞赛进阶指南》0x21树与图的遍历

树与图的深度优先遍历,树的dfs序,深度和重心

树的深度优先遍历,就是在每个点x上面对多条分支时,任意选一条边走下去,执行递归,直到回溯至点x后,再考虑走其他的边。

void dfs(int x)
{
	v[x]=1;//记录点x被访问过
	for(int i=head[x];i;i=ne[i])
	{
		int y=ver[i];
		if(v[y])continue;//点有、被访问过 
		dfs(y);
	} 
}

树的dfs序
一般来讲,我们对树进行深度优先遍历时,对于每个节点,在刚进入递归后以及即将进入递归前各记录一次该点的编号,最后产生的长度为2n的节点序列就是树的dfs序。

void dfs(int x)
{
	a[++m]=x;//a数组存储DFS序
	v[x]=1;//记录点x被访问过 
	for(int i=head[x];i;i=ne[i])
	{
		int y=ver[i];
		if(v[y])continue;
		dfs(y);
	} 
	a[++m]=x; 
}

DFS序的特点:每个节点x的编号在序列中恰好出现两次。设这两次出现的位置为L[x]和R[x],那么区间[L[x],R[x]]就是以x为根的子树的DFS序。

树的深度
树中各个节点的深度是一种自上而下的统计信息。起初我们已知根节点的深度为0。若节点x的深度为d[x],则它的子节点y的深度就是d[y]=d[x]+1。

void dfs(int x)
{
	v[x]=1;
	for(int i=head[x];i;i=ne[i])
	{
		int y=ver[i];
		if(v[y])continue;
		d[y]=d[x]+1;//从父结点x到子节点y递推,计算深度 
		dfs(y);
	}
} 

树的重心
有很多信息是自下而上统计的,比如每个节点x为根的子树大小size[x]。若节点x的根节点为y1,y2,…yn,则size[x]=size[y1]+size[y2]+…+size[yn]+1。
对于一个节点x,如果我们把它从树中删除,那么原来一棵树可能会被分成若干个不相连的部分,其中每一部分都是一棵子树。设max_part(x)表示在删除节点x后产生的子树中最大的一棵的大小。使max_part函数取到最小值的节点p就称为整棵树的重心。

void dfs(int x)
{
	v[x]=1,size[x]=1;//子树x的大小 
	int max_part=0;//删除x后分成的最大子树的大小 
	for(int i=x;i;i=ne[i])
	{
		int y=ver[i];
		if(v[y])continue;//点y已经访问过了 
		dfs(y);
		size[x]+=size[y];//从子节点向父节点递推 
		max_part=max(max_part,size[y]);//n为整棵树节点数目 
	}
	max_part=max(max_part,n-size[x]);
	if(max_part<ans)
	{
		ans=max_part;//全局变量ans记录重心对应的max_part的值 
		pos=x;//全局变量pos记录了重心 
	}
} 

图的连通块划分
对一个森林进行深度优先遍历,可以划分出森林里的每棵树。

void dfs(int x)
{
	v[x]=cnt;
	for(int i=1;i;i=ne[i])
	{
		int y=ver[i];
		if(v[y])continue;
		dfs(y);
	}
}
for(int i=1;i<=n;i++)
{
	if(v[i])continue;
	cnt++;
	dfs(i);
}

树与图的广度优先遍历,拓扑排序

树与图的广度优先遍历需要使用一个队列来实现。起初队列中包含一个起点。在广度优先遍历的过程中,不断从队头取出一个节点x,对于x面对的多条分支,把沿着每条分支到达的下一个节点插入队尾。

void bfs()
{
	memset(d,0,sizeof d);
	queue<int>q;
	q.push(1),d[1]=1;
	while(q.size()>0)
	{
		int x=q.front();q.pop();
		for(int i=head[x];i;i=ne[i])
		{
			int y=ver[i];
			if(d[y])continue;
			d[y]=d[x]+1;
			q.push(y);
		}
	}
}

广度优先遍历的两个重要性质:
1.在访问过所有第i层节点后,才会开始访问第i+1层节点。
2.任意时刻,队列中至多有两个层次的节点。若第一部分属于第i层,则第2部分属于i+1层,并且所有第i层节点排在第i+1层节点前面。也就是说,广度优先遍历队列中的元素关于层次满足“两段性”和“单调性”。

拓扑排序

拓扑排序的思想很简单,我们只需要不断选择图中入度为0的节点x,然后把x连向的点的入读减1.我们可以结合广度优先遍历的框架来高效地实现这个过程。
1.建立空的拓扑序列A。
2.预处理出所有的入度deg[i],起始把所有入度为0的点入队。
3.取出队头节点x,把x加到拓扑序A的末尾。
4.对于从x出发的每条边(x,y),把deg[y]减1.若被减数为0则把y入队。
5.重复3-4步直到队列为空,此时A即为所求。

void topsort()
{
	queue<int>q;
	for(int i=1;i<=n;i++)
	if(deg[i]==0)q.push(i);
	while(q.size())
	{
		int x=q.front();q.pop();
		a[++cnt]=x;
		for(int i=head[x];i;i=ne[i])
		{
			int y=ver[i];
			if(--deg[y]==0)q.push(y);
		}
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
	}
	topsort();
	for(int i=1;i<=cnt;i++)
	printf("%d ",a[i]);
	cout<<endl;
	return 0;
}

例题
acwing164.可达性统计

先求出拓扑序,之后按照拓扑序的倒序依次求出每一个点所能到达的点的集合。

#include<iostream>
#include<bitset>
#include<queue>
using namespace std;
#define MAX_N 30000
int n,m;
int ver[MAX_N+5],ne[MAX_N+5],head[MAX_N+5];
int a[MAX_N+5];
int d[MAX_N+5];
int tot=0,cnt=0;
bitset<MAX_N+5>f[MAX_N+5];
void add(int x,int y)
{
    ver[++tot]=y,ne[tot]=head[x],head[x]=tot;
    return ;
}
void topsort()
{
    queue<int>q;
    for(int i=1;i<=n;i++)if(!d[i])q.push(i);
    while(q.size())
    {
        int x=q.front();
        q.pop();
        a[++cnt]=x;
        for(int i=head[x];i;i=ne[i])
        {
            int y=ver[i];
            if(--d[y]==0)q.push(y);
        }
    }
    return ;
}
int main()
{
    cin>>n>>m;
    for(int i=1,x,y;i<=m;i++)
    {
        cin>>x>>y;
        add(x,y);
        d[y]+=1;
    }
    topsort();
    for(int i=n;i>=1;i--)
    {
        int j=a[i];
        f[j][j]=1;
        for(int k=head[j];k;k=ne[k])
        f[j]|=f[ver[k]];
    }
    for(int i=1;i<=n;i++)printf("%d\n",f[i].count());
    return 0;
}
  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值