《算法》系列-深度广度优先搜索

Posted 山主

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《算法》系列-深度广度优先搜索相关的知识,希望对你有一定的参考价值。


1.广度优先搜索(BFS)

定义:又叫做层次遍历,从上往下对每一层,从左往右(也可以从右往左)访问节点,访问完一层就进入下一层,直至没有节点为止。

用图来直观的看一下:

先来说一下解题思路:

广度优先遍历是一层一层的往下去遍历,我们可以用一个队列(先进先出)来存储每一层的节点,在遍历每一个节点时,取它下一层的节点,放到队列的末尾,遍历完一层时,下一层的节点也放到了队列中。我们在放入节点时,记录下一层的节点数量,就可以在队列中区分出每一层的节点了,这样就完成了广度优先遍历。

下面来看一下广度优先遍历的代码模板

《算法》系列-深度、广度优先搜索

然后看一个例题

《算法》系列-深度、广度优先搜索

然后用广度优先遍历的解题思路来解题,我在代码里去写了每一行注释,接下来就看代码:

/**
 * 广度优先遍历
 * @param root 根节点
 * @return 结果集
 */

public List<List<Integer>> levelOrder(TreeNode root) {
    //用来存储结果集
    List<List<Integer>> ret = new ArrayList<List<Integer>>();
    if (root == null) {
        return ret;
    }

    //队列,用来进出节点
    Queue<TreeNode> queue = new LinkedList<TreeNode>();
    //首先把根节点放入队列中
    queue.offer(root);
    while (!queue.isEmpty()) {
        //level用来存储每一层的节点数据
        List<Integer> level = new ArrayList<Integer>();
        //当前层节点的数量
        int currentLevelSize = queue.size();
        //遍历当前层的节点
        for (int i = 1; i <= currentLevelSize; ++i) {
            //从队列中poll一个节点
            TreeNode node = queue.poll();
            //将节点数据添加到层集合
            level.add(node.val);
            //如果当前节点有下一层的节点,将下一层的节点放到队列末尾
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
        }
        //遍历完成一层节点,添加到结果集
        ret.add(level);
    }
    //返回结果集
    return ret;
}

广度优先搜索解题我认为有两个要点:一是用一个队列去进出节点;二是取当前层的节点数量,通过这个数量去队列中取节点,就是一层中的节点。

2.深度优先搜索(DFS)

深度优先搜索就是对每一个可能的分支路径一探到底,且每个节点只能访问一次。

用图来直观的看一下:

接下来直接看一个例题:

这里使用了回溯递归的方式去解题,直接看代码:

//定义结果集
List<List<Integer>> ret = new LinkedList<List<Integer>>();
//用栈来存储路径
Stack<Integer> path = new Stack<>();
/**
 * 深度优先遍历
 * @param root 跟节点
 * @param sum 目标和
 */

public void dfs(TreeNode root, int sum) {
    if (root == null) {
        return;
    }
    //先将根节点压入栈
    path.push(root.val);
    //目标值减去节点的val
    sum -= root.val;
    //节点没有子节点,且目标和为0,说明满足这样的路径
    if (root.left == null && root.right == null && sum == 0) {
        //加入结果集
        ret.add(new LinkedList<Integer>(path));
    }
    //遍历左节点
    dfs(root.left, sum);
    //遍历又节点
    dfs(root.right, sum);
    //弹出当前节点(回溯)
    path.pop();
}

有没有很像二叉树的先序、中序、后续遍历的递归写法?其实二叉树的先序、中序、后续遍历也是一种深度优先遍历的算法,所以这里看起来有异曲同工的意思。

而且跟之前的回溯算法也相似噢,有兴趣的可以看看我前面的文章。

好了,知道了二叉树的先中后序遍历也是一种深度优先遍历,相信很多读者都知道二叉树的先序遍历的非递归写法,用到了栈作为中间存储的数据结构,这里也把二叉树的前、中、后序遍历的代码贴在这里一下。

3.二叉树的前、中、后序遍历

//前序遍历:根->左->右
public void firstTraversal(Node root){
     //定义一个栈
        Stack<Node> stack = new Stack<>(); 
     //判断节点是否为空或者栈中是否为空,当均为空时,结束循环
        while (root!=null||stack.size()>0){ 
            if(root!=null){
                printNode(root);
                stack.push(root);
                root = root.getLeftNode();
            }else {
                root = stack.pop();
                root = root.getRightNode();
            }
        }
    }
//中序遍历:左->根->右
    public void inOrderTraversal(Node root){
        Stack<Node> stack = new Stack<>(); //定义一个栈
        while (root!=null||stack.size()>0){
            if(root!=null){
                stack.push(root);   //直接压入栈
                root = root.getLeftNode();
            }else {
                root = stack.pop(); //出栈时输出下
                printNode(root);
                root = root.getRightNode();
            }
        }
    }
//后续遍历:左->右->根
public void postOrderTraversal(Node root){
        Stack<Node> stack = new Stack<>();
        Stack<Node> output = new Stack<>();//构造一个中间栈存储后序遍历的结果
        while (root!=null||stack.size()>0){
            if(root!=null){
                output.push(root);
                stack.push(root);
                root = root.getRightNode();
            }else {
                root = stack.pop();
                root = root.getLeftNode();
            }
        }
        while (output.size()>0){
            printNode(output.pop());
        }
    }

总结

1.广度优先遍历:一层一层往下遍历,用一个队列来作为中间存储,取当前层的节点数量,然后遍历当前层节点,同时记录下一层节点,完成广度优先遍历;

2.深度优先遍历:本文的代码用到了回溯法,先写递归终止条件和结果集的处理,然后遍历左子树节点、右子树节点,然后pop()当前节点。值得注意的是,二叉树的前、中、后遍历也是深度优先遍历的。

以上是关于《算法》系列-深度广度优先搜索的主要内容,如果未能解决你的问题,请参考以下文章

深度&&广度优先算法

手撸golang 基本数据结构与算法 图的搜索 深度优先/广度优先

基本算法——深度优先搜索(DFS)和广度优先搜索(BFS)

七十九深度和广度优先搜索算法

算法题——深度优先搜索与广度优先搜索

手撸golang 基本数据结构与算法 图的搜索 深度优先/广度优先