回溯法求解n皇后问题(复习)

回溯法

回溯法是最常用的解题方法,有“通用的解题法”之称。当要解决的问题有若干可行解时,则可以在包含问题所有解的空间树中,按深度优先的策略,从根节点出发搜索解空间树。算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的搜索,继续查找该结点的兄弟结点,若它的兄弟结点都不包含问题的解,则返回其父结点——这个步骤称为回溯。否则进入一个可能包含解的子树,继续按深度优先的策略进行搜索。这种以深度优先的方式搜索问题的解的算法称为回溯法。它本质上是一种穷举法,但由于在搜索过程中不断略过某些显然不合适的子树,所以搜索的空间大大少于一般的穷举,故它适用于解一些组合数较大的问题。

总结一下:

一、基本定义

回溯法(back track method)是在包含问题的所有可能解的解空间树中,从根结点出发,按照深度优先的策略进行搜索,对于解空间树的某个结点,若满足约束条件,则进入该子树继续搜索,否则将以该结点为根结点的子树进行剪枝。

二、适用范围

可避免搜索所有的可能解,适用于求解组合数较大的问题。

三、n皇后问题

问题:在n x n的棋盘上摆放n个皇后,而且n个皇后中的任意两个是不能处于同一行、同一列、或同一斜线上。

用数组x[i](1≤i≤n)表示n后问题的解。其中x[i]表示皇后i放在棋盘的第i行的第x[i]列。由于不允许将2个皇后放在同一列,所以解向量中的x[i]互不相同。2个皇后不能放在同一斜线上是问题的隐约束。对于一般的n后问题,这一隐约束条件可以化成显约束形式。设2个皇后放置位置为(i,j),(k,l):

显然,棋盘的每一行上可以而且必须摆放一个皇后,所以,n皇后问题的可能解用一个n元向量X=(x1, x2, …, xn)表示,其中,1≤i≤n并且1≤xi≤n,即第i个皇后放在第i行第xi列上

由于两个皇后不能位于同一列上,所以,解向量X必须满足约束条件:

xi≠xj (式1)

若两个皇后摆放的位置分别是(i, xi)和(j, xj),在棋盘上斜率为-1的斜线上,满足条件i-j= xi-xj,在棋盘上斜率为1的斜线上,满足条件i+j= xi+xj,综合两种情况,由于两个皇后不能位于同一斜线上,所以,解向量X必须满足约束条件:

|i-xi|≠|j-xj| (式2)

为了简化问题,下面讨论四皇后问题:

四皇后问题的解空间树是一个完全4叉树,树的根结点表示搜索的初始状态,对应Backtrack(1,x);从根结点到第2层结点对应皇后1在棋盘中第1行的可能摆放位置,从第2层结点到第3层结点对应皇后2在棋盘中第2行的可能摆放位置,依此类推。

完全4叉树,我只画了一部分,完整的应该是除了叶结点,每个内部结点都有四个子结点,k表示层数:

img

剪枝之后:

img

回溯法求解4皇后问题的搜索过程:

img

当然这个图只表示到找到的第一个解,我们知道还有另外一个解。

代码

变量sum记录可行方案个数,初始为0;

n表示皇后个数,由用户输入;

x[]数组保存问题的解,表示皇后i放在棋盘的第i行第x[i]列,初始时各元素都为0,而我们目的是求出有多少组(x[1],x[2],x[3]……x[n])满足摆放条件;

output(int x[])函数作用是输出当前找到的一个可行解,只在搜索到叶节点时才会调用;

Place(int k,int x[])函数作用是,对当前行k以上的所有行(即1到k-1行)逐行进行检查,如果该行与上面任何一行相互攻击(即位于同一对角线上了或同列了:abs(i-k)abs(x[i]-x[k]) || x[i]x[k]),那么返回false,否则返回true;

Backtrack(int k,int x[])函数表示搜索解空间中第k层子树,k>n时,算法搜索至叶节点,得到一个新的n皇后互不攻击放置方案,那么输出该方案,可行方案数sum加1;k<=n时,当前扩展节点是解空间的内部节点,该节点有x[1],x[2],x[3]……x[n]共n个子节点,对每一个子节点,用函数Place检查其可行性,如果可行,以深度优先的方式递归地对可行子树搜索,如果不可行剪枝。

#include <iostream>
#include <cmath>
using namespace std;
// 记录可行方案个数
int sum=0;
// 表示皇后个数,由用户输入
int n;
// 输出当前找到的一个可行解,只在搜索到叶节点时才会调用
int output(int  x[]){
    int i;
    for(i=1;i<=n;i++){
      cout << "(" << i << "," << x[i] << ")" << " ";                                   
    }
    cout << endl;
    return 0;
}

// 对当前行k以上的所有行(即1到k-1行)逐行进行检查
bool Place(int k,int  x[]){
     int i;
     for(i=1;i<k;i++){
     if(abs(i-k)==abs(x[i]-x[k]) || x[i]==x[k])
         return false;                     
     } 
     return true;
     
}
     
     
// 见上文详解     
int Backtrack(int k,int  x[]){
    int i;
     if(k>n){//如果是叶节点,直接输出找到的一个解 
          output(x);
          sum++; 
     }
     else{//内部节点,如果满足约束条件,继续深度搜索 。i代表列数,从1到n 
         for(i=1;i<=n;i++){
               x[k]=i;
               if(Place(k,x))
               Backtrack(k+1,x);
         } 
     }
     
     
}

int main(){
    int *x,i;
    
    cout << "输入皇后个数:" << endl;
    cin >> n;
    cout << endl;
    
    // 数组保存问题的解,表示皇后i放在棋盘的第i行第x[i]列
    x=new int[n+1];
    // 初始时各元素都为0
    for(i=0;i<=n;i++){
      x[i]=0;                
    }
    
    Backtrack(1,x);
    
    cout << endl;
    cout << "解的个数:" << sum << endl;
    
    system("pause"); 
    return 0;
}

运行结果

image

posted @ 2022-12-12 16:09  Galaxy不写bug  阅读(643)  评论(0编辑  收藏  举报