回溯法经典案例----N皇后

   在一个N*N的棋盘上放置N个皇后,且使得每两个之间不能互相攻击,也就是使得每两个不在同一行,同一列和同一斜角线上。
   下面N取8:

代码如下:


//八皇后=====92种

#include 

#define N 8

char board[N+2][N+2];

typedef struct pos
{
    int ios;
    int jos;
}Pos_t;

Pos_t pos[3] = {{-1,-1},{-1,0},{-1,1}};

//init初始化,显示#边框
void init( void )                       //1
{
    int i;
    int j;

    for( i=0;i2;i++)
    {
        board[0][i]='#'; 
        board[N+1][i]='#'; 
        board[i][0]='#'; 
        board[i][N+1]='#'; 
    }

    for (i=1;i<=N;i++)
    {
        for(j=1;j<=N;j++)
        {
            board[i][j] = ' '; 
        }
    }
}

//显示
void display( void )                    //4
{
    int i;
    int j;
    static int cnt = 0;

    for(i=0;i2;i++)
    {
        for(j=0;j2;j++)
        {
            printf("%c ",board[i][j]); 
        }
        printf("\n");
    }

    printf("cnt=%d\n",++cnt);
}


//检测是否不符合规则
int check(int i,int j)                      //3
{
    int ret=1;
    int p=0;

    for(p=0;ret && p<3;p++)
    {
        int ni=i;
        int nj=j;
        while( ret && board[ni][nj] != '#' )
        {
            ni=ni + pos[p].ios;
            nj=nj + pos[p].jos;
            ret=ret && (board[ni][nj]!= '*');
        }
    }

    return ret;
}

void find( int i )                          //2
{
    int j=0;
    if(i>N)
    {
        display();
        getchar();
    }
    else for(j=1;j<=N;j++)
    {
         if( check(i,j) )
         {
            board[i][j]='*';
            find(i+1);
            board[i][j]=' ';
         }
    }
}


int main( void )
{
    printf("\n");
    init();
    find(1);
    printf("\n");
    return 0;
}

案例分析:

8皇后问题是十九世纪著名数学家高斯于1850年提出的,已经过去这么多年了,网上有好多写八皇后的例子,不过我感觉我学到的是最好懂的的。

首先是我们init函数。这个函数主要用来画出我们8*8的图案,所以我们定义一个board[N+2][N+2]的一个二维字符数组,

其次是我们的find函数,这个函数用来不断的在我们的棋盘上摆放“皇后“,每次都会让check函数去检测,我们放的这个位置可不可以,可以的话就放那里,然后让行数i加一,递归调用自己,继续判断;如果放哪里不行的话,就清空刚才放的那个“皇后“。知道i=N的时候,在调用我们的display函数,显示整个图形。

在find函数调用check函数的时候他会把当前的行i、列j传递给check函数。在check函数中,是我们的核心,他会检测每次传递过来的i,j可不可以在当前的位置摆放,那么按照我们的规则是,使得每两个“皇后“不在同一行,同一列和同一斜角线上,那么只要我们不摆放在(i-1,j-1)、(i-1,j)、(i-1,j+1)这几个位置就好了,不是吗?,所以,我们定义一个结构体pos_t类型的pos[3]数组,定义了一个循环变量p,然后只要让他去循环三次足以不是吗,在学习这个代码的时候有同学问过我为什么要定义ni和nj,不是多次一举吗,当你细细去想的时候,就不会这样说了,因为,在我们的while循环中每次都会改变我们的i,j的值,这样在我们下次循环的时候,我们的i,j,的值发生了变化,所以就会出现错误,这下明白了吧,gg。。。定义ret为我们带回check的返回值。

对后就是我们的显示函数display。这个我们不多说了吧。

看完代码是不是感觉这个也特别简单呢。。。

写这个程序我们主要应该领会两个问题,一个是回溯法解题的思想,一个是递归的思想。在我们的find函数,和check函数中就应用到了这两个思想,回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。当我们遇到某一类问题时,它的问题可以分解,但是又不能得出明确的动态规划或是递归解法,这时可以考虑用回溯法解决此类问题。回溯法的优点在于其程序结构明确,可读性强,易于理解,而且通过对问题的分析可以大大提高运行效率。但是,对于可以得出明显的递推公式迭代求解的问题,还是不要用回溯法,因为它花费的时间比较长。

你可能感兴趣的:(c)