八皇后問題--遞歸與非遞歸的實現

八皇后問題是一個古老而著名的問題,是回溯算法的典型案例。該問題是國際西洋棋棋手馬克斯·貝瑟爾於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);  
}
相關文章
相關標籤/搜索