0x21树与图的遍历

表示方法:
给定N个点的树或图,其节点编号为1-N,无向图中的边看作成对出现的双向边,树看作一张具有n-1条边的无向图,他们的边都存储在一个邻接表中,邻接表以head数组为表头,使用ver数组存储边的终点,edge数组存储边的权值,使用next数组模拟链表指针。

//加入有向边(x,y)权值为z
void add(int x,int y,int z)
{
	ver[++tot]=y;edge[tot]=z;
	next[tot]=head[x];head[x]=tot;//在表头处插入
}
//访问从x出发的所有边
	for(int i=head[x];i;i=next[i])
	{
		int y=ver[i],z=edge[i];//有向边(x,y)权值为z
	}

图的深度优先遍历:
dfs(1):访问每条边和点恰好一次(无向图正反向各访问一次)O(n+m)

void dfs(int x)
{
	vis[x]=1;
	for(int i=head[x];i;i=next[i])
	{
		int y=ver[i];
		if(v[y]) continue;
		dfs(y);
	}
}

时间戳dfn
按照以上优先遍历过程,以每个节点第一次被访问的顺序,依次给N个节点1~N的整数标记,该标记称为时间戳。

树的dfs序:
进行深度优先遍历时,对于每个节点,在刚进入递归后以及即将回溯前各记录一次该点的编号,最后产生长度为2N的节点序列就称为DFS序。
特点:每个节点编号在序列中恰好出现两次。设这两次出现位置为L[x],R[x],闭区间【L[x],R[x]】就是以x为根的子树的DFS序。
可以通过DFS序把子树统计转化为序列上的区间统计。

void dfs(int x)
{
	a[++m]=x;
	v[x]=1;
	for(int i=head[x];i;i=next[i])
	{
		int y=ver[i];
		if(v[y]) continue;
		dfs(y);
	}
	a[++m]=x;
}

树的深度:(自顶向下的统计信息)

void dfs(int x)
{
	v[x]=1;
	for(int i=head[x];i;i=next[i])
	{
		int y=ver[x];
		if(v[y]) continue;
		d[y]=d[x]+1;
		dfs(y);
	}
}

树的大小:(自底向上的统计信息)
以每个节点x为根的子树大小size[x],对于叶子节点,已知以其为根的子树大小为1,非叶子节点x,有k个子节点y1~yk,则以x为根的子树大小:size[x]=size[y1]+size[y2]+·····+size[yk]+1
树的中心
对于一个节点x,删除x可能会得到多个子树
设max_part(x)表示在删除节点x后产生的子树中,最大的一棵的大小,使max_part函数取到最小值的节点p称为整棵树的重心。

void dfs(int x)
{
	v[x]=1;size[x]=1;
	int max_part=0;//删除x后分成的最大子树的大小
	for(int i=head[x];i;i=next[i])
	{
		int y=ver[i];
		if(v[y]) continue;
		dfs(y);
		size[x]+=size[y];
		max_part=max(max_part,size[y]);
	}
	max_part=max(max_part,n-size[x]);//n是整个树的节点数
	if(max_part<ans)
	{
		ans=max_part;pos=x;//全局变量ans记录重心对应的max_part,pos记录重心
	}
}

图的连通块划分:

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

广度优先遍历(并求树的深度)

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=next[i])
		{
			int y=ver[i];
			if(d[y]) continue;
			d[y]=d[x]+1;
			q.push(y);
		}
	}
}

广度优先遍历的重要性质:

  1. 在访问完所有的第i层节点后,才会开始访问第i+1层节点
  2. 任意时刻,队列中至多有两个层次的节点,若其中一部分属于第i层,则另一部分属于第i+1层,且所有第i层节点排在第i+1层节点之前,即广度优先遍历队列中元素关于层次满足“两段性”和“单调性”

拓扑排序:
给定一张有向无环图(DAG),若一个由图中所有点构成的序列A满足:对于图中的每条边(x,y),x在A中都出现在y之前,则称A是该有向无环图顶点的一个拓扑序、求解拓扑序的过程即是拓扑排序。
DAG的拓扑序不唯一。
非DAG无拓扑序。

拓扑排序过程:

  1. 建立空的拓扑序列A
  2. 预处理出所有点的入度deg[i],起初把所有入度为0的点入队
  3. 取出队头节点x,把x加入拓扑序列A的末尾
  4. 对于从x出发的每条边(x,y),deg[y]–,若deg[y]=0,则把y入队
  5. 重复第3~4步直到队列为空,此时A即为所求。

拓扑排序可以判定有向图中是否存在环,对有向图执行拓扑排序操作,完成后检查序列A的长度,若A序列长度小于图中点的数量,说明存在环。

void add(int x,int y)
{
	ver[++tot]=y;next[tot]=head[x];head[x]=tot;
	deg[y]++;
}
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=next[i])
		{
			int y=ver[i];
			if(--deg[y]==0) q.push(y);
		}
	}
 } 

164. 可达性统计

#include<bits/stdc++.h>
using namespace std;
const int N=3e4+10;
int ver[N],ne[N],head[N],tot,n,m;
int deg[N],a[N],cnt;
bitset<N> f[N];
void add(int x,int y)
{
	ver[++tot]=y;ne[tot]=head[x];head[x]=tot;
	deg[y]++;
}
void topsort()
{
	queue<int>q;
	for(int i=1;i<=n;++i)
	{
		if(deg[i]==0) q.push(i);
	}cnt=0;
	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];deg[y]--;
			if(deg[y]==0) q.push(y);
		}
	}
 } 
int main()
{
	scanf("%d%d",&n,&m);
	memset(head,-1,sizeof(head));
	memset(deg,0,sizeof(deg)); 
	for(int i=0;i<m;++i)
	{
		int x,y;scanf("%d%d",&x,&y);
		add(x,y);
	}
	topsort();
	for(int i=cnt;i>=1;--i)
	{
		int x=a[i];
		f[x][x]=1;
		for(int j=head[x];j;j=ne[j])
		{
			int y=ver[j];
			f[x]|=f[y];
		}
	}
	for(int i=1;i<=n;++i)
	{
		printf("%d\n",f[i].count());
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值