八皇后问题--递归与非递归的实现

八皇后问题是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不一样的做者发表了40种不一样的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言能够解决此问题。

      下面是算法的高级伪码描述,这里用一个N*N的矩阵来存储棋盘:ios

      1) 算法开始, 清空棋盘,当前行设为第一行,当前列设为第一列算法

      2) 在当前行,当前列的位置上判断是否知足条件(即保证通过这一点的行,列与斜线上都没有两个皇后),若不知足,跳到第4步数组

      3) 在当前位置上知足条件的情形:bash

                 在当前位置放一个皇后,若当前行是最后一行,记录一个解;数据结构

                 若当前行不是最后一行,当前行设为下一行, 当前列设为当前行的第一个待测位置;函数

                 若当前行是最后一行,当前列不是最后一列,当前列设为下一列;spa

                 若当前行是最后一行,当前列是最后一列,回溯,即清空当前行及如下各行的棋盘,而后,当前行设为上一行,当前列设为当前行的下一个待测位置;设计

                以上返回到第2步code

      4) 在当前位置上不知足条件的情形:排序

                若当前列不是最后一列,当前列设为下一列,返回到第2步;

                若当前列是最后一列了,回溯,即,若当前行已是第一行了,算法退出,不然,清空当前行及如下各行的棋盘,而后,当前行设为上一行,当前列设为当前行的下一个待测位置,返回到第2步; 

一种递归的实现

数据结构:在N*N的矩阵中,设皇后与列对应,摆放皇后,便是要找到皇后对就的行。i 表示第几个皇后,queue[i]表求皇后所在的行。

1.存放第 i 个皇后:从第1行到N行,对每一行进行试探,每次试探检查是否符合要求,

2.若符合要求,则检查是否为最后个皇后(即最后一列)

3.如果,则打印出全部皇后对应的位置坐标。若不是最后一个则对下皇后进行找位,即返回到1处再执行

/*递归方法的一种实现*/  
#include <stdio.h>  
#include <stdlib.h>  
  
#define max 8  
int queen[max], sum=0; /* max为棋盘最大坐标 ,sum 计数输入的方案数量*/  
  
void show() {/* 输出全部皇后的坐标 */  
    int i;  
    for(i = 0; i < max; i++){  
        printf("(%d,%d) ", i, queen[i]);  
    }  
    printf("\n");  
    sum++;  
}  
  
int check(int n) {/* 检查当前列可否放置皇后 */  
    int i;  
    for(i = 0; i < n; i++){ /* 检查横排和对角线上是否能够放置皇后 */  
        if(queen[i] == queen[n] || abs(queen[i] - queen[n]) == (n - i))     {  
            return 0;  
        }  
    }  
    return 1;  
}  
  
void put(int n) {/* 回溯尝试皇后位置,n为第N个皇后 */  
    int i;  
    for(i = 0; i < max; i++){         
        queen[n] = i; /* 将皇后摆到当前循环到的位置 */  
        if(check(n)){             
            if(n == max - 1){  
                show(); /* 若是所有摆好,则输出全部皇后的坐标 */  
            }           
            else{  
                put(n + 1); /* 不然继续摆放下一个皇后 */  
            }  
        }  
    }  
}  
int main()  
{  
    put(0); /* 从第0个皇后开始放起 */  
    printf("%d", sum);  
    system("pause");  
    return 0;  
}

pause脚本以下

#!/bin/bash

echo 按任意键继续
read -n 1
echo 继续运行

chmod a+x pause 

sudo cp pause /usr/bin/.

可是通常来讲递归的效率比较差,下面重点讨论一下该问题的非递归实现。

一种非递归的实现

        非递归方法的一个重要问题时什么时候回溯及如何回溯的问题。程序首先对N行中的每一行进行探测,寻找该行中能够放置皇后的位置,具体方法是对该行的每一列进行探测,看是否能够放置皇后,若是能够,则在该列放置一个皇后,而后继续探测下一行的皇后位置。若是已经探测完全部的列都没有找到能够放置皇后的列,此时就应该回溯,把上一行皇后的位置日后移一列,若是上一行皇后移动后也找不到位置,则继续回溯直至某一行找到皇后的位置或回溯到第一行,若是第一行皇后也没法找到能够放置皇后的位置,则说明已经找到全部的解程序终止。若是该行已是最后一行,则探测完该行后,若是找到放置皇后的位置,则说明找到一个结果,打印出来。可是此时并不能再此处结束程序,由于咱们要找的是全部N皇后问题全部的解,此时应该清除该行的皇后,从当前放置皇后列数的下一列继续探测。

/** 
* 回溯法解N皇后问题 
* 使用一个一维数组表示皇后的位置 
* 其中数组的下标表示皇后所在的行 
* 数组元素的值表示皇后所在的列 
* 这样设计的棋盘,全部皇后一定不在同一行,因而行冲突就不存在了 
* date  : 2011-08-03  
* author: liuzhiwei 
**/  
  
