图的深度优先遍历与广度优先遍历

本文详细讲解了深度优先遍历(DFS)和广度优先遍历(BFS)在游览游乐场景点的应用,通过示例展示了这两种算法在图中的遍历过程。代码实现包括邻接矩阵表示的图遍历和PHP代码演示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概念

深度优先遍历简称DFS(Depth First Search),广度优先遍历简称BFS(Breadth First Search),它们是遍历图当中所有顶点的两种方式。

场景

我们来到一个游乐场,游乐场里有11个景点。我们从景点0开始,要玩遍游乐场的所有景点,可以有什么样的游玩次序呢?

深度优先遍历

二叉树的前序、中序、后序遍历,本质上也可以认为是深度优先遍历。

第一种是一头扎到底的玩法。我们选择一条支路,尽可能不断地深入,如果遇到死路就往回退,回退过程中如果遇到没探索过的支路,就进入该支路继续深入。

在图中,我们首先选择景点1的这条路,继续深入到景点4、景点5、景点3、景点6,终于发现走不动了(景点旁边的数字代表探索次序):

于是,我们退回到景点1,然后探索景点7,景点8,又走到了死胡同。于是,退回到景点7,探索景点10:

  

按照这个思路,我们再退回到景点1,探索景点9,最后再退回到景点0,后续依次探索景点2,终于玩遍了整个游乐场:

广度优先遍历

二叉树的层序遍历,本质上也可以认为是深度优先遍历。

在图中,我们首先探索景点0的相邻景点1、2、3、4

接着,我们探索与景点0相隔一层的景点7、9、5、6:

最后,我们探索与景点0相隔两层的景点8、10:

 

代码实现

<?php
/**
 * 图的深度优先遍历、广度优先遍历
 * 图的存储结构--邻接矩阵
 */
class Graph {
    // 存储节点信息
    public $vertices;
    // 存储边信息
    public $arcs;
    // 图的节点数
    public $vexnum;
    // 记录节点是否已被遍历
    public $visited = [];

    // 初始化
    public function __construct($vertices) {
        $this->vertices = $vertices;
        $this->vexnum = count($this->vertices);
        for ($i = 0; $i < $this->vexnum; $i++) {
            for ($j = 0; $j < $this->vexnum; $j++) {
                $this->arcs[$i][$j] = 0;
            }
        }
    }

    // 两个顶点间添加边(无向图)
    public function addEdge($a, $b) {
        if ($a == $b) { // 边的头尾不能为同一节点
            return;
        }
        $this->arcs[$a][$b] = 1;
        $this->arcs[$b][$a] = 1;
    }

    // 从第i个节点开始深度优先遍历
    public function traverse($i) {
        // 标记第i个节点已遍历
        $this->visited[$i] = 1;
        // 打印当前遍历的节点
        echo $this->vertices[$i] . PHP_EOL;
        // 遍历邻接矩阵中第i个节点的直接联通关系
        for ($j = 0; $j < $this->vexnum ; $j++) {
            // 目标节点与当前节点直接联通,并且该节点还没有被访问,递归
            if ($this->arcs[$i][$j] == 1 && $this->visited[$j] == 0) {
                $this->traverse($j);
            }
        }
    }

    //深度优先遍历
    public function dfs() {
        // 初始化节点遍历标记
        $this->init();
        
        // 从没有被遍历的节点开始深度遍历
        for ($i = 0; $i < $this->vexnum; $i++) {
            if ($this->visited[$i] == 0) {
                // 若是连通图,只会执行一次
                $this->traverse($i);
            }
        }
    }
    
    // 初始化节点遍历标记
    public function init(){
        for ($i = 0; $i < $this->vexnum; $i++) {
            
            $this->visited[$i] = 0;
        }
    }
    
    //广度优先遍历
    public function bfs() {
        // 初始化节点遍历标记
        $this->init();
        
        $queue = [];
        for ($i = 0; $i < $this->vexnum; $i++) { // 对每一个顶点做循环
            if (!$this->visited[$i]) {      // 若是未访问过就处理
                $this->visited[$i] = 1;     // 设置当前顶点访问过
                echo $this->vertices[$i] . PHP_EOL;   // 打印顶点
                $queue[] = $i;              // 将此顶点入队列
                while (!empty($queue)) {    // 若当前队列不为空
                    $curr = array_shift($queue);    // 将队对元素出队
                    for ($j = 0; $j < $this->vexnum; $j++) {
                        if ($this->arcs[$curr][$j] == 1 && $this->visited[$j] == 0) {
                            $this->visited[$j] = 1; // 将找到的此顶点标记为已访问
                            echo $this->vertices[$j] . PHP_EOL;   // 打印顶点
                            $queue[] = $j;          // 将找到的此顶点入队列
                        }
                    }
                }
            }
        }
    }
}

