DFS 序总结

本文详细介绍了深度优先搜索(DFS)序和欧拉序列的概念、特点以及它们在树结构中处理子树统计和求解最近公共祖先(LCA)问题的性质。DFS序中每个节点出现两次,而欧拉序则记录了节点进入和离开的顺序,两者在路径上的节点计数规律揭示了节点间的祖先关系。同时,文章还提到了DFN序和如何通过区间统计来辅助树的分析。

DFS 序

一、 DFS 序

1. 定义

对树进行深度优先搜索遍历时,对于每个节点,在刚进入递归后及即将回溯前各记录一次该点的编号,得到的最后产生的长度为 2N2N2N 的节点序列为 DFS 序;

void dfs(int i) {
    d[++len] = i;
    flag[i] = true;
    for (int t = 0; t < g[i].size(); t++) {
        int v = g[i][t];
        if (!flag[v]) {
            dfs(v);
        }
    }
    d[++len] = i;
    return;
}

2. 特点

  1. 每个节点 xxx 的编号在序列恰好存在两次;

  2. 设两次出现区间为 lil_ilirir_iri ,则闭区间 [li,ri][l_i, r_i][li,ri] 就是以 xxx 为根的子树的 DFS 序;

则可以通过 DFS 序将子树的统计转化为序列上的区间统计;

3. 性质

树上任意两点 x,yx, yx,y 路径上的点为,最后一个 xxx 到第一个 yyy 之间出现奇数次的数,再加上两点的 LCA;

说明

由于在 DFS 序中节点 xxx 第一次出现的位置到最后一次出现的位置之间都是 xxx 子树上的节点;

所以在 xxx 节点最后一次出现到 yyy 节点第一次出现间的节点即为 xxxyyy 的祖先;

但是,由于在这其中可能会遍历到其他不在两点路径上的子树,则这些字数上的节点均应是已经回溯的,则出现次数为 2 次,所以找这段区间中出现次数为奇数的节点,为两点上的路径;

由于在 DFS 序中,两点的遍历顺序为 x→LCA(x,y)→yx \rarr LCA(x, y) \rarr yxLCA(x,y)y ,但是又由于 DFS 遍历时,只有进入节点与完全遍历完子树时才会记录,所以还要加上两点的 LCA;

4. DFN 序

DFN 序则为,在 DFN 序中该节点被搜索的次序 (时间戳) ;

void dfs(int i) {
    dfn[i] = ++len;
    flag[i] = true;
    for (int t = 0; t < g[i].size(); t++) {
        int v = g[i][t];
        if (!flag[v]) {
            dfs(v);
        }
    }
    return;
}

二、欧拉序

1. 定义

进入节点时记录,每次遍历完一个子节点时,返回到此节点记录,得到的 2∗N−12 * N - 12N1 长的序列;

void dfs(int i) {
    d[++len] = i;
    flag[i] = true;
    for (int t = 0; t < g[i].size(); t++) {
        int v = g[i][t];
        if (!flag[v]) {
            dfs(v);
            d[++len] = i;
        }
    }
    return;
}

2. 性质

  1. 节点 xxx 第一次出现与最后一次出现的位置之间的节点均为 xxx 的子节点;

  2. 任意两个节点的 LCA 。是欧拉序中两节点第一次出现位置中深度最小的节点;

    说明

    因为在欧拉序中节点 xxx 第一次出现的位置到最后一次出现的位置之间都是 xxx 子树上的节点;

    所以在 xxx 节点最后一次出现到 yyy 节点第一次出现间的节点即为 xxxyyy 的祖先;

    但因为 xxx 可能为 yyy 的祖先,或 yyy 可能为 xxx 的祖先;

    所以应查找 xxxyyy 第一次出现的区间内的节点;

    则可将 xxxyyy 看作以其 LCA 为根的子树上的两个节点,则在欧拉序遍历时,顺序为 x→LCA(x,y)→yx \rarr LCA(x, y) \rarr yxLCA(x,y)y ,则区间中深度最小的节点即为两点的 LCA ;

    用 RMQ 计算 xxxyyy 第一次出现的区间内深度最小的节点即可;

