(算法)深度优先搜索

深度优先搜索

概述

  • [一条路走到底,不撞南墙不回头]
  • [撞南墙]有两种情况:①遇到了边界条件,②遇到了已经走过的路
  • 深度优先的另一种结束条件,就是找到了目标出口
  • 深度优先遍历的本质就是穷举

常见的深度优先搜索

树的深度优先遍历

  1. [二叉树最大深度][https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof/]

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数

说明: 叶子节点是指没有子节点的节点

class Solution{
  public int maxDepth(TreeNode root){
    if(null==root) return 0;
    return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1;
  }
}
  1. [二叉树最小深度][https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/]

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

**说明:**叶子节点是指没有子节点的节点。

class Solution{
  public int minDepth(TreeNode root){
    if(null==root) return 0;
    int leftH = minDepth(root.left);
    int rightH = minDepth(root.right);
    if(0==rightH||0==leftH){
      return rightH+leftH+1;
    }else{
      return Math.min(leftH,rightH)+1;
    }
  }
}
  1. [路径总和][https://leetcode-cn.com/problems/path-sum/]

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if(null==root) return false;
        if(null==root.left && null==root.right){
            return targetSum==root.val;
        }
        return hasPathSum(root.left, targetSum-root.val) || hasPathSum(root.right, targetSum-root.val);
    }
}
  1. [翻转二叉树][https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/]

输入一个二叉树,该函数输出它的镜像。

class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if(root==null) return null;
        TreeNode left = root.left;
        TreeNode right = root.right;
        if(left==null && right==null){
            return root;
        }
        root.left = mirrorTree(right);
        root.right = mirrorTree(left);
        return root;
    }
}
  1. [相同的树][https://leetcode-cn.com/problems/same-tree/]

给你两棵二叉树的根节点 pq ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p==null && q==null) return true;
        if(p==null || q==null) return false;
        if(p.val != q.val) return false;
        return isSameTree(p.left,q.left) && isSameTree(p.right,q.right); 
    }
}
  1. [对称二叉树][https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/]

请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的.

class Solution {
    private boolean dfs(TreeNode left,TreeNode right){
        if(left==null && right==null) return true;
        if(left==null || right==null) return false;
        if(left.val!=right.val) return false;
        return dfs(left.right,right.left) && dfs(left.left,right.right);
     }

    public boolean isSymmetric(TreeNode root) {
        if(root==null) return true;
        return dfs(root.left,root.right);
    }
}
  1. [求根到叶子节点数字之和][https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/]

给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。

例如,从根到叶子节点路径 1->2->3 代表数字 123。

计算从根到叶子节点生成的所有数字之和。

class Solution {
    public int sumNumbers(TreeNode root) {
        return dfs(root,0);
    }
    private int dfs(TreeNode root,int fromNums){
        if(root==null) return 0;
        fromNums = fromNums*10 + root.val;
        if(root.left==null &&root.right==null) return fromNums;
        return   dfs(root.right,fromNums)+ dfs(root.left,fromNums);
    }
}
  1. [二叉树的公共祖先][https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/]

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null || root==p ||root ==q) return root;
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        if(left==null) return right;
        if(right==null) return left;
        return root;
    }
}
  1. [从前序跟中序遍历序列构造二叉树][https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/]

根据一棵树的前序遍历与中序遍历构造二叉树

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder.length>0&&inorder.length>0){
        TreeNode root = new TreeNode(preorder[0]);
        int num=0;
        //从中序遍历序列中查找根节点位置
        for(int i =0; i<inorder.length; i++){
            if(inorder[i]==root.val){
                num=i;
            }
        }
        int[] preLeft = Arrays.copyOfRange(preorder,1,num+1);
        int[] preRight = Arrays.copyOfRange(preorder,num+1,preorder.length);
                  
        int[] inoLeft = Arrays.copyOfRange(inorder,0,num);
        int[] inoRight = Arrays.copyOfRange(inorder,num+1,inorder.length);
        root.left=buildTree(preLeft,inoLeft);
        root.right=buildTree(preRight,inoRight);
        return root;
        }else{
           return null;
        }

    }
}
  1. [从中序跟后序遍历序列构造二叉树][https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/submissions/]
