【暖*墟】 #树与图# 深度优先遍历入门知识点

【一. 时间戳(dfn)】

什么是时间戳? 就是每个位置被访问到的次序。

比如说我们对一棵树进行深搜,在深搜中访问的相应次序就被我们称为时间戳。

【二. 树的dfs序】

1.dfs序的作用

维护一系列树上的问题,解决一棵树上的所有后代结点信息的更改和祖先结点有关改变,

通过dfs来记录树的每个顶点的出入时间戳,来控制它子树上的所有结点的状态的更新。

用 L[ i ],R[ i ] 来记录这个祖先结点控制后代结点的区间。

即dfs序的特点就是:每个节点编号x在序列中恰好出现两次。

一棵子树的dfs序就变成了一个区间 [ L[ x ] , R[ x ] ]

树的dfs序也用于深搜解决树的前序、中序、后序遍历问题。

  • 把树上的问题转化成了序列上的问题
  • 辅以各种数据结构(ST表、树状数组、线段树)进行运算
  • 子树求和->区间求和

2.dfs序的图解

3.dfs序的代码

void dfs(int x){
    a[++m]=x; //a数组储存dfs序
    v[x]=1; //记录点x被访问过
    for(int i=head[x];i;i=next[i]){
        int y=ver[i]; //取出点的价值
        if(v[y]) continue; //已经到达过
        dfs(y);
    }
    a[++m]=x; //dfs返回到x
}

4.例题:poj3321 Apple Tree 

【三. 树的深度、直径和重心】

1.树的深度

已知根节点深度为0。自顶而下统计信息。

若节点x的深度为 d[x] ,则它的子节点y的深度就是 d[y]=d[x]+1 。

结合dfs,可求得每个节点的深度。

void dfs_deep(int x){
    v[x]=1; //记录x点被访问过
    for(int i=head[x];i;i=next[i]){
        int y=ver[i];
        if(v[y]) continue;
        d[y]=d[x]+1; dfs_deep(y);
    }
}

2.树的直径与重心

1)树的直径  即树上最长的简单路径。

在树上任选一点w,求距离w最远的点u,求距离u最远的点v,u到v的距离即为树的直径。

简单证明:

<1> 如果w在直径上,那么u一定是直径的一个端点。

反证:若u不是端点,则从直径另一端点到w再到u的距离比直径更长,与假设矛盾。

<2> 如果w不在直径上,且w到其距最远点u的路径与直径一定有一交点c,

那么由上一个证明可知,u是直径的一个端点。

<3> 如果w到最远点u的路径与直径没有交点,设直径的两端为S与T,

那么(w->u)>(w->c)+(c->T),推出(w->u)+(S->c)+(w->c)>(S->c)+(c->T)=(S->T)与假设矛盾。

因此w到最远点u的路径与直径必有交点。

S-----------c-----------T

|

w------u

#include<cstdio>
#include<cstring>
#define N 4200

struct node{
    int next;
    int to;
}edge[N];

int num_edge,head[N],dis[N],n,a,b,y;

int 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; //寻找与w距离最大的某点u
    memset(dis,0,sizeof(dis));

    dfs(y); //以y为根,重新建树
    for(int i=y=1;i<=n;i++)
        if(dis[i]>dis[y])
            y=i; //找到离u最大的v
    printf("%d",dis[y]); //得出树的直径

    return 0;
}

2)树的重心  树上一点,使该点为根的所有子树中最大子树的节点数最少。

一般的树只有一个重心,有些有偶数个节点的树,有两个节点。

法一:简单的两次搜索求出。

分别搜索求出每个结点的子结点数量son[u]、使max{son[u],n-son[u]-1}最小的结点

实际上这两步操作可以在一次遍历中解决。对结点u的每一个儿子v,递归的处理v,

求出son[v],然后判断是否是结点数最多的子树,处理完所有子结点后,判断u是否为重心。

struct CenterTree{
    int n,ans,siz,son[maxn];
    void dfs(int u,int pa){
        son[u]=1;
        int res=0;
        for(int i=head[u];i!=-1;i=edges[i].next){
            int v=edges[i].to;
            if(v==pa) continue;
            if(vis[v]) continue;
            dfs(v,u); son[u]+=son[v];
            res=max(res,son[v]-1);
        }
        res=max(res,n-son[u]);
        if(res<siz){ ans=u; siz=res; }
    }
    int getCenter(int x){
        ans=0; siz=INF;
        dfs(x,-1);
        return ans;
    }
}Cent;  //树的重心

