Can I fly high in the Sky?

Never say never.

导航

回溯法之八皇后问题简单理解

Posted on 2015-08-13 18:55  lsr_flying  阅读(1579)  评论(0编辑  收藏  举报

回溯法,简单理解就是有源可溯。基本思想要借鉴穷举法,但是它不是一味地穷举,当发现某一步不符合条件时,这一步后面的穷举操作就不进行了(俗称“剪枝”),我自己把它叫做动态穷举法。假设第一个步骤可行,那么执行第二个步骤,第三个......如果其中第三个步骤不行,那么我们再回过来(回溯),第二个步骤换一种方法尝试,然后再重新第三个步骤,第四个......直到完成任务要求为止。

这里,以八皇后问题为例。试图把回溯法讲清楚。

注意:递归应该是一种算法结构,回溯法是一种算法思想。

 

何为八皇后问题?

(百度百科)八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n×n,而皇后个数也变成n。当且仅当 n = 1 或 n ≥ 4 时问题有解。

八皇后问题最早是由国际西洋棋棋手马克斯·贝瑟尔于1848年提出。之后陆续有数学家对其进行研究,其中包括高斯和康托,并且将其推广为更一般的n皇后摆放问题。八皇后问题的第一个解是在1850年由弗朗兹·诺克给出的。诺克也是首先将问题推广到更一般的n皇后摆放问题的人之一。1874年,S.冈德尔提出了一个通过行列式来求解的方法,这个方法后来又被J.W.L.格莱舍加以改进。

艾兹格·迪杰斯特拉在1972年用这个问题为例来说明他所谓结构性编程的能力。
八皇后问题出现在1990年代初期的著名电子游戏第七访客中。

解题思想

我们采用回溯法来解决这个问题。还记得我说的动态穷举法(“剪枝”)?那么我们就开始穷举吧。过程请看下图。八个皇后,每个皇后放一行,那么我们要确定的就是每行皇后要放的列的位置。对于第一行,假设把皇后放在第一列(这里就开始了一个for循环了)。第一步当然满足,然后我们看第二行(又开始一个for循环啦),假设把第二个皇后放在(2,1)(行,列)处,不行(“剪枝”)!那继续for,放在(2,2)处,不行(“剪枝”)!继续for,放在(2,3)处。Bingo!那我们进行第三步,第四步....也许,在第三步当中,执行完第三步的八次for循环后,仍未有合理的答案。那么就得返回第二步了。这时候,把第二个皇后放在(2,4)处。继续......

(参考http://blog.csdn.net/justme0/article/details/7540425)

         

 

接下来,我们就要尝试把这个过程转化成伪代码。

//寻找当前行的皇后应该位于哪一列
void FindQueen(row)
{
  for(int i=0;i<8;i++)
  {
    //1.判断当前是否满足要求
    if(Is_Meet(row,i))
    {
    //2.判断当前是否是最后一行了
      if(最后一行){输出操作,并返回}
    //3.执行下一行匹配
      FindQueen(row+1);
    //4.如果进行到这一步,说明步骤三已经操作完,没有合适结果,需要返回上一步,即执行当前for的下一个循环
    }
  }
}

 

判断是否满足条件

这里,有一步我们需要再考虑下的,即第1步,如何判断当前插入的皇后满足条件。

根据国际象棋的规则,不能有两个皇后位于:1)同一列;2)同一对角线。

1)同一列

比较好判断,列号相同,即属于同一列;

2)同一对角线

有两种位置:正斜对角线和反斜对角线。判断条件分别为:(r1+c1)==(r2+c2),(r1-c1)==(r2-c2)

将新的皇后位置依次与已经插入过的皇后位置进行比较判断即可。

 

具体实现

看到以上代码,应该有点思路了吧。上具体实现代码。

bool Is_Meet(int row,int column)
{
    int c;
    for(int r=0;r<row;r++)
    {
        c=queen[r];
        if(column==c)
            return 0;
        if((row+column)==(c+r))
            return 0;
        if((column-row)==(c-r))
            return 0;
    }
    return 1;
}

void findQueen(int row)
{
    for(int c=0;c<8;c++)
    {
        if(Is_Meet(row,c))
        {
            queen[row]=c;
            if(row==7)
            {
                print();
                return;
            }
            findQueen(row+1);
        }
        queen[row]=-1;
    }
}

 

为了好玩,简单地改了下,使其可以在控制台动态显示匹配过程。

#include "iostream"
#include "string"
#include <Windows.h>
using namespace std;
int queen[8];

void print()
{
    system("cls");
    cout << "八皇后问题动态演示\n";
    cout<< "------------------------\n";
    for (int outer = 0; outer < 8; outer++)
    {
        if(queen[outer]!=-1)
        {
        for (int inner = 0; inner < queen[outer]; inner++)
            cout << " . ";
        cout<<" # ";
        }
        for (int inner = queen[outer] + 1; inner < 8; inner++)
            cout << " . ";
        cout<<endl;
    }
}

bool Is_Meet(int row,int column)
{
    int c;
    for(int r=0;r<row;r++)
    {
        c=queen[r];
        if(column==c)
            return 0;
        if((row+column)==(c+r))
            return 0;
        if((column-row)==(c-r))
            return 0;
    }
    return 1;
}

void findQueen(int row)
{
    for(int c=0;c<8;c++)
    {
        Sleep(1000);
        print();
        if(Is_Meet(row,c))
        {
            queen[row]=c;
            if(row==7)
            {
                //print();
                return;
            }
            findQueen(row+1);
        }
        queen[row]=-1;
    }
}
int main()
{
    memset(queen,-1,8*sizeof(int));//这里是赋-1,故不会出错,要清楚memset是依次对单个字节进行赋值
    findQueen(0);
}