对八皇后解法分析

八皇后问题一向用来作为回溯的样例,这里就稍微抛下自己当初的解法

问题:在一个8*8的棋盘上摆放八个皇后,要求皇后不能相互攻击。

解法:这里以C语言为求解语言,先讲解非递归方法,之后讲解递归方法

 

由于皇后攻击方向为横向,竖向,和斜线攻击,问题也就简化为每排摆放一个皇后,

也即找到每排摆放皇后的位置。

 

一个简单的思路就是可以利用11维数组来求解,定义为全局变量,方便调用。

int arr[8];    // 用于每排皇后的位置,0-7表示第1列到第8列,初始化为-1,表示无皇后

所以一个遍历即可完成。

for( int row = 0; row < 8; ++row )

{

    // 这里就是定位每排皇后的位置

}

 

那么问题就直接集中在如何确定每排皇后的位置

由于每排皇后有8个摆放的位置,所以只能一个一个试,即

for( int col = 0; col < 8; ++col )

{

    if( 判断该排,该列是否可以摆放皇后 )

    {

        可以摆放,就直接确定皇后的位置,然后跳出循环

        arr[row] = col;

        break;

    }

}

 

这里的if判断其实只用确定,第row排的皇后和之前的皇后是否冲突即可,

判断也很简单,只要不在同一列,而且斜线对角不成45度或135度即可,

所以可以抽离成一个函数单独实现

// 用于判断第row排第col列的皇后和前row排的皇后是否冲突

// 冲突返回true

bool judge(int row, int col) 

{

    for( int i = 0; i < row; ++i )

    {

        if( col == arr[i] ||  // 判断是否位于同一列

            fabs( i - row ) == fabs( arr[i] - col ) ) // 判断是否位于斜线上

            return true;

    }

    return false;

}

 

那么上面的循环就很好写了

for( int row = 0; row < 8; ++row )

{

    for( int col = 0; col < 8; ++col )

    {

        if( !judge( row, col ) )

        {

            arr[row] = col;

            break;

        }

    }

}

 

当然,做到这一步很简单,但却得不到正确的解,若是第row排每一列都不能摆放皇后,

则第row排产生不了结果

这里就需要回溯判断,

    1、因为第row排都不能摆放,那么就必须修改row-1排的皇后位置,

    2、然后再来摆放第row排的皇后,确定好位置,则继续row+1排皇后的位置

    3、如果继续不能摆放,则重复1,若第row-1排的皇后所有的位置换过之后,

       还是确定不了row排皇后的位置,那么就继续修改row-2排,直到可以,或者

       已达到第0排,还是不行,则结束查找。

    

那么做出以下修改,加粗为增加部分

for( int row = 0; row < 8; ++row )

{

    int col = 0; // col需要在for之外用到,因为受作用域的限制,这里需要提出来

    // 这里需要有些处理,稍下分析

    

    for( ; col < 8; ++col )

    {

        if( !judge( row, col ) )

        {

            arr[row] = col;

            break;

        }

    }

    

    // 若是col == 8表示第row排皇后,已经尝试过每一列,

    // 但是均不能摆放,需要重新摆放第row-1排的皇后

    if( col == 8 )

    {

        row -= 2; // 这里需要-2,因为for循环中有一个++row

    }

}

 

这里只是单纯回到了row-1排,却没有任何处理,因为第row-1排之前已经放过皇后了(假设为第y列),

所以现在row-1排的皇后只需要从第y+1列开始摆放即可,所以可以添加的处理是

if( arr[row] != -1 )     // 如果这是回溯的一排,那么必然已经摆放过皇后了,那就单独处理

{

    col = arr[row] + 1;    // 既然是回溯的,就令该排皇后从摆放在y+1列处

    arr[row] = -1;      // 既然是回溯的,就表示这排重新摆放,所以就直接令该排为-1,表示没摆皇后

}

 

好了,处理到了,这一步,如果不考虑边界问题,基本上已经可以实现输出一种情况

(1,1) (2,5) (3,8) (4,6) (5,3) (6,7) (7,2) (8,4) #1

这种结果恰巧不涉及到回溯2排的情况,所以,正好可以输出,现在添加边界处理

if( arr[row] != -1 )     // 如果这是回溯的一排,那么必然已经摆放过皇后了,那就单独处理

{

    col = arr[row] + 1;    // 既然是回溯的,就令该排皇后从摆放在y+1列处

    arr[row] = -1;      // 既然是回溯的,就表示这排重新摆放,所以就直接令该排为-1,表示没摆皇后

    

    // 添加边界处理

    if( arr[row] == 7 ) // 都到了最后一列也不行,需要继续回溯

    { 

        row -= 2;

        continue;

    }

}

到目前为止,所以的处理都已经结束,处理了边界,现在看下全貌

这里去掉注释和judge函数

void slove()
{
    for( int row = 0; row < 8; ++row )
    {
        int col = 0;

        if( arr[row] != -1 )
        {
            col = arr[row] + 1;
            arr[row] = -1;     

            if( arr[row] == 8 ) 
            { 
                row -= 2;
                continue;
            }
        }        

        for( ; col < 8; ++col )
        {
            if( !judge( row, col ) )
            {
                arr[row] = col;
                break;
            }
        }
     
        if( col == 8 )
        {
            row -= 2;
        }
    }
}


好了,再接再厉,实现全部结果输出

