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


    深度优先遍历简称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();
  • 相关阅读:
    Android获取SIM卡信息--TelephonyManager
    android2.2应用开发之IccCard(sim卡或USIM卡)
    简易计算器
    c++ 按行读取txt文本
    poj 2010 Moo University
    字符串的最长公共子序列问题
    常用工具之zabbix
    常用工具之stunnel
    oracle 查看表属主和表空间sql
    linux shell执行方式
  • 原文地址:https://www.cnblogs.com/nr-zhang/p/11236369.html
Copyright © 2020-2023  润新知