法二:利用推论求出。

随意确定一个根节点,先把无根树转化为有根树,dfs求出所有点的子树的节点个数。

如果有一点满足该点的 子树的节点数的二倍 >= 总结点数 (size[u]*2>=n),

并且该点儿子都满足 子树节点数二倍 <= 总结点数 (size[son_u]*2<=n),就是树的重心。

#include<cstdio>
#define N 42000

int n,a,b,next[N],to[N],head[N],num,size[N],father[N],ans;

void add(int false_from,int false_to){
    next[++num]=head[false_from];
    to[num]=false_to;
    head[false_from]=num;
}

void dfs(int x){
    size[x]=1;
    for(int i=head[x];i;i=next[i])
        if(father[x]!=to[i]){
            father[to[i]]=x;
            dfs(to[i]);
            size[x]+=size[to[i]];
        }
    if(size[x]*2>=n&&!ans)
        ans=x;
}

int main(){
    scanf("%d",&n);
    for(int i=1;i<n;++i){
        scanf("%d%d",&a,&b);
        add(a,b); add(b,a);
    }
    dfs(1);
    printf("%d",ans);
    return 0;
}

【四. 图的连通块划分】

1.连通块的定义

连通块(连通图):无向图 G 中,若从顶点 i 到 j 有路径相连,则 i、j 是连通的。

如果 G 是有向图,那么连接 i 和 j 的路径中所有的边都必须同向

如果图中任意两点都是连通的,那么图被称作连通图(或连通块)。

如果此图是有向图,则称为强连通图(注意:需要双向都有路径)。

2.求图中连通块的个数

深搜法:

void dfs_num(int x){
    v[x]=cnt; //记录x点属于的连通块的编号
    for(int i=head[x];i;i=next[i]){
        int y=ver[i];
        if(v[y]) continue; //已经找到归属的连通块
        dfs_num(y);
    }
}

//在主程序 int main() 中:
for(int i=1;i<=n;i++){
    if(!v[i]){
        cnt++; //无向图中包含连通块的个数
        dfs_num(i);
    }
}

并查集法:

#include <stdio.h>
#include <vector>
using namespace std;

const int maxn = 100010;
vector<int> G[maxn]; // 邻接表存储图

bool isRoot[maxn] = {false}; // 标记是否访问
int pre[maxn];

int Find(int x) {
    int r = x;
    while(x != pre[x])  x = pre[x];
    //↑↑路径压缩:此时x已经是老大
    int j; while(r != pre[r]) {
        j = r;
        r = pre[r];
        pre[j] = x;
    }
    return x;
}

void Union(int a, int b) {
    int preA = Find(a);
    int preB = Find(b);
    if(preA != preB) pre[preA] = preB;
}

int calculateBlockNum(int n) {
    int block = 0;
    for(int i = 1; i <= n; i++) // 枚举所有顶点
        isRoot[Find(i)] = true;// 这样同样的数字只计算一次
    for(int i = 1; i <= n; i++)
        block += isRoot[i]; //true当1用,false当0用
    return block;
}

void init(int n) {
    for(int i = 1; i <= n; i++)
        pre[i] = i;
}

int main() {
    int n, a, b; scanf("%d", &n);
    init(n); // 很重要很重要!
    for(int i = 1; i < n; i++) { // n - 1条边
        scanf("%d%d", &a, &b);
        G[a].push_back(b);
        G[b].push_back(a);
        Union(a,b); // 合并顶点a,b所在的集合
    }
    int block = calculateBlockNum(n);
    printf("%d\n",block);
}

在输入边的两个顶点时进行合并+路径压缩,统计pre数组中的元素有多少个不同值即可,

为了统计,用到标记数组,这里没有统计每个块中的元素数目,所以用布尔型的数组即可。

——时间划过风的轨迹,那个少年,还在等你。

原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/9366109.html

时间: 2024-11-06 03:54:31

【暖*墟】 #树与图# 深度优先遍历入门知识点的相关文章

图 - 深度优先遍历

图的遍历和树的遍历类似,我们希望从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫做图的遍历(Traverse Graph). 图的遍历方法一般有两种,第一种是深度优先遍历(Depth First Search),也有称为深度优先搜索,简称为DFS.第二种是<广度优先遍历(Breadth  First Search)>,也有称为广度优先搜索,简称为BFS.我们在<堆栈与深度优先搜索>中已经较为详细地讲述了深度优先搜索的策略,这里不再赘述.我们也可以把图当作

数据结构(15):图 深度优先遍历(DFS)