/*
   0 1 2 3 4 5 6 7 8 9 10
0  0 1 1 1 1 0 0 0 0 0 0
1  1 0 0 0 1 0 0 1 0 1 0 
2  1 0 0 0 0 0 0 0 0 0 0
3  1 0 0 0 0 1 1 0 0 0 0
4  1 1 0 0 0 1 0 0 0 0 0
5  0 0 0 1 1 0 0 0 0 0 0
6  0 0 0 1 0 0 0 0 0 0 0
7  0 1 0 0 0 0 0 0 1 0 1
8  0 0 0 0 0 0 0 1 0 0 0
9  0 1 0 0 0 0 0 0 0 0 0
10 0 0 0 0 0 0 0 1 0 0 0

so
0 1,2,3,4
1 0,4,7,9
2 0
3 0,5,6
4 0,1,5
5 3,4
6 3
7 1,8,10
8 7
9 1
10 7
*/
// 测试
$vertices = ['景点0', '景点1', '景点2', '景点3', '景点4', '景点5', '景点6', '景点7', '景点8', '景点9', '景点10'];
$graph = new Graph($vertices);
$graph->addEdge(0, 1);
$graph->addEdge(0, 2);
$graph->addEdge(0, 3);
$graph->addEdge(0, 4);
$graph->addEdge(1, 4);
$graph->addEdge(1, 7);
$graph->addEdge(1, 9);
$graph->addEdge(3, 5);
$graph->addEdge(3, 6);
$graph->addEdge(4, 5);
$graph->addEdge(7, 8);
$graph->addEdge(7, 10);
// 递归
echo "dfs:";
$graph->dfs();

echo "<br />";
echo "bfs:";
$graph->bfs();

转自https://www.cnblogs.com/nr-zhang/p/11236369.html

<think>嗯,用户想学习无向深度优先遍历(DFS)广度优先遍历(BFS)的相关知识点。首先,我需要回顾一下的基本结构这两种遍历的基本概念。记得DFSBFS在二叉树中的应用,比如二叉树的先序、中序、后序遍历属于DFS,而层次遍历属于BFS。这可能遍历有相似之处,用户提到的引用[1]也提到了这一点,所以可能需要引用这个观点。 接下来,我需要明确无向的特点。无向的边没有方向,所以邻接节点之间是相互连接的。这会影响遍历时如何避免重复访问节点,必须记录已访问过的节点。对于DFS,通常使用栈或者递归来实现,访问一个节点后深入其邻接节点,直到无法继续再回溯。而BFS使用队列,逐层访问节点,先访问离起点近的节点。 然后,我需要详细描述DFSBFS的算法步骤。比如DFS的递归非递归实现,BFS的队列操作。可能需要举例子,比如一个具体的无向结构,展示遍历顺序。同时,要注意两者的应用场景,比如DFS适合寻找路径、连通分量,BFS适合最短路径问题。 时间复杂度方面,DFSBFS的时间复杂度都是O(V+E),其中V是顶点数,E是边数,因为每个节点边都会被访问一次。空间复杂度则取决于实现方式,比如递归深度或者队列的大小。 用户可能的问题包括两者的区别、如何选择使用哪种遍历、具体代码实现等。需要准备好这些问题的答案,并确保代码示例正确无误。例如,Python中使用邻接表表示,DFS用递归或栈,BFS用队列。 还要检查是否有遗漏的知识点,比如如何处理不连通的遍历所有连通分量。或者如何处理带有权重的,不过用户的问题是无向,可能不需要涉及权重。另外,是否需要对比树遍历的异同,帮助用户理解迁移已有的知识。 最后,生成相关问题时要覆盖算法步骤、应用场景、时间复杂度比较、代码实现如何处理特殊情况,比如环路或重复访问。这样用户可以有更全面的理解。</think>### 无向深度优先遍历(DFS)广度优先遍历(BFS) #### 一、结构基础 无向由顶点集合 $V$ 边集合 $E$ 组成,边没有方向,即 $(u, v)$ $(v, u)$ 等价。常用邻接表或邻接矩阵表示结构[^1]。例如,邻接表表示: ```python graph = { 0: [1, 2], 1: [0, 3], 2: [0, 4], 3: [1, 4], 4: [2, 3] } ``` #### 二、深度优先遍历(DFS) 1. **核心思想**:类似二叉树的先序遍历,优先沿一条路径深入探索,直到无法继续再回溯。 2. **算法步骤**: - 标记当前顶点为已访问 - 递归访问其未访问的邻接顶点 3. **时间复杂度**:$O(V + E)$ 4. **代码示例**(递归实现): ```python def dfs(graph, start, visited=None): if visited is None: visited = set() visited.add(start) print(start, end=' ') for neighbor in graph[start]: if neighbor not in visited: dfs(graph, neighbor, visited) ``` #### 三、广度优先遍历(BFS) 1. **核心思想**:类似二叉树的层次遍历,逐层向外扩展,使用队列辅助实现。 2. **算法步骤**: - 将起始顶点加入队列并标记 - 循环取出队首顶点,将其未访问邻接顶点入队 3. **时间复杂度**:$O(V + E)$ 4. **代码示例**: ```python from collections import deque def bfs(graph, start): visited = set() queue = deque([start]) visited.add(start) while queue: vertex = queue.popleft() print(vertex, end=' ') for neighbor in graph[vertex]: if neighbor not in visited: visited.add(neighbor) queue.append(neighbor) ``` #### 四、关键对比 | 特性 | DFS | BFS | |---------------|------------------|------------------| | 数据结构 | 栈(隐式或显式) | 队列 | | 适用场景 | 路径查找、连通分量 | 最短路径、社交网络 | | 空间复杂度 | $O(h)$(h为深度) | $O(w)$(w为宽度) | #### 五、典型应用 1. **DFS**:检测环路、拓扑排序、寻找连通分量 2. **BFS**:社交网络中的“六度分离”理论、迷宫最短路径
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值