zoukankan      html  css  js  c++  java
  • [总结]树与图的遍历


    一、图的深度优先遍历

    图的深度优先遍历,就是在遍历到一个点(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

  • 相关阅读:
    JAVA 设计模式 备忘录模式
    JAVA 设计模式 职责链模式
    JAVA 设计模式 中介者模式
    JAVA 设计模式 解释器模式
    JAVA 设计模式 观察者模式
    Linux下/usr/bin/python被删除的后果
    selenium 页面超时后捕获异常也无法继续get(url)使用的问题解决方案
    linux批量更改权限
    linux卸载软件
    安装pymysqlpool并使用(待补充)
  • 原文地址:https://www.cnblogs.com/cyanigence-oi/p/11736394.html
Copyright © 2011-2022 走看看