/*-----------------------------------------------*/ /* 邻接矩阵的DFS */ // 基于 数据结构(14) 中的邻接矩阵的结构 #include <iostream> using namespace std; typedef char VertexType; typedef int EdgeType; const int MAXVEX = 100; const int INFINITY = 65535; typedef struct {

数据结构实践——迷宫问题之图深度优先遍历解法

版权声明:本文为博主原创文章,未经博主允许不得转载.

41 蛤蟆的数据结构笔记之四十一图的遍历之深度优先

41  蛤蟆的数据结构笔记之四十一图的遍历之深度优先 本篇名言:"对于我来说 , 生命的意义在于设身处地替人着想 , 忧他人之忧 , 乐他人之乐. -- 爱因斯坦" 上篇我们实现了图的邻接多重表表示图,以及深度遍历和广度遍历的代码,这次我们先来看下图的深度遍历. 欢迎转载,转载请标明出处: 1.  原理 图遍历又称图的遍历,属于数据结构中的内容.指的是从图中的任一顶点出发,对图中的所有顶点访问一次且只访问一次.图的遍历操作和树的遍历操作功能相似.图的遍历是图的一种基本操作,图的许多其它

图的遍历之 深度优先搜索和广度优先搜索

本章会先对图的深度优先搜索和广度优先搜索进行介绍,然后再给出C/C++/Java的实现. 目录 1. 深度优先搜索的图文介绍 1.1 深度优先搜索介绍 1.2 深度优先搜索图解 2. 广度优先搜索的图文介绍 2.1 广度优先搜索介绍 2.2 广度优先搜索图解 3. 搜索算法的源码 深度优先搜索的图文介绍 1. 深度优先搜索介绍 图的深度优先搜索(Depth First Search),和树的先序遍历比较类似. 它的思想:假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点,然

图的深度优先遍历DFS

图的深度优先遍历是树的前序遍历的应用,其实就是一个递归的过程,我们人为的规定一种条件,或者说一种继续遍历下去的判断条件,只要满足我们定义的这种条件,我们就遍历下去,当然,走过的节点必须记录下来,当条件不满足后,我们就return,回到上一层,换个方向继续遍历. 模板: 1 //邻接矩阵存储方式 2 bool visited[MAX]; 3 void dfs(MGraph G,int i) 4 { 5 int j; 6 visited[i]=true; 7 cout<<G.vex[i]<&

图的遍历方法(深度优先和广度优先算法)

图的遍历方法有两种: 1 深度优先 该算法类似于树的先根遍历: 2   广度优先 该算法类似树的层次遍历: 事例: 深度优先遍历顺序为:V1–V2–V4–V8–V5–V3–V6–V7 广度优先遍历顺序为:V1–V2–V3–V4–V5–V6–V7–V8 3   注意事项 1)一个图,它的深度优先和广度优先是不唯一的,可以有多个! 2)一般情况都是给邻接表或者邻接矩阵求深度优先和广度优先,此时,深度优先和广度优先都是唯一的了,因为当你的存储结构固定的时候,深度优先和广度优先也随之被固定了!

基于邻接矩阵存储的图的深度优先遍历和广度优先遍历

图的存储结构相比较线性表与树来说就复杂很多,对于线性表来说,是一对一的关系,所以用数组或者链表均可简单存放.树结构是一对多的关系,所以我们要将数组和链表的特性结合在一起才能更好的存放. 那么我们的图,是多对多的情况,另外图上的任何一个顶点都可以被看作是第一个顶点,任一顶点的邻接点之间也不存在次序关系. 仔细观察以下几张图,然后深刻领悟一下: 因为任意两个顶点之间都可能存在联系,因此无法以数据元素在内存中的物理位置来表示元素之间的关系(内存物理位置是线性的,图的元素关系是平面的). 如果用多重链表

图的遍历之深度优先和广度优先

图的遍历之深度优先和广度优先 深度优先遍历 假设给定图G的初态是所有顶点均未曾访问过.在G中任选一顶点v为初始出发点(源点),则深度优先遍历可定义如下:首先访问出发点v,并将其标记为已访问过:然后依次从v出发搜索v的每个邻接点w.若w未曾访问过,则以w为新的出发点继续进行深度优先遍历,直至图中所有和源点v有路径相通的顶点(亦称为从源点可达的顶点)均已被访问为止.若此时图中仍有未访问的顶点,则另选一个尚未访问的顶点作为新的源点重复上述过程,直至图中所有顶点均已被访问为止. 图的深度优先遍历类似于树