<think>嗯,用户想了解如何用Python实现DFS和BFS算法。首先,我需要回忆一下这两种算法的基本概念。DFS是深度优先搜索,类似于走迷宫时先沿着一条路走到底,再回溯;BFS是广度优先搜索,像水波纹一样一层层扩展。接下来,我得考虑用户可能的背景,可能是刚开始学算法,所以需要代码示例和简单解释。 根据提供的引用内容,引用2和引用3中有具体的Python代码示例。需要确认这些代码是否正确。比如,DFS通常用栈实现,而BFS用队列。但要注意,引用2的DFS用的是栈,但代码里是后进先出,而引用3提到DFS用栈,BFS用队列。这符合常规做法。不过,有时候递归也可以实现DFS,但用户可能更希望看到迭代版本的代码,因为递归可能有栈溢出的问题。 用户的问题是关于实现,所以需要分步骤解释。首先定义图结构,比如用邻接表表示。然后分别写DFS和BFS的函数。代码中需要注意数据结构的选择,比如栈用列表的append和pop,而队列可以用collections.deque的popleft()来提高效率。 还要注意访问节点的顺,比如DFS访问节点是在出栈时打印,而BFS是在出队时处理。可能需要指出这点差异。另外,seen集合用于记录已访问的节点,避免重复访问。 用户可能对代码中的某些细节有疑问,比如为什么DFS用栈而BFS用队列,或者如何处理不同的图结构。不过当前问题主要是实现方法,所以保持代码简洁,注释清晰即可。 还要检查提供的引用中的代码是否有错误。比如,引用2中的DFS代码在弹出节点后打印,而邻接节点的处理是否正确。比如,是否应该先处理最后一个邻居,因为栈是后进先出。可能需要指出DFS的顺可能因为邻接节点顺不同而变化,但算法逻辑是正确的。 总结步骤:1. 定义图结构;2. 实现DFS(栈);3. 实现BFS(队列);4. 解释代码逻辑;5. 提供示例输出;6. 比较两者区别。同时,根据用户提供的引用,确保引用标记正确,比如引用2和3中的代码示例。 最后,生成相关问题时要围绕DFS和BFS的应用、区别、性能分析等,帮助用户进一步学习。</think>以下是用Python实现DFS(深度优先搜索)和BFS(广度优先搜索)的逐步说明: --- ### 一、DFS与BFS核心区别 - **DFS**:通过栈(后进先出)实现,优先访问深层节点,适合寻找所有解或路径问题 - **BFS**:通过队列(先进先出)实现,逐层遍历节点,适合最短路径问题 --- ### 二、Python代码实现 #### 1. 定义图结构(邻接表) ```python graph = { 'A': ['B', 'C'], 'B': ['A', 'D', 'E'], 'C': ['A', 'F'], 'D': ['B'], 'E': ['B', 'F'], 'F': ['C', 'E'] } ``` #### 2. DFS实现(栈结构) ```python def dfs(graph, start): stack = [start] # 初始化栈 visited = set() # 记录已访问节点 visited.add(start) while stack: vertex = stack.pop() # 关键点:后进先出 print(vertex, end=" ") # 处理当前节点 # 将未访问的相邻节点逆入栈(保证遍历顺与递归DFS一致) for neighbor in reversed(graph[vertex]): if neighbor not in visited: stack.append(neighbor) visited.add(neighbor) # 执行示例:dfs(graph, 'A') → A C F E B D ``` #### 3. BFS实现(队列结构) ```python from collections import deque def bfs(graph, start): queue = deque([start]) # 初始化队列 visited = set() visited.add(start) while queue: vertex = queue.popleft() # 关键点:先进先出 print(vertex, end=" ") for neighbor in graph[vertex]: if neighbor not in visited: queue.append(neighbor) visited.add(neighbor) # 执行示例:bfs(graph, 'A') → A B C D E F ``` --- ### 三、关键点对比 | 特性 | DFS | BFS | |------------|----------------------|----------------------| | 数据结构 | 栈 | 队列 | | 空间复杂度 | O(h)(h为树高度) | O(w)(w为最大宽度) | | 适用场景 | 拓扑排、连通性检测 | 最短路径、社交网络 | --- ### 四、可视化执行过程(以DFS为例) ``` 栈状态:[A] 访问A → 栈弹出A → 相邻节点B,C入栈(逆后C先入) 栈状态:[B, C] → 弹出C → 访问C → 相邻节点F入栈 栈状态:[B, F] → 弹出F → 访问F → 相邻节点E入栈... ``` --- ### 五、常见问题解答 **为什么DFS需要逆插入相邻节点?** 逆插入可以保证遍历顺与传统递归DFS一致(先左后右),若保持原则遍历方向相反,但算法仍正确[^2]。 **如何处理带权图?** 需使用优先队列(BFS→Dijkstra算法)或记录路径权重,代码实现需调整数据结构[^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值