美文网首页
广度和深度优先搜索

广度和深度优先搜索

作者: 云飞扬1 | 来源:发表于2020-02-29 19:14 被阅读0次

    BFS、DFS主要是针对图这种数据结构来说的,记得以前大学第一次接触到这2种算法时,觉得理解起来很简单,但是代码确总写不好。回想起来,还是数据结构基础不扎实,当时总觉得图太复杂了,感觉无从下手,其实这些基本思想都可以在二叉树中找到原型。

    1. BFS算法

    我们先来说说二叉树,入门学习二叉树的时候,必然绕不开二叉树的遍历,通常有前序遍历、中序遍历、后序遍历,除此外还有一种就是层序遍历,层序遍历就是从根节点开始从上往下、从左往右一层层依次遍历节点,层序遍历其实就是广度优先算法。二叉树中一个节点最多只有左右两个子节点,我们把二叉树想象成一种特殊的图就可以了,图无非就是一个节点可能连接着多个节点。
    如果你对二叉树的层序遍历算法很熟悉的话,那么图的广度优先算法基本上就可以信手拈来了,不熟悉的可以参考这篇文章:二叉树的几种遍历

    /**
     * 无向图
     *
     * @param vertexCount 图的顶点数
     */
    class Graph(private val vertexCount: Int) {
    
        /**
         * 用邻接链表来存储图
         */
        private val adjacencyList: Array<LinkedList<Int>> = Array(vertexCount) {
            LinkedList<Int>()
        }
    
        private var found = false
    
        /**
         * 添加图的边,对于无向图需要添加2次
         *
         * @param start 起始顶点
         * @param end 终止顶点
         */
        fun addEdge(start: Int, end: Int) {
            adjacencyList[start].add(end)
            adjacencyList[end].add(start)
        }
    
        /**
         * 广度优先搜索
         *
         * @param s 起始顶点
         * @param e 终止顶点
         */
        fun bfs(s: Int, e: Int) {
            if (s == e) {
                return
            }
            //记录每个顶点是否有访问过,初始默认值都是未访问 false
            val visited = BooleanArray(vertexCount) { false }
            //记录每个顶点的前驱顶点,初始默认值都是 -1
            val prev = IntArray(vertexCount) { -1 }
            //广度搜索,类似树的按层访问,用队列来辅助
            val queue = LinkedList<Int>()
            queue.add(s)
            visited[s] = true
            while (queue.isNotEmpty()) {
                //取出队头顶点 i
                var i = queue.poll()
                //取出顶点 i 的下一层相邻顶点
                var list = adjacencyList[i]
                //遍历顶点 i 的所有下一层顶点
                for (j in list) {
                    if (!visited[j]) {
                        //记录前驱节点,顶点 m 的前驱顶点为 i
                        prev[j] = i
                        //如果找到终止顶点,则循环结束
                        if (e == j) {
                            printPath(prev, s, e)
                            return
                        }
                        //如果没有访问过
                        visited[j] = true
                        //没访问过的顶点入队
                        queue.offer(j)
                    }
                }
            }
    
            println("未找到任何从顶点 $s 到顶点 $e 的路径.")
        }
    
        private fun printPath(prev: IntArray, s: Int, e: Int) {
            //先打印前驱顶点
            if (prev[e] != -1 && s != e)
                printPath(prev, s, prev[e])
            print(e)
        }
    }
    

    2. DFS算法

    同样我们拿二叉树来说明,二叉树的前序、中序、后序遍历都有递归写法,DFS算法根本上与二叉树遍历的递归实现是一样的。
    一般二叉树的后序遍历的伪代码如下:

    postOrder(node) {
      //遍历左子节点
      postOrder(node.left)
      //遍历右子节点
      postOrder(node.right)
      //遍历当前节点
    }
    

    在二叉树中,只需要遍历节点的左、右2个子节点,而在图中,一个顶点会与多个顶点相连,我们只需要把与顶点相列的多个顶点依次遍历就可以了。

        /**
         * 深度优先搜索
         *
         * @param s 起始顶点
         * @param e 终止顶点
         */
        fun dfs(s: Int, e: Int) {
            if (s == e)
                return
            found = false
            val visited = BooleanArray(vertexCount) { false }
            val prev = IntArray(vertexCount) { -1 }
            recursive(s, e, visited, prev)
            if (found) {
                printPath(prev, s, e)
            } else {
                println("未找到任何从顶点 $s 到顶点 $e 的路径.")
            }
        }
    
        /**
         * 递归查找
         * 如果顶点 s 的邻接顶点有 n 个,我们用 next[n] 表示,要找到顶点 s 到顶点 e 的路径,
         * 遍历查找 (s - next[0], next[0] - e),(s - next[1], next[1] - e)...(s - next[n-1], next[n-1] - e)
         * 反复递归下去,直到查找完毕
         */
        private fun recursive(s: Int, e: Int, visited: BooleanArray, prev: IntArray) {
            if (found)
                return
            visited[s] = true
            val list = adjacencyList[s]
            for (i in list) {
                //如果该顶点没有访问过
                if (!visited[i]) {
                    prev[i] = s
                    if (i == e) {
                        //已经找到
                        found = true
                        return
                    }
                    recursive(i, e, visited, prev)
                }
            }
        }
    
    

    3. 小结

    二叉树是很多算法的基础,很多其他算法都是在二叉树的基础上演化而来的。对于广度优先与深度优先算法,我们可以这样来记:

    BFS:二叉树的层序遍历
    DFS:二叉树的后序递归遍历

    通常情况下,BFS 用来解决最短路径问题,DFS 是用来解决连通性问题的。

    这里总结了 2 个模板,基本上大部分 BFS、DFS的问题都可以套用这2个框架来解决。

    dfs(当前状态) {
        if (遇到边界条件 | 已找到结果) {
            return
        }
        //遍历当前状态所能到达的所有子状态
        for (int i = 0; i < n; i++) {
            if (子状态满足约束条件) {
                dfs(子状态)
            }
        }
    }
    
    bfs() {
        //定义一个队列 queue
        queue.offer(初始状态)
        while (queue不为空) {
            //取出队列头部的状态
            x = queue.poll()
            if(x为目标结果)
              return
            //遍历 x 的所有相邻状态
            for (int i = 0; i < n; i++) {
                if (子状态满足约束条件) {
                    //加入到队列尾部
                    queue.offer(子状态)
                }
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:广度和深度优先搜索

          本文链接:https://www.haomeiwen.com/subject/rimmhhtx.html