#include <stdio.h>  
#include <stdlib.h>  
#include <math.h>  
  
#define QUEEN 8          //皇后的数目  
#define INITIAL -10000   //棋盘的初始值  
  
int a[QUEEN];            //一维数组表示棋盘  
  
void init() {//对棋盘进行初始化  
    int *p;  
    for (p = a; p < a + QUEEN; ++p) {      
        *p = INITIAL;  
    }  
}   
  
int valid(int row, int col) {   //判断第row行第col列是否能够放置皇后  
    int i;  
    for (i = 0; i < QUEEN; ++i) {  //对棋盘进行扫描  
        if (a[i] == col || abs(i - row) == abs(a[i] - col))   //判断列冲突与斜线上的冲突  
            return 0;  
    }  
    return 1;  
}   
  
void print() {   //打印输出N皇后的一组解  
  
    int i, j;  
    for (i = 0; i < QUEEN; ++i)  {  
        for (j = 0; j < QUEEN; ++j)  {  
            if (a[i] != j)      //a[i]为初始值  
                printf("%c ", '.');  
            else                //a[i]表示在第i行的第a[i]列能够放置皇后  
                printf("%c ", '#');  
        }  
        printf("\n");  
    }  
    for (i = 0; i < QUEEN; ++i)  
        printf("%d ", a[i]);  
    printf("\n");  
    printf("--------------------------------\n");  
}  
  
void queen(){    //N皇后程序  
    int n = 0;  
    int i = 0, j = 0;  
    while (i < QUEEN){  
        while (j < QUEEN) {       //对i行的每一列进行探测,看是否能够放置皇后  
            if(valid(i, j)) {    //该位置能够放置皇后  
                a[i] = j;        //第i行放置皇后  
                j = 0;           //第i行放置皇后之后,须要继续探测下一行的皇后位置,因此此处将j清零,从下一行的第0列开始逐列探测  
                break;  
            }else{  
                ++j;             //继续探测下一列  
            }  
        }  
        if(a[i] == INITIAL) {        //第i行没有找到能够放置皇后的位置  
            if ( 0 == i)             //回溯到第一行,仍然没法找到能够放置皇后的位置,则说明已经找到全部的解,程序终止  
                break;  
            else {                  //没有找到能够放置皇后的列,此时就应该回溯  
                --i;  
                j = a[i] + 1;        //把上一行皇后的位置日后移一列  
                a[i] = INITIAL;      //把上一行皇后的位置清除,从新探测  
                continue;  
            }  
        }  
        if (i == QUEEN - 1)  {        //最后一行找到了一个皇后位置,说明找到一个结果,打印出来  
            printf("answer %d : \n", ++n);  
            print();  
            //不能在此处结束程序,由于咱们要找的是N皇后问题的全部解,此时应该清除该行的皇后,从当前放置皇后列数的下一列继续探测。  
            //_sleep(600);  
            j = a[i] + 1;             //从最后一行放置皇后列数的下一列继续探测  
            a[i] = INITIAL;           //清除最后一行的皇后位置  
            continue;  
        }  
        ++i;              //继续探测下一行的皇后位置  
    }  
}  
  
int main(void){  
    init();  
    queen();  
    system("pause");  
    return 0;  
}

利用STL库

#include <cmath>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MAX = 8;

vector<int> board(MAX);

void show_result(){
    for(size_t i = 0; i < board.size(); i++)
        cout<<"("<<i<<","<<board[i]<<")";
    cout<<endl;
}


int check_cross(){
    for(size_t i = 0; i < board.size()-1; i++){
        for(size_t j = i+1; j < board.size(); j++){
            if((j-i) == (size_t)abs(board[i]-board[j]))
                return 1;
        }
    }
    return 0;
}


void put_chess(){
    while(next_permutation(board.begin(), board.end())){
        if(!check_cross())
            show_result();
    }
}



int main(){
    for(size_t i =0; i < board.size(); i++)
        board[i] = i;
    put_chess();
    return 0;
}

回溯法

#include <cstdio>