#由于python数组索引切片跟数组的index非常方便,感受一下python的魅力
class Solution(object):
    def buildTree(self, inorder, postorder):
        if postorder:
            root=TreeNode(postorder[-1])
            index = inorder.index(postorder[-1])
            root.left = self.buildTree(inorder[:index],postorder[:index])
            root.right = self.buildTree(inorder[index+1:],postorder[index:-1]) 
            return root
  1. [前序遍历构造二叉搜索树][https://leetcode-cn.com/problems/construct-binary-search-tree-from-preorder-traversal/]

返回与给定前序遍历 preorder 相匹配的二叉搜索树(binary search tree)的根结点。

(回想一下,二叉搜索树是二叉树的一种,其每个节点都满足以下规则,对于 node.left 的任何后代,值总 < node.val,而 node.right 的任何后代,值总 > node.val。此外,前序遍历首先显示节点 node 的值,然后遍历 node.left,接着遍历 node.right。)

题目保证,对于给定的测试用例,总能找到满足要求的二叉搜索树。

class Solution(object):
    def bstFromPreorder(self, preorder):
        if preorder:
            root = TreeNode(preorder[0])
            root.left = self.bstFromPreorder([x for x in preorder[1:] if x<preorder[0] ])
            root.right = self.bstFromPreorder([x  for x in preorder[1:] if x>preorder[0]])
            return root

图的深度优先遍历

  1. [无向图中连接分量的数目][https://leetcode-cn.com/problems/number-of-connected-components-in-an-undirected-graph/]
class Solution {
    public int countComponents(int n, int[][] edges) {
        // 所有节点都没被访问
        boolean[] visited = new boolean[n];

        //构建邻接表
        List<Integer> [] adj = new ArrayList[n];
        for (int i = 0; i < n; i++) {
            adj[i] = new ArrayList<>();
        }
        for(int[] edge: edges){
            adj[edge[0]].add(edge[1]);
            adj[edge[1]].add(edge[0]);
        }

        //深度优先遍历
        int count=0;
        for(int i=0;i<n;i++){
            if(!visited[i]){
                dfs(adj,i,visited);
                count++;
            }
        }
        return count;
    }

    private void dfs(List<Integer> [] adj,int u,boolean[] visited){
        visited[u] = true;
        List<Integer> path = adj[u];
        for(int p:path){
            if(!visited[p]){
                dfs(adj,p,visited);
            }
        }
    }
}
  1. [冗余连接][https://leetcode-cn.com/problems/redundant-connection/]

    在本问题中, 树指的是一个连通且无环的无向图。

    输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, …, N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。

    结果图是一个以边组成的二维数组。每一个边的元素是一对[u, v] ,满足 u < v,表示连接顶点u 和v的无向图的边。

    返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v] 应满足相同的格式 u < v。

    class Solution {
        private Map<Integer, List<Integer>> adj; //保存每个顶点能到达的所有顶点
        private Set<Integer> visited; //为了减少dfs次数,没有出现过的节点直接add这条边
    
        public int[] findRedundantConnection(int[][] edges) {
            this.adj = new HashMap<>();
            this.visited = new HashSet<>();
            for (int[] edge : edges) {
                int u = edge[0];
                int v = edge[1];
                if (adj.containsKey(u) && adj.containsKey(v)) {
                    visited.clear();
                    if (dfs(u, v)) {
                        return edge;
                    }
                }
                addEdg(v, u);
                addEdg(u, v);
            }
            return null;
        }
    
        private void addEdg(int u, int v) {
            if (adj.containsKey(u)) {
                adj.get(u).add(v);
                return;
            }
            List<Integer> successors = new ArrayList<>();
            successors.add(v);
            adj.put(u, successors);
        }
    
        private boolean dfs(int from, int to) {
            if (to == from) return true;
            visited.add(from);
            for (Integer point : adj.get(from)) {
                if (!visited.contains(point)) {
                    if (dfs(point, to)) return true;
                }
            }
            return false;
        }
    }
    
  2. [找到最终安全状态][https://leetcode-cn.com/problems/find-eventual-safe-states/]

    public class Solution {
        private Boolean[] visited;
        public List<Integer> eventualSafeNodes(int[][] graph) {
            int len = graph.length;
            visited = new Boolean[len];
            List<Integer> res = new ArrayList<>();
            for (int i = 0; i < len; ++i) {
                if (dfs(i, graph)) {
                    continue;
                }
                res.add(i);
            }
            return res;
        }
    
        /**
         * @param u
         * @param graph
         * @return 从顶点 u 出发的所有路径是不是有一条能够回到 u,有回路就返回 true
         */
        private boolean dfs(int u, int[][] graph) {
            if (visited[u] != null) {
                return visited[u];
            }
            // 先默认从 u 出发的所有路径有回路
            visited[u] = true;
            // 结点 u 的所有后继结点都不能回到自己,才能认为结点 u 是安全的
            for (int successor : graph[u]) {
                if (dfs(successor, graph)) {
                    return true;
                }
            }
            visited[u] = false;
            return false;
        }
    }
    

栈与深度优先搜索

栈这种后进先出的数据结构,对于迭代法写深度优先搜索有很重要的作用,同样,栈也被用于回溯算法,先看一看,怎么样使用栈来迭代深度优先算法吧:

  1. 前序遍历二叉树的迭代写法

    //前序遍历的迭代写法
        public List<Integer> preorderTraversal(TreeNode root) {
            List<Integer> res = new ArrayList<>();
            Deque<TreeNode> stack = new LinkedList<>();
            while (!stack.isEmpty() || null != root) {
                while (null != root) {
                    stack.push(root);
                  //由于前序遍历是先遍历根节点,所以在压入栈的时候就可以输出该节点的值
                    res.add(root.val);
                  // 根->左节点
                    root = root.left;
                }
              //因为是从根节点过来的 pop出栈之后即可获得上一层节点
                root = stack.pop();
                root = root.right;
            }
            return res;
        }
    
  2. 中序遍历二叉树的迭代写法

    //中序遍历的迭代写法
        public List<Integer> inorderTraversal(TreeNode root) {
            List<Integer> res = new ArrayList<>();
            Deque<TreeNode> stack = new LinkedList<>();
            while (!stack.isEmpty() || null != root) {
                while (null != root) {
                    stack.push(root);
                    root = root.left;
                }
                root = stack.pop();
              //最下面的左节点先输出
                res.add(root.val);
                root = root.right;
            }
            return res;
     }
    
    
  3. 后序遍历二叉树的迭代写法

    public List<Integer> postorderTraversal(TreeNode root) {
            List<Integer> res = new ArrayList<>();
            Deque<TreeNode> stack = new LinkedList<>();
            TreeNode prev = null;
            while (!stack.isEmpty() || null != root) {
                while (null != root) {
                    stack.push(root);
                    root = root.left;
                }
                root = stack.pop();
                if(root.right == null || root.right==prev){
                  //先输出左节点
                    res.add(root.val);
                    prev = root;
                    root = null;
                }else{
                    stack.push(root);
                   //再输出右节点
                    root = root.right;
                }
            }
            return res;
     }
    
  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值