二道经典OJ题带你入门回溯剪枝算法

风起于青萍之末

浪成于微澜之间

二道经典OJ题带你入门回溯剪枝算法_第1张图片


个人主页

个人专栏

前期回顾-环形链表


目录

 回溯算法的简介

N皇后问题

思路

代码测试

N皇后 

思路

判断一竖列是否有皇后

判断对角线是否有皇后

代码测试

 回溯算法的简介

回溯是递归的副产品,只要有递归就会有回溯 所以回溯法也经常和DFS混在一起

回溯的介绍:在搜索解空间时会采用 尝试 与 回退 的策略

二道经典OJ题带你入门回溯剪枝算法_第2张图片

回溯算法实际上一个 类似枚举的搜索 尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就 回溯 返回,尝试别的路径。回溯法是一种深度优先搜索,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为 回溯点 

剪枝的介绍:

回溯法的基本行为是 搜索,搜索过程使用 剪枝 函数来为了避免无效的搜索

(1)使用约束函数,剪去不满足约束条件的路径

(2)使用限界函数,剪去不能得到最优解的路径

DFS的介绍:

DFS是一种遍历或搜索图、树等数据结构的算法,当然这个图、树未必要存储下来(隐式处理就是回溯法)常见的是通过某种关系构造出的搜索树,搜索树一般是排列型搜索树子集型搜索树

排列型就是每次枚举选哪个

二道经典OJ题带你入门回溯剪枝算法_第3张图片

子集型就是对于每一个元素选或不选

二道经典OJ题带你入门回溯剪枝算法_第4张图片

DFS 从起始节点开始,沿着一条路径尽可能探入地搜索直到无法继续为止,然后回溯到前一个节点,继续探索其他路径,直到遍历采整个图

DFS 使用栈或递归来管理节点的遍历顺序

N皇后问题

题目链接:N皇后问题


题目描述#

