递归回溯总结

  8皇后问题是一道非常经典的题目。题目是说,一个N*N的国际象棋棋盘上主放置N个皇后,使其不能相互攻击,即任何两个皇后都不能处在棋盘的同一行,同一列,同一条斜线上,试问共有多少种摆法?

        其实,题目就是要找出所有的可能情况,然后排除其中不符合条件的情况,剩下的情况即为所要求的。怎么找出所有的情况呢?对于8皇后,我们可以使用穷举法,穷举出每一种放置方法,然后判断是否符合题意。如果每次放一行,那就需要8重循环才可以解出来。虽然空间复杂度可以小到为0,但是时间复杂度太高。

        书中一般使用回溯法来解此题。仔细分析此题,可以发现:每一行上只能放置一个皇后,然后后面每行放置的皇后,不能与前面的行上放置的皇后在同一列上或者同一对角线上。所以用一个一维数组就可以存放在棋盘上放置的皇后的行列信息:一维数组的第i个位置存放的数值j就表示,在棋盘的第i行、第j列上放着一个皇后。棋盘的一行就用一个元素来表示,所以不能在同一行就不用判断了。知道了皇后在棋盘的行列位置后,判断是否符合后面的两个条件也比较容易了(对角线只要仔细分析下,两个二维坐标点如果在对角线上,他们的行列坐标将会满足何种情况即可)。

         搞定了数据结构,接着就要考虑如何进行回溯搜索了。回溯一般借用递归来实现。用我的一个ACM非常牛的一个同学的话来说:回溯就是让计算机自动的去搜索,碰到符合的情况就结束或者保存起来,在一条路径上走到尽头也不能找出解,就回到原来的岔路口,选择一条以前没有走过的路继续探测,直到找到解或者走完所有路径为止。就这一点,回溯和所谓的DFS(深度优先搜索)是一样的。那现在的关键是,怎么实现搜索呢?回溯既然一般使用递归来实现,那个这个递归调用该如何来写呢?我现在的理解就是,进行回溯搜索都会有一系列的步骤,每一步都会进行一些查找。而每一步的情况除了输入会不一样之外,其他的情况都是一致的。这就刚好满足了递归调用的需求。通过把递归结束的条件设置到搜索的最后一步,就可以借用递归的特性来回溯了。因为合法的递归调用总是要回到它的上一层调用的,那么在回溯搜索中,回到上一层调用就是回到了前一个步骤。当在当前步骤找不到一种符合条件情况时,那么后续情况也就不用考虑了,所以就让递归调用返回上一层(也就是回到前一个步骤)找一个以前没有尝试过的情况继续进行。当然有时候为了能够正常的进行继续搜索,需要恢复以前的调用环境。

下面贴出8皇后问题的代码:

#include<iostream>
#include
<cmath>
usingnamespace std;

void PrintResult(int*arr,int
n)
{
    
for (int i=1; i!= n+ 1;++
i)
         cout
<<"("<< i << ","<< arr[i]<< ")"<<""
;
     cout
<<
endl;
}

bool Verify(int*arr,int
i)
{
    
/* 和前面的i - 1行比较,看当前放置位置是否合法?*/

    
for (int k = 1; k != i;++ k)
        
if (arr[k]== arr[i]|| abs(i - k)== abs(arr[i]-
arr[k]))
            
returnfalse
;
    
return true
;
}
/*
虽然只用了一个一维数组,但是其中已经保存了足够的信息。
因为每一行只能放一个皇后,所以一维数组的第i个位置存放的
是在第i行的哪一列(第j列)上放置了皇后。这个递归函数
每次处理一行,直到第n行(下标从1开始)。
*/

void NQueens(int*arr,int i, int n)
{
    
/* 尝试着在第i行的第j列放置一个皇后。*/

    
for (int j = 1; j != n+1;++ j)
     {
         arr[i]
=
j;
        
if
(Verify(arr, i))
         {
            
/*
这个递归程序的结束条件是第n行放置完毕,
             所以,当递归函数从调用NQueens(arr, i + 1, n)返回时,
             就是回到了第i行,继续搜索合适的位置。当第i + 1行的
             所有位置都不能满足的时候,上面的调用就会返回,也就
             是进行了所谓的回溯。这个回溯不需要显示的恢复以前的
             调用环境,因为所需要的信息没有被破坏。
*/

            
if (i== n)
                 PrintResult(arr, n);
            
else

                 NQueens(arr, i
+1 , n);//下一行放置皇后
         }
     }
}

int
main()
{
    
int
n;
     cin
>>
n;
    
int *arr =newint[n +1
];
     NQueens(arr,
1
, n);

    
return 0
;
}

最后还有一个回溯的模般

回溯法对解空间作深度优先搜索,因此,在一般情况下用递归方法实现回溯法。

void backtrack (int t)
{
if (t>n) output(x);
else
for (int i=f(n,t);i<=g(n,t);i++) {
x[t]=h(i);
if (constraint(t)&&bound(t)) backtrack(t+1);
}
}
供大家学习!

 

注:回溯法是一个既带有系统性又带有跳跃性的的搜索算法。它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。否则,进入该子树,继续按深度优先的策略进行搜索。回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题.

详细介绍参照:百度百科 http://baike.baidu.com/view/699271.htm

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值