要实现全部输出,则需要在完成一次结果输出之后,继续回溯上一排,以祈求一次新的输出

这样重复直到回溯到第1排第8列这最后一个结束为止,即可退出查找新的摆放方法

那么修改如下

void slove()

{

    // 如果回溯到了第1排第8列,就退出

    if( arr[row] == 7 && row == 0 ) 

    {

        return ;

    }

    

    // 和原先内容一样

    

    // 若是最后一排也摆放好皇后,则输出该种摆法结果,然后继续循环

    if( arr[row] != -1 && row = 7 )

    {

        disp();     // 一个输出函数

        // 需要回溯到上一排,以祈求新的摆法

    }

}

这里由于使用非递归,要实现这种跳转,就只能采用goto,尽管我们学习中,都被告诫限制使用goto,但是

在不破坏程序结构的同时,采用一下也是可以的

最终结果如下

void slove()
{
    for( int row = 0; row < 8; ++row )
    {
        if( arr[row] == 7 && row == 0 ) 
        {
            return ;
        }
       

        label: // 跳到这里,处理回溯的那一行
         int col = 0;
        if( arr[row] != -1 )
        {
            col = arr[row] + 1;
            arr[row] = -1;               

            if( arr[row] == 8 ) 
            { 
                row -= 2;
                continue;
            }
        }       

        for( ; col < 8; ++col )
        {
            if( !judge( row, col ) )
            {
                arr[row] = col;
                break;
            }
        }
        
        if( col == 8 )
        {
            row -= 2;
        }
        
        if( arr[row] != -1 && row == 7 )
        {
            disp();     
            goto label;
        }
    }
}


至此,关于非递归的解法就全部结束了

这里将此封装下,给下可以运行的全套代码

/*
 * eighetPuzzle.cpp
 *
 * Created on : 2012-11-26
 *     Author : zwsatan
 */
#include <iostream>
#include <cmath>
using namespace std;

class PuzzleQueen
{
    int arr[8];
    bool judge(int row, int col) 
    {
        for( int i = 0; i < row; ++i )
        {
            if( col == arr[i] || fabs( i - row ) == fabs( arr[i] - col ) )
                return true;
        }
        return false;
    }
    
public:
    PuzzleQueen() 
    {
        for( int i = 0; i < 8; ++i )
            arr[i] = -1;
    }
    
    void slove()
    {
        for( int row = 0; row < 8; ++row )
        {
            if( arr[row] == 7 && row == 0 ) 
            {
                return ;
            }
            
            label:
            int col = 0;
            if( arr[row] != -1 )
            {
                col = arr[row] + 1;
                arr[row] = -1;     
                
                if( arr[row] == 8 ) 
                { 
                    row -= 2;
                    continue;
                }
            }
            
            for( ; col < 8; ++col )
            {
                if( !judge( row, col ) )
                {
                    arr[row] = col;
                    break;
                }
            }
            
            if( col == 8 )
            {
                row -= 2;
            }
            
            if( arr[row] != -1 && row == 7 )
            {
                disp();     
                goto label;
            }
        }
    }
    
    void disp()
    {
        static int count = 0;
        for( int i = 0; i < 8; ++i )
        {
            cout << "(" << i+1 << "," << arr[i] + 1 << ") ";
        }
        cout << "#" << ++count;
        cout << endl;
    }
};

int main(int argc, char *argv[])
{
    PuzzleQueen pq;
    pq.slove();
    
    return 0;
}


 

 

疲乏了吗?

还是继续接下来看递归的解法,`(*_*)

当然,我们绝大多数时间,对待回溯采用的都是递归方法,因此,我们在上述的方法上,稍作修改,

以使它成为递归形式

回到我们最初,即使使用递归,要做到的也是每排摆放一个皇后

so

void slove(int row)    // 这里改为处理第row行皇后 

{

    // 这里用来处理,第row行皇后分别放在col列上的结果

    for( int col = 0; col < 8; ++col )

    {

        // 这里进行递归处理

    }

}

递归处理里也是很简单

// 这里处理第row行第col列的皇后,是否可以摆放

void recursion(int row, int col)

{

    // 判断是否与之前的皇后冲突

    if( !judge( row, col ) )

    {

        // 不发生冲突,将此位置记录

        arr[row] = col;

        // 若达到第七行,表示一次摆放方法结束

        if ( row == 7 ) {

            disp();    // 输出摆放结果

            return;

        }

        // 继续遍历下一个皇后位置

        slove( row + 1 );

    }

    

    // 发生冲突,不处理,退出此函数,会在slove函数中继续处理后一列的摆放位置

}

最终slove方法稍加封装可以这样

void slove()
{
    slove_( 0 ); // 处理第1行
}

void slove_(int row)    
{
    for( int col = 0; col < 8; ++col )
    {
        recursion( row, col );
    }
}

void recursion(int row, int col)
{
    if( !judge( row, col ) )
    {
        arr[row] = col;
        if ( row == 7 ) {
            disp();
            return;
        }
      
        slove_( row + 1 );
    }
}



当然,这个,也可以稍加封装成类,就不再浪费大家时间了

最后,我关于回溯的理解,就是

    循环+递归

好了,最后,拖一个以前写的八皇后程序

这是基于Qt写出来的demo程序,放出下载链,感兴趣的可以看看

 http://download.csdn.net/detail/satanzw/4813268

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值