在 N x N 的方格棋盘放置了 N 个皇后,使得它们不相互攻击(即任意 2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成 45 度角的斜线上。你的任务是,对于给定的 N,求出有多少种合法的放置方法
输入描述#
输入中有一个正整数 N <= 10,表示棋盘和皇后的数量
输出描述#
为一个正整数,表示对应输入行的皇后的不同放置数量
输入输出样例#

输入

5

输出

10

思路

算法思想:排列型回溯

二道经典OJ题带你入门回溯剪枝算法_第5张图片

二道经典OJ题带你入门回溯剪枝算法_第6张图片

假设我们是4 x 4棋盘,然后判断皇后在棋盘的攻击范围:

<1>

二道经典OJ题带你入门回溯剪枝算法_第7张图片

二道经典OJ题带你入门回溯剪枝算法_第8张图片

二道经典OJ题带你入门回溯剪枝算法_第9张图片

二道经典OJ题带你入门回溯剪枝算法_第10张图片

因为我们是 4x4 的棋盘所以我们要放置4个皇后,所以皇后放置第一行第一列是不行的

<2>

二道经典OJ题带你入门回溯剪枝算法_第11张图片

二道经典OJ题带你入门回溯剪枝算法_第12张图片

二道经典OJ题带你入门回溯剪枝算法_第13张图片

我们发现:

<1>棋盘每行都 必然有且仅有 一个皇后

<2>棋盘的 第一行第一列 放皇后是 不行

<3>每放置一个皇后就将对应的米字型区域设置为 禁区,后面的皇后就不能放在 禁区 里,回溯的时候将 禁区 取消掉

<4>为了正确维护 禁区,不能使用 bool 数组来表示 禁区,需要使用 int 数组,表示这个位置被多少个皇后占用了,当占用数为 0 时表示 禁区 解除

于是我们可以采取逐行放置策略:从第一行开始,在每行放置一个皇后,直至最后一行结束

代码测试

#include
using namespace std;
const int N = 10;
int vis[N][N];    //表示多少个皇后被占用了
int n,ans;        //设置全局变量初始化为0
void dfs(int dep) //dep表示当前的搜索深度 
{
  if(dep == n+1)  //从第一行开始,在每行放置一个皇后,直至最后一行结束
  {
    ans++;
    return;
  }
  for(int i = 1;i<=n;i++)
  {
    //当前的位置已经被占用了
    if(vis[dep][i])continue;
    //修改状态,米字形式方阵
    for(int _i = 1;_i<=n;_i++)vis[dep][i]++,vis[_i][i]++;
    for(int _i = dep,j = i;_i>=1&&j>=1;_i--,j--)vis[_i][j]++;
    for(int _i = dep,j = i;_i<=n&&j>=1;_i++,j--)vis[_i][j]++;
    for(int _i = dep,j = i;_i>=1&&j<=n;_i--,j++)vis[_i][j]++;
    for(int _i = dep,j = i;_i<=n&&j<=n;_i++,j++)vis[_i][j]++;
    dfs(dep+1);//搜索下一层
    //恢复(回溯)棋盘,减少位置占用
    for(int _i = 1;_i<=n;_i++)vis[dep][i]--,vis[_i][i]--;
    for(int _i = dep,j = i;_i>=1&&j>=1;_i--,j--)vis[_i][j]--;
    for(int _i = dep,j = i;_i<=n&&j>=1;_i++,j--)vis[_i][j]--;
    for(int _i = dep,j = i;_i>=1&&j<=n;_i--,j++)vis[_i][j]--;
    for(int _i = dep,j = i;_i<=n&&j<=n;_i++,j++)vis[_i][j]--;
  }
}

int main()
{
    cin>>n;
    dfs(1);//从第一个位置开始搜索
    cout<

N皇后 

题目链接:N皇后


按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

示例 1:

二道经典OJ题带你入门回溯剪枝算法_第14张图片

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

示例 2:

输入:n = 1
输出:[["Q"]]

提示:

  • 1 <= n <= 9

思路

回溯+剪枝+坐标系

以下我们用3 x 3的棋盘进行模拟

二道经典OJ题带你入门回溯剪枝算法_第15张图片

二道经典OJ题带你入门回溯剪枝算法_第16张图片

二道经典OJ题带你入门回溯剪枝算法_第17张图片

二道经典OJ题带你入门回溯剪枝算法_第18张图片

我们发现:

<1>皇后放在第一行第一列是凑不出 3 的,这种放法是不可行的,所以我们要进行剪枝

<2>棋盘在米字行禁区的需要剪枝

判断一竖列是否有皇后

二道经典OJ题带你入门回溯剪枝算法_第19张图片

我们可以定义一个 bool 类型的数组,判断我们这一列是否有皇后

如果为 true 则这一列有皇后,为 false 则这一列没有皇后

判断对角线是否有皇后

二道经典OJ题带你入门回溯剪枝算法_第20张图片

我们给将棋盘放在直角坐标系中,对角线的方程为:y = x + b转换一下就是y - x = b

我们发现 (纵坐标 - 横坐标) 是一个常数,于是我们可以定义一个 bool 类型的数组

将这个常数存放在 bool 的数组里面

如果 常数 的坐标是 true 则代表这条对角线有皇后,是 false 则代表这条对角线没有皇后

二道经典OJ题带你入门回溯剪枝算法_第21张图片

如果 y - x = b(b是负数),我们可以采用下面这个式子:y - x + n = b + n 整体向上平移

这一侧的对角线已经分析完毕,还有一侧对角线就留给大家去分析


递归深度就是 row 控制棋盘的行,每一层里 for 循环的 col 控制棋盘的列,一行一列,确定了放置皇后的位置

每次都是要从新的一行的起始位置开始搜,所以都是从 0 开始

代码测试

class Solution {
    bool checkCol[10],checkDig1[20],checkDig2[20]; //checkCol一竖列,checkDig1主对角线,checkDig2副对角线
    vector> ret;                    //最终棋盘
    vector path;                           //每个棋盘
    int n;
public:
    vector> solveNQueens(int _n) 
    {
        n = _n;
        path.resize(n);                     //分配空间
        for(int i = 0;i

你可能感兴趣的:(C++,剪枝,算法,c语言,C++,回溯,DFS)