[总结]树与图的遍历


一、图的深度优先遍历

图的深度优先遍历,就是在遍历到一个点\(x\)时任取一条边继续遍历,直到回溯到\(x\),再考虑走其他的边。
图的深度优先遍历会访问每个点,每条边各一次(无向图正反边各访问一次),故时间复杂度为\(O(N+M)\)
利用图的深度优先遍历可以统计图或树上的各种信息。
具体实现:

void dfs(int u){
	vis[u]=1;
	for(int i=first[u];e;e=next[e]){
		int v=go[e];
		if(vis[v]) continue;
		dfs(v);
	}
} 

1.时间戳

我们对图按照深度优先遍历的时候,以每次访问到的节点的顺序标记(也可以理解为vis[u]被标记成1的时候),这样的标记就是时间戳,通常用\(dfn[ ]\)表示。
具体实现:

void dfs(int u){
	vis[u]=1;
	dfn[u]=dfn[0]++; //默认节点编号为1~n
	for(int i=first[u];e;e=next[e]){
		int v=go[e];
		if(vis[v]) continue;
		dfs(v);
	}
} 

2.树的DFS序

当我们对树进行深度优先遍历时,我们分别在一个点递归开始和递归结束(即将回溯)时各记录一次这个节点的编号,最后产生长度为2N的序列就是树的DFS序。
特点:
设一个点\(x\)在DFS序中出现的位置分别为\(L,R\),那么[L,R]就是以\(x\)为根结点的子树的DFS序,这样就可以把对子树的操作转化为在序列上操作。
具体实现:

void dfs(int u){
	vis[u]=1;
	num[++cnt]=u;
	for(int i=first[u];e;e=next[e]){
		int v=go[e];
		if(vis[v]) continue;
		dfs(v);
	}
	num[++cnt]=u;
} 

3.树的深度

一棵树的深度就是根结点到叶子节点的最大距离,我们在遍历每个点时,不断递推子节点高度。
最初,根结点的深度为0,每次遍历都有\(dep[v]=dep[u]+1;\),这样可以求出每个点的深度。
具体实现:

void dfs(int u){
	vis[u]=1;
	for(int i=first[u];e;e=next[e]){
		int v=go[e];
		if(vis[v]) continue;
		dep[v]=dep[u]+1;
		dfs(v);
	}
} 

4.树的重心

正如字面意思,我们要寻找这样一个节点,使得树的左右两边尽量平衡。平衡意味着我们要找到这样一个节点:
它最大的子树的大小要小于任意其他节点的最大子树的大小。
我们任选一个节点作为我们树的重心,这时求出该节点最大子树的大小\(maxsize\),并记录着个节点为答案。如果之后遍历的节点能够使\(maxsize\)减小,那么这个点作为重心一定更优,因此我们更新\(maxsize\),以及最终答案。
图片2.png
具体实现:

void dfs(int u){
	vis[u]=1;size[u]=1;//只有一个点的大小为1
	for(int i=first[u];e;e=next[e]){
		int v=go[e];
		if(vis[v]) continue;
		dfs(v);
		size[u]+=size[v];//在回溯的时候(由下至上)更新根结点的子树大小
		maxsize=max(size[v],maxsize);//目前节点最大子树的大小,对应"子树1","子树2"
	}
	maxsize=max(maxsize,n-size[u]);//更新其余部分的大小
	if(maxsize<ansize){//如果产生更小的最大子树
		ansize=maxsize;//更新最小的最大子树大小
		pos=u;//记录这个点
	}
}

5.树的直径

树的直径就是一棵树中任意两点的最远距离。要求树的直径,可以先从根结点遍历,找到最远的一个节点,再由这个节点作为根结点进行遍历就能求出这棵树的直径。我们可以知道,该直径的两端点必然是树的两个叶子节点。
具体实现:

