回溯法
概念
我们先来说说什么叫做回溯吧。回溯法是一个既带有系统性又带有跳跃性的搜索算法。它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。搜索到某个结点时,总是要先判断该结点是否肯定包含问题的解,如果不包含,则跳过以该结点为根的子树的系统搜索,逐层向祖先结点回溯;否则就进入该子树,继续按照深度优先的策略进行搜索。回溯法在求问题的所有解释要回溯到根,并且根节点的所有子树都要被搜索遍之后才会结束,而在寻求任一个解时,通常搜索到问题的一个解就可以结束。
这么说可能还有点难以理解,下面我们直接上大餐!
例题分析
力扣之中,有一道经典的N皇后,题目是这样的:它要求在一个n * n的棋盘上放置n个皇后,使它们彼此不受攻击。而按照国际象棋中的规则,皇后是可以攻击自身所处位置的同一行、同一列,以及同一斜线的任何位置。因此,这个问题就变成了在n * n的棋盘上放置n个皇后,并使得任何两个皇后都不在同一行、同一列、同一斜线上。
基本思想
在这里,我们选择4 * 4 的棋盘进行分析。首先我们按照顺序先在第一行第一列放置第一个皇后Q,以第一个皇后为基准(根结点),放置第二个皇后。在第二个皇后的放置过程中,由于放在第一列和第二列,与第一个皇后处在同一列和同一斜线上,所以不能放置,只能在第三列进行放置,之后在以第二个皇后为基准,放置第三个皇后,这个时候发现无论放在哪里不能够满足条件,也就是说已经肯定了第三行中的每个位置都肯定不满足条件(包含问题解),于是我们回溯到根结点(也就是第二个皇后),由于第二行第四列的位置的放置情况还未讨论,所以应当先放置在第二行第四列,之后继续按照规则遍历,最终发现在放置第四个皇后的也是肯定了不包含问题的解,所以回溯到上一根结点,改变其位置,最终回溯到第一个皇后的身上,故改变第一个皇后的位置,放在第一行第二列的位置上继续讨论情况。
思想图解
具体代码
而我们也可以根据其问题的定义设计代码
int Place(int * Column,int index){
int i;
for(i = 1; i < index; i++){
int Column_differ = abs(Column[index] - Column[i]);//列之差
int Row_differ = abs(index - i); //行之差
if(Column[i] == Column[index] || Column_differ == Row_differ){
//有皇后与其在同列或者同一直线上
return 0;
}
return 1; //没有有皇后与其在同列活同一直线上
}
}
void Queue(int n){
//定义数组,index表示行,他的值则表示放置的列
int Column_Num[n + 1];//列
int index = 1;//行
int i;
int answer_num = 0;
for(i = 1; i <= n; i++)
Column_Num[i] = 0
while(index > 0){
Column_Num[index]++; //列数加一
while(Column_Num[index] <= n && !Place(Column_Num,index)) //寻找皇后的位置,判断为零,不能放置,列数加一
Column_Num[index]++;
if(Column_Num[index] <= n){
if(index == n){ //最后一个皇后放置成功
answer_num++;
for(i = 1;i <= n; i++)
Column_Num[index]++;
}
else{ //继续寻找下一个皇后的位置
index++;
Column_Num[index] = 0;//注意这一步,放置为零,回溯到上一个结点时,需要消除棋盘,不会影响后续判断
}
}
else
index--; //当前位置无法放置,回溯到上一个皇后
}
}
代码分析
定义数组
我们定义了一个Column_Num数组,以及下标index,分别代表皇后放置的列和行,同时index也代表这第几个皇后,通过index自增来表示上一个皇后的位置已经放置成功,而通过Column_Num[index]的值来表示放置的列数,自增则表示该位置不能放置,需要跳到下一列。
判定函数Place
在每次改变行和列之前,都需要通过判定改皇后与其他皇后是否处在同一斜线同一列,而进行判定该位置是否可以放置。为什么不用判断同一行呢?因为很显然,index控制行数,index传进去,i和index根本不可能相同。
具体逻辑
初始化Column_Num[index],全部置为0.这里需要注意的是,下标从 1 开始。
首先将第一个皇后放置在第一行,index = 1,Column_Num[1] = 1,由于还没有其他的皇后,所以第二个while循环未进入,而后index自增,放置第二个皇后
此时index = 2,Column_Num[2] 置为 1,进入while循环判断,发现同列,自增Column_Num[2] = 2,直到符合条件,跳出while。
之后进行下一个皇后的放置,index = 3,Column_Num[3] 同样置为 1,这个时候进入while循环判定,发现当其等于1 2 3 4时都判定为不可放置,此时Column_Num[3] = 5 时跳出循环,可是根本没有第五列,这个时候控制index-- 回溯至上一个皇后,也就是index = 2的时候
这个时候,Column_Num[2] = 2,被迫变换位置,此时在进行相同的分析操作,发现此路不通,回溯至第一个皇后,在寻找下一个皇后时,所进行的 Column_Num[index] = 0操作,也是为了清除原先的棋盘,防止影响后续操作。
如此反复循环之后,最终index 的下标等于 4时,answer_num++,表示找到一个放置的方法。最终遍历棋盘上所有的位置,得到最终结果
总结
回溯法其实还是比较复杂的,需要掌握深度遍历,回溯循环条件,回溯终止条件等等,还是需要多刷题目进行探索,后续我们还会讲到其他的算法,敬请期待!
这种方法还是比较巧妙的,我也是从书中学习到的,现在分享给大家,希望对大家有所帮助,谢谢!