递归初涉dfs深搜
前言
最近在家学习,路线任务做了个大概,打算多抽出来时间学习算法,应付一下蓝桥杯的比赛。我很菜的,不过看了许多关于dfs的博客自己也研究了一些,特别是今天撞了一下脑袋觉得突然开窍,为了不辜负这几天dfs带给我心灵上的痛苦,写下这篇博客,记录一下。
基本概念
深度优先搜索算法(Depth First Search,简称DFS):一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。
我觉得这就是一条路走到黑,走到头了,再退一步换个方向走到新头,再退回来,再走到新的头。如此反复走到“头”,得到所有的结果。
算法思想
回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。
模板
int check(参数)
{
if(满足条件)
return 1;
return 0;
}
void dfs(int step)
{
if语句判断是否达到边界
{
相应操作
}
for语句循环每种可能
{
满足check条件
标记
继续下一步dfs(step+1)
恢复初始状态(回溯)
}
}
搞一些例子
1.【力扣算法】17-电话号码的字母组合
题目
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
题解
如上图,有这几种输出。
示例中的输入 2, 3.。这是分为了两级,需要先进入第一级 2 对应的 a,然后进入第二级,分别与d,e,f 组合。再有 b, c。
代码
class Solution {
char[][] m = new char[][]{ //new 一个号码映射关系字符数组
{}, //号码0
{}, //号码1
{'a', 'b', 'c'}, //号码2
{'d', 'e', 'f'},
{'g', 'h', 'i'},
{'j', 'k', 'l'},
{'m', 'n', 'o'},
{'p', 'q', 'r', 's'},
{'t', 'u', 'v'},
{'w', 'x', 'y', 'z'} //号码9
};
public List<String> letterCombinations(String str) { // 在此处接收传入的字符串
List<String> res = new ArrayList<>(); //new 一个列表,存放答案
if(str.length() == 0)return res; //判断输入的字符串长度
dfs(str, 0, new StringBuilder(), res); //调用dfs
//StringBuilder 可以动态的改变字符串的长度,适合用来当做中间的容器
return res;
}
void dfs(String str, int index, StringBuilder sb, List<String> res){
//截止条件
if(index == str.length()){
res.add(sb.toString()); //满足条件就把它放进 res 字符列表中
return;
}
//候选节点
for(char c : m[str.charAt(index) - '0']){
sb.append(c); //先把字符存起来,此时没存够
dfs(str, index+1, sb, res); //进行下一级
sb.deleteCharAt(sb.length() - 1); //回溯
}
}
}
解题思路
在dfs(1)进行到 dfs(2) 时便暂停后续运行,直接将dfs(1) 所代表的m[2]入栈,运行dfs(2)所代表的m[3],循环三次之后,退栈,此时栈中还有m[2],继续循环
2.【蓝桥杯】八皇后
描述如下:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法!
题解
来分析一下,假设想要在黑色方块位置放置一个皇后
如果一列一列的放置皇后的话,图中黑色位置能放置一个皇后的合法性条件为:
1、绿色线条经过的方格没有皇后 (不处于同一斜线)
2、红色线条经过的方格没有皇后 (不处于同一行)
3、紫色线条经过的方格没有皇后 (不处于同一斜线)
所以假设某一行皇后的位置用列来记录,比如queen[row] = col,意思是第row列的皇后的位置在第col行。
同行的逻辑很好判断,那么我们想要在黑色方块位置放置一个皇后,怎么判断前面几行是否在绿色线条和紫色线条上已经有了皇后呢?思路也很简单:
假设测试放置皇后的方块的位置为n行,nCol行,假设位于它之前列的所在的行是否有皇后位于紫色线或者绿色上,那么就符合下面条件:
//假设此时测试在n行放置一个皇后,n>m
//测试放入n行的列数为 nCol
]//获取m行上皇后所在的列
int mCol = queen[m];
//行的差值
int rowDvaule = n - m;
//列的差值
int columnDvalue= n-mCol;
//如果行的差值的绝对值 == 列的差值的绝对值
//那么就是测试放置皇后的方块与之前存在的皇后在同一条斜率为 正负1 的直线上
所以,判断某个方块是不是可以放置皇后,让它满足以下条件即可
bool available(int row,int col){//传入测试的行和列
for(int i = 1; i < row; i++){
if(col == queen[i])return false;//同一列不行
if((row-i)==(col-queen[i]))return false;//在斜率为1的对角线上
if((row-i)+(col-queen[i])==0)return false;//同一副对角线拒绝
}
//满足以上判断条件,不在同一列,不在同一条直线上 。
//因为判断的是测试块之前所有行的皇后,所以不需判断测试行与之前存在的皇后是不是在同一行。测试行大于其他皇后所在行
return true;
}
完整代码
//八皇后递归解法
public class eight {
static int queen[]= new int[]{-1,-1,-1,-1,-1,-1,-1,-1};
static int count=0;
public static void main(String[] args){
dfs(0);
System.out.print(count);
}
static boolean check(int row,int col){//判断某个皇后是否与已有皇后冲突
for(int i=0;i<row;i++){
if(col == queen[i])return false;//同一列不行
if((row-i)==(col-queen[i]))return false;//在斜率为1的对角线上
if((row-i)+(col-queen[i])==0)return false;//同一副对角线拒绝
}
return true;
}
static void dfs(int queenNumber){//在第queenNumber行找能放皇后的位置
if(queenNumber==8){//如果八个皇后都放满了统计一下
count++;
return;
}
if(queen[queenNumber] == -1){
for(int i=0;i<8;i++){//从0~7遍历这一行的八个空位
if(check(queenNumber,i)){
//如果可以放这个位置就记录下第queenNumber个皇后的位置
queen[queenNumber]=i;
dfs(queenNumber+1);
queen[queenNumber] = -1;//回溯
}
}
}
return;
}
想法
后记
这是目前所了解的一点dfs的知识
我知道 a --> b --> c --> d 然后回去到c 进而有 a --> b --> c --> e
可是,我不明白再怎么回到b,这是困扰我的问题
然后想象栈的机制,明白了他是一层层的 return的,dfs(7)遍历完了之后,退栈,此时又恢复到dfs(6),而dfs(6)现在还有空位呢,就执行空出的,又把dfs(7)入栈,如此反复。就把所有的情况都遍历了一遍