#include<bits/stdc++.h>
#define N 5000
struct node{
    int next,to;
}edge[N];
int num_edge,head[N],dis[N],n,a,b,y;
inline void add_edge(int from,int to){
    edge[++num_edge].next=head[from];
    edge[num_edge].to=to;
    head[from]=num_edge;
}
int dfs(int x){
    for(int i=head[x];i;i=edge[i].next)
        if(!dis[edge[i].to]){
            dis[edge[i].to]=dis[x]+1;
            dfs(edge[i].to);
        }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;++i){
        scanf("%d%d",&a,&b);
        add_edge(a,b);add_edge(b,a);
    }
    dfs(1);
    for(int i=y=1;i<=n;i++) if(dis[i]>dis[y]) y=i;
    memset(dis,0,sizeof(dis));
    dfs(y);
    for(int i=y=1;i<=n;i++) if(dis[i]>dis[y]) y=i;
    printf("%d",dis[y]);
    return 0;
}

二、图的广度优先遍历

图的深度优先遍历会沿着一条边走并回溯,而广度遍历会将该节点能到达的所有的节点全部插入队列(已访问过的除外),直到队列为空。
性质:

  1. 队列里只会保存第\(x\)层和第\(x+1\)层的节点。
  2. 只有第\(x\)层节点遍历完以后,才会遍历第\(x+1\)层的节点。
    具体实现:
void bfs(){
	queue<int> q;
	q.push(1);
	while(!q.empty()){
		int u=q.front();q.pop();
		vis[u]=1;
		for(int i=first[u];i;i=next[i]){
			int v=go[i];
			if(vis[v]) continue;
			vis[v]=1;
			q.push(v);
		}
	}
} 

三、练习

P2986 [USACO10MAR]伟大的奶牛聚集

首先计算所有牛到根结点所用的路程之和,之后依次推出到其他节点的路程。
由原来集合的节点u到节点v所走的路程变化为:
少走了\(cow[u]×dist[u]\) (u的子树节点的牛的个数×由u到v的路径长度),多走了\((all-cow[u])*dist[u]\)(其余节点牛的个数×由u到v的路径长度)。
图片27.png
Code:

#include<bits/stdc++.h>
#define N 200010
#define ll long long
const ll INF=0x3f3f3f3f3f3f3f3f;
using namespace std;
int n,all,c[N];
int tot,first[N<<1],nxt[N],go[N],cost[N];
ll cow[N],Ans=INF,dist[N],sum[N],ans[N];
inline void add_edge(int u,int v,int w){
	nxt[++tot]=first[u];
	first[u]=tot;
	go[tot]=v;
	cost[tot]=w;
}
inline void calc_sum(int u,int fath){
	cow[u]=c[u];//节点u的子树中牛的数量
	for(int e=first[u];e;e=nxt[e]){
		int v=go[e],w=cost[e];
		if(v==fath) continue;
		dist[v]=w;//记录u,v的边权
		calc_sum(v,u);
		cow[u]+=cow[v];
		sum[u]+=sum[v]+w*cow[v];//求出子树中所有奶牛到节该点的路程
	}
}
inline void calc_ans(int u,int fath){
	if(u==1) ans[u]=sum[u];//从根结点扩展
	else ans[u]=ans[fath]+(all-cow[u])*dist[u]-cow[u]*dist[u];//计算左右牛到节点u的路程
	for(int e=first[u];e;e=nxt[e]){
		int v=go[e];
		if(v==fath) continue;
		calc_ans(v,u); 
	}
} 
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&c[i]);
		all+=c[i];//求出所有奶牛的数量
	}
	for(int i=1,u,v,w;i<n;i++){
		scanf("%d%d%d",&u,&v,&w);
		add_edge(u,v,w);
		add_edge(v,u,w);
	}
	calc_sum(1,0);//计算所有牛到根结点的路程
	calc_ans(1,0);//求出所有牛到树上每一个点的路程
	for(int i=1;i<=n;i++) Ans=min(Ans,ans[i]);
	printf("%lld",Ans);
	return 0;
}

pic.png

posted @ 2019-10-25 09:45  Wolfloral  阅读(866)  评论(0编辑  收藏  举报