static int count=0;
void print(int (*chess)[8],int row)
{
  for(int i=0;i<row;i++)
   {
     for(int j=0;j<8;j++)
      printf("%d ",chess[i][j]);
      printf("\n");
   }
return;
}
bool notDanger(int row,int col,int (*chess)[8])
{
    int flag1=0,flag2=0,flag3=0,flag4=0,flag5=0,flag6=0;
     
    for(int i=0;i<8;i++)
      {
    if(chess[i][col])
         {
           flag1=1;
           break;
         }
      }
    for(int i=0;i<8;i++)
      {
        if(chess[row][i])
         {
           flag2=1;
           break;
         }
      }
    for(int i=row,j=col;i<8&&j<8;i++,j++)
      {
        if(chess[i][j])
         {
           flag3=1;
           break;
         }
      }
    for(int i=row,j=col;i<8&&j>=0;i++,j--)
      {
        if(chess[i][j])
         {
           flag4=1;
           break;
         }
      }
    for(int i=row,j=col;i>=0&&j>=0;i--,j--)
      {
        if(chess[i][j])
         {
           flag5=1;
           break;
         }
      }
    for(int i=row,j=col;i>=0&&j<8;i--,j++)
      {
        if(chess[i][j])
         {
           flag6=1;
           break;
         }
      }
   if(flag1||flag2||flag3||flag4||flag5||flag6)
       return false;
   else
       return true;
}
void eightQueen(int row,int col,int (*chess)[8])
{
   int chesstmp[8][8];
   for(int i=0;i<8;i++)
     for(int j=0;j<8;j++)
    chesstmp[i][j]=chess[i][j];
   if(8==row)
   {
      count++;
      printf("the %d answer\n",count);
      print(chess,8);
      printf("-------------\n");
   }
   else
   {
      for(int i=0;i<col;i++)
       {
    if(notDanger(row,i,chess))
       {
          for(int j=0;j<8;j++)
         {
            chesstmp[row][j]=0;
         }
          chesstmp[row][i]=1;
              eightQueen(row+1,col,chesstmp);
           }
       }
   }
return;
}
int main()
{
   int chess[8][8]={0};
   eightQueen(0,8,chess);
}
#include <stdio.h>
#include <stdlib.h>
#define N 8
#define FALSE 0
#define TRUE 1
static int x = 0;
//回溯法递归实现八皇后问题
void//输出棋盘
printChessboard(int const *flag){
    int chessboard[N][N] = {0};
    for ( int i=0; i<N; i++) {
        chessboard[i][flag[i]] = TRUE;
    }
    for ( int i=0; i<N; i++) {
        for ( int j=0; j<N; j++) {
            printf( "%d ", chessboard[i][j]);
        }
        printf( "\n");
    }
}
int
checkQueen(int const *flag, int p){
    int flag_t = TRUE;
    int pv = flag[p];
    if ( p==0) {
        return flag_t;
    }
    p--;
    for ( int i=1; p>=0; p--, i++) {
        if ( flag[p]==pv || flag[p]==pv-i || flag[p]==pv+i) {
            flag_t = FALSE;
        }
    }
    return flag_t;
}
void
eightQueen(int *flag, int p){
    for ( int i=0; i<N; i++) {
        flag[p] = i;
        if ( checkQueen( flag, p)==TRUE && p==7) {
            printChessboard( flag);
            x++;
            printf( "%d\n", x);
        }
        else if ( checkQueen( flag, p ) == TRUE){
            eightQueen( flag, p+1);
        }
    }
}
 
int main(int argc, const char * argv[]) {
        int flag[N] = {-1, -1, -1, -1, -1, -1, -1, -1};
        //        int flag[N] = {3, 2, 1, 5, 6, 5, 2, 3};
        eightQueen( flag, 0);
        //        printChessboard( flag);
    return 0;
}

8皇后问题扩展:如下代码根据用户输入的棋盘格数,打印出全部的解法,输入n=8时,即为8*8格的棋盘。核心代码参考自刘汝佳的《算法竞赛入门经典》,完整代码以下,只要保存为.c文件,编译执行便可:

#include <stdio.h>
#define MAXN 20  

//打印函数  
void printf_count(int *A,int n)  
{  
	for(int i=0;i<n;i++)  
	{  
		for(int j=0;j<n;j++)  
		{  
			if(A[i]==j)  
			  printf("#");  
			else  
			  printf("*");  
		}  
		printf("\n");  
	}  
}  


//递归函数  
int count;  
void search(int n,int *A,int cur)  
{  
	int i,j,ok;  
	if(n==cur)//递归边界:最后一行也排序好了,全部皇后不冲突  
	{  
		count++;//可行方案数+1  
		printf_count(A,n);//打印一个可行方案  
		printf("\n");  
	}else{  
		for(i=0;i<n;i++)//在cur这一行尝试全部0至n-1的位置  
		{  
			ok=1;  
			A[cur]=i;  
			for(j=0;j<cur;j++)//与前cur-1行进行检查是否冲突  
			{  
				if(A[cur]==A[j]||cur-A[cur]==j-A[j]||cur+A[cur]==j+A[j])//若是在同一列、或两个斜线上,则此位置冲突  
				{  
					ok=0;break;  
				}  
			}  
			if(ok)search(n,A,cur+1);//i的位置可用,递归调用,设置下一行皇后的位置  
		}  
	}  
}  

void main()  
{  
	int n,A[MAXN];  
	printf("请输入棋盘的行列数,一般为8\n");  
	scanf("%d",&n);//输入棋盘行(列)数  
	search(n,A,0);  
	printf("%d\n",count);  
}
相关文章
相关标签/搜索