深度优先搜索DFS
- 一条道路走到底,直到走不下去为止。然后再返回上一节点,继续重复刚才的过程
- 代码模板
DFS: 使用栈来保存已经走过的节点, 节点按照深度优先的次序被访问并依次压入栈中,并已相反的次序出栈进行新的检测。 // 也可以使用数组来标记每一个节点的状态。 DFS(dep,……)//dep代表目前DFS的深度 { if(找到解||走不下去了) { ……; return; } else { // 标记当前节点已经走过 // 这种标记是一种方式,也可以像下面的例子上每模拟下一次情况时,将下一次的情况对应的节点标记为已经走过,递归完成后再标记为未走过。 // 模拟下一种情况; DFS(dep+1,……); // 取消当前节点已经走过的标记 } }
- 关于节点的标记问题,下面的两个版本的代码实际上的效果是一样的。
List<String> ans = new ArrayList<String>(); int[][] status; //1 没有被访问过,0被访问过了 char[][] board; String word=""; public boolean exist(char[][] board, String word) { status = new int[board.length][board[0].length]; this.word=word; this.board=board; for (int i = 0; i < board.length; i++) { for (int j = 0; j < board[0].length; j++) { status[i][j]=1; } } for (int i = 0; i < board.length; i++) { for (int j = 0; j < board[0].length; j++) { status[i][j]=0; dfs(i, j,""); status[i][j]=1; } } System.out.println(ans); for(int i=0;i<ans.size();i++){ if(Objects.equals(word, ans.get(i))) return true; } return false; } public void dfs(int x, int y,String s) { if(s.length()>=word.length()) return; if(word.charAt(s.length())!=board[x][y]) return; s+=board[x][y]; ans.add(s); if(x-1>=0 && status[x-1][y]==1){ status[x-1][y]=0; dfs(x-1,y,s); status[x-1][y]=1; } if(x+1<board.length && status[x+1][y]==1){ status[x+1][y]=0; dfs(x+1,y,s); status[x+1][y]=1; } if(y-1>=0 && status[x][y-1]==1){ status[x][y-1]=0; dfs(x,y-1,s); status[x][y-1]=1; } if(y+1<board[0].length && status[x][y+1]==1){ status[x][y+1]=0; dfs(x,y+1,s); status[x][y+1]=1; } }
List<String> ans = new ArrayList<String>(); int[][] status; //1 没有被访问过,0被访问过了 char[][] board; String word=""; public boolean exist(char[][] board, String word) { status = new int[board.length][board[0].length]; this.word=word; this.board=board; for (int i = 0; i < board.length; i++) { for (int j = 0; j < board[0].length; j++) { status[i][j]=1; } } for (int i = 0; i < board.length; i++) { for (int j = 0; j < board[0].length; j++) { dfs(i, j,""); } } System.out.println(ans); for(int i=0;i<ans.size();i++){ if(Objects.equals(word, ans.get(i))) return true; } return false; } public void dfs(int x, int y,String s) { if(s.length()>=word.length()) return; if(word.charAt(s.length())!=board[x][y]) return; s+=board[x][y]; ans.add(s); status[x][y]=0; if(x-1>=0 && status[x-1][y]==1){ dfs(x-1,y,s); } if(x+1<board.length && status[x+1][y]==1){ dfs(x+1,y,s); } if(y-1>=0 && status[x][y-1]==1){ dfs(x,y-1,s); } if(y+1<board[0].length && status[x][y+1]==1){ dfs(x,y+1,s); } status[x][y]=1; }
实例
leetcode797题
- 题目如下:
给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序)
二维数组的第 i 个数组中的单元都表示有向图中 i 号节点所能到达的下一些节点,空就是没有下一个结点了。
译者注:有向图是有方向的,即规定了 a→b 你就不能从 b→a 。
输入:graph = [[1,2],[3],[3],[]]
输出:[[0,1,3],[0,2,3]]
解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3 - 需要两个变量,ans用来存储所有的路径,stack作为栈来记录路径上已经走过的点,使用队列来实现。
List<List<Integer>> ans = new ArrayList<List<Integer>>(); // 使用栈来记录路径上的点 Deque<Integer> stack = new ArrayDeque<Integer>();
- 写一个函数dfs用于深搜:
这里graph代表输入的图,x代表当前遍历到了几号节点,n代表最后一个节点的编号(因为所有的路径的终点都是最后一个节点)public void dfs(int[][] graph, int x, int n) { // 如果遍历到了最后一个节点,就将其添加到结果数组中 if (x == n) { ans.add(new ArrayList<>(stack)); return; } //节点按照深度优先的次序被访问并依次压入栈中,并以相反的次序出栈进行新的检测。 for (int y : graph[x]) { // 将节点压入栈中 stack.offerLast(y); // 求从节点y到最后一个节点的所有路径 dfs(graph,y,n); // 将y节点出栈 stack.pollLast(); } }
- 在主函数中进行调用,这里首先要将0号节点入栈,因为所有的路径都要从0号节点开始
public List<List<Integer>> allPathsSourceTarget(int[][] graph) { // 从0号节点开始走 stack.offerLast(0); // graph.length-1代表最后一个节点的编号 dfs(graph,0,graph.length-1); return ans; }
leetcode112题:路径总和
- 题目如下:
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。
叶子节点 是指没有子节点的节点。
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
/** * Definition for a binary tree node.
*public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
*/ - 首先定义两个变量,ans用来存储所有的路径,stack作为栈来记录路径上已经走过的点,使用队列来实现。
List<List<TreeNode>> ans = new ArrayList<List<TreeNode>>(); // 使用栈来记录路径上的点 Deque<TreeNode> stack = new ArrayDeque<TreeNode>();
- 写一个函数dfs用于深搜:
public void dfs(TreeNode node) { if(node == null){ return; } // 如果已经得到叶子结点 if(node.left==null && node.right==null){ ans.add(new ArrayList<>(stack)); return; } else{ // 遍历左子树 if(node.left!=null){ stack.offerLast(node.left); dfs(node.left); stack.pollLast(); } // 遍历右子树 if(node.right!=null){ stack.offerLast(node.right); dfs(node.right); stack.pollLast(); } } }
- 在主函数中进行调用,这里首先要将根节点入栈,因为所有的路径都要从根节点开始
public boolean hasPathSum(TreeNode root, int targetSum) { // 将根节点入栈 if(root!=null) stack.offerLast(root); // 进行深搜并将结果存入ans中 dfs(root); for(int i=0;i<ans.size();i++){ int sum=0; for(int j=0;j<ans.get(i).size();j++){ sum+=ans.get(i).get(j).val; } if(sum==targetSum) return true; } return false; }
- 也可以这样实现(节点标记的方式不同)(Offer34)
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { List<List<Integer>> ans = new LinkedList<List<Integer>>(); // 使用栈来记录路径上的点 Deque<Integer> stack = new LinkedList<Integer>(); public List<List<Integer>> pathSum(TreeNode root, int target) { if(root==null) return new ArrayList<>(); dfs(root); List<List<Integer>> res=new ArrayList<>(); for (int i = 0; i < ans.size(); i++) { int sum=0; for(int j=0;j<ans.get(i).size();j++){ sum+=ans.get(i).get(j); System.out.print(ans.get(i).get(j)+" "); } System.out.println(sum); if(sum==target){ res.add(ans.get(i)); } } return res; } public void dfs(TreeNode node){ if(node==null) return; stack.offerLast(node.val); if(node.left==null && node.right==null){ ans.add(new ArrayList<>(stack)); // 注意这儿不能加return,否则会影响队列元素的出队,从而影响整个结果。(与另一种节点标记方式不同) } dfs(node.left); dfs(node.right); stack.pollLast(); } }
从上面两道题目中我们可以看出其实代码的结构差不太多,因此深搜也不是很难,只要按部就班写就行啦~当然这儿写的只是皮毛,有待深入