1. 程式人生 > >演算法總結——八皇后問題(三種解法)

演算法總結——八皇后問題(三種解法)

問題描述

會下國際象棋的人都很清楚:皇后可以在橫、豎、斜線上不限步數地吃掉其他棋子。如何將8個皇后放在棋盤上(有8 * 8個方格),使它們誰也不能被吃掉!這就是著名的八皇后問題。 對於某個滿足要求的8皇后的擺放方法,定義一個皇后串a與之對應,即a=b1b2...b8,其中bi為相應擺法中第i行皇后所處的列數。已經知道8皇后問題一共有92組解(即92個不同的皇后串)。給出一個數b,要求輸出第b個串。串的比較是這樣的:皇后串x置於皇后串y之前,當且僅當將x視為整數時比y小。 
輸入資料
第1行是測試資料的組數n,後面跟著n行輸入。每組測試資料佔1行,包括一個正整數b(1 <= b <= 92)
輸出要求
n行,每行輸出對應一個輸入。輸出應是一個正整數,是對應於b的皇后串
輸入樣例
2
1
92


輸出樣例
15863724
84136275


解題思路一

因為要求出92種不同擺放方法中的任意一種,所以我們不妨把92種不同的擺放方法一次性求出來,存放在一個數組裡。為求解這道題我們需要有一個矩陣模擬棋盤,每次試放一個棋子時只能放在尚未被控制的格子上,一旦放置了一個新棋子,就在它所能控制的所有位置上設定標記,如此下去把八個棋子放好。當完成一種擺放時,就要嘗試下一種。若要按照字典序將可行的擺放方法記錄下來,就要按照一定的順序進行嘗試。也就是將第一個棋子按照從小到大的順序嘗試;對於第一個棋子的每一個位置,將第二個棋子從可行的位置從小到大的順序嘗試;在第一第二個棋子固定的情況下,將第三個棋子從可行的位置從小到大的順序嘗試;依次類推。
首先,我們有一個8*8的矩陣模擬棋盤標識當前已經擺放好的棋子所控制的區域。用一個有92行每行8個元素的二維陣列記錄可行的擺放方法。用一個遞迴程式來實現嘗試擺放的過程。基本思想是假設我們將第一個棋子擺好,並設定了它所控制的區域,則這個問題變成了一個7皇后問題,用與8皇后同樣的方法可以獲得問題的解。那我們就把重心放在如何擺放一個皇后棋子上,擺放的基本步驟是:從第1到第8個位置,順序地嘗試將棋子放置在每一個未被控制的位置上,設定該棋子所控制的格子,將問題變為更小規模的問題向下遞迴,需要注意的是每次嘗試一個新的未被控制的位置前,要將上一次嘗試的位置所控制的格子復原。


參考程式一

#include <stdio.h>
#include <math.h>
int queenPlaces[92][8]; //存放92種皇后棋子的擺放方法
int count = 0;
int board[8][8]; //模擬棋盤
void putQueen(int ithQueen); //遞迴函式,每次擺好一個棋子

void main()
{
   int n, i, j;  
    for(i = 0; i < 8; i++){  // 初始化
	    for(j = 0; j < 8; j++)
	        board[i][j] = -1;
	    for(j = 0; j < 92; j++)
	        queenPlaces[j][i] = 0;
    }
    putQueen(0); //從第0個棋子開始擺放,執行的結果是將queenPlaces生成好
   scanf("%d", &n);
   for(i = 0; i < n; i++){
        int ith;
        scanf("%d", &ith);
        for(j = 0; j < 8; j++)
           printf("%d", queenPlaces[ith - 1][j]);
        printf("\n");
    }
}
void putQueen(int ithQueen){
     int i, k, r;
     if(ithQueen == 8){
	     count ++;
	     return;
     }
     for(i = 0; i < 8; i++){
         if(board[i][ithQueen] == -1){
			 //擺放皇后
             board[i][ithQueen] = ithQueen;
			 //將其後所有的擺放方法的第ith個皇后都放在i+1的位置上
			 //在i增加以後,後面的第ith個皇后擺放方法後覆蓋此時的設定
		     for(k = count; k < 92; k++)
		         queenPlaces[k][ithQueen] = i + 1;
			 //設定控制範圍
	         for(k = 0; k < 8; k++)
			 for(r = 0; r < 8; r++)
				 if(board[k][r] == -1 && 
					 (k == i || r == ithQueen || abs(k - i) == abs(r - ithQueen))) 
		             board[k][r] = ithQueen;
			 //向下級遞迴
             putQueen(ithQueen + 1);
             //回溯,撤銷控制範圍
             for(k = 0; k < 8; k++)
			 for(r = 0; r < 8; r++)
					 if(board[k][r] == ithQueen) board[k][r] = -1;
		 }
	 }
}


解題思路二

上面的方法用一個二維陣列來記錄棋盤被已經放置的棋子的控制情況,每次有新的棋子放置時用了列舉法來判斷它控制的範圍。還可以用三個一維陣列來分別記錄每一列,每個45度的斜線和每個135度的斜線上是否已經被已放置的棋子控制,這樣每次有新的棋子放置時,不必再搜尋它的控制範圍,可以直接通過三個一維陣列判斷它是否與已經放置的棋子衝突,在不衝突的情況下,也可以通過分別設定三個一維陣列的相應的值,來記錄新棋子的控制範圍。

參考程式二

#include <stdio.h>
int  record[92][9], mark[9], count = 0; //record記錄全部解,mark記錄當前解;
bool range[9], line1[17], line2[17]; //分別記錄列方向,45度,135度方向上被控制的情況
void tryToPut(int ); //求全部解的過程
void main()
{
	int i, testtimes, num;
	scanf("%d", &testtimes);
	
	for(i = 0; i <=8; i++)
		range[i] = true;
	for(i = 0; i < 17; i ++)
		line1[i] = line2[i] = true;

	tryToPut(1);

	while(testtimes --){
		scanf("%d", &num);
		for(i = 1; i <=8; i++)
			printf("%d", record[num - 1][i]);
		printf("\n");
	}
}

void tryToPut(int i){
    if(i > 8){ //如果最後一個皇后被放置完畢,將當前解複製到全部解中
	    for(int k = 1; k < 9; k ++) 
		    record[count][k] = mark[k];
	    count ++;
     }					
     for(int j=1; j<=8; j++){ 逐一嘗試將當前皇后放置在不同列上
	     if(range[j] && line1 [i + j] && line2[i - j + 9]){ //如果與前面的不衝突,
                                               //則把當前皇后放置在當前位置
		    mark[i] = j;
		    range[j] = line1[i + j] = line2[i - j + 9] = false;
            tryToPut(i + 1);
		    range[j] = line1[i + j] = line2[i - j + 9] = true;
	    }
    }
}


解題思路三

這個題目也可以不用模擬棋盤來模擬已放置棋子的控制區域,而只用一個有8個元素的陣列記錄已經擺放的棋子擺在什麼位置,當要放置一個新的棋子時,只需要判斷它與已經放置的棋子之間是否衝突就行了。

參考程式三

#include <stdio.h>
int ans[92][8], n, b, i, j, num, hang[8];
void queen(int i){
	int j, k;
	if(i == 8){ //一組新的解產生了
		for(j = 0; j < 8; j++)  ans[num][j] = hang[j] + 1;
		num++;
		return;
	}
	for (j=0; j<8; j++){ //將當前皇后i逐一嘗試放置在不同的列
        for(k=0; k<i; k++) //逐一判定i與前面的皇后是否衝突
            if( hang[k] == j || (k - i) == (hang[k] - j) || (i - k) == (hang[k] - j )) break;
		if (k == i) {  //放置i,嘗試第i+1個皇后
			hang[i] = j;
			queen(i + 1);
		}
	}
}
void main( ){
	num=0;
	queen(0);
	scanf(“%d”, &n);
	for(i = 0; i < n; i++){
		scanf(“%d”, &b);
		for(j = 0; j < 8; j++)  printf(“%d”, ans[b - 1][j]);
		printf(“\n”);
	}
}


實現中常見的問題

問題一: 使用列舉法,窮舉8個皇后的所有可能位置組合,逐一判斷是否可以互相被吃掉,得到超時錯誤;
問題二:對於多組輸入,有多組輸出,沒有在每組輸出後加換行符,得到格式錯;
問題三:對輸入輸出的函式不熟悉,試圖將數字轉換成字元或者將8個整數轉換成8位的十進位制整數來完成輸出,形成不必要的冗餘程式碼。

相關推薦

演算法總結——皇后問題解法

問題描述 會下國際象棋的人都很清楚:皇后可以在橫、豎、斜線上不限步數地吃掉其他棋子。如何將8個皇后放在棋盤上(有8 * 8個方格),使它們誰也不能被吃掉!這就是著名的八皇后問題。 對於某個滿足要求的8皇后的擺放方法,定義一個皇后串a與之對應,即a=b1b2...b8,其中b

LeetCode演算法題-Longest Palindrome解法

這是悅樂書的第220次更新,第232篇原創 01 看題和準備 今天介紹的是LeetCode演算法題中Easy級別的第87題(順位題號是409)。給定一個由小寫或大寫字母組成的字串,找到可以用這些字母構建的最長的迴文長度。這是區分大小寫的,例如“Aa”在這裡不被視為迴文。例如: 輸入:“abccccdd

【劍指offer】陣列中出現次數超過陣列長度一半的數字解法

題目描述 陣列中有一個數字出現的次數超過陣列長度的一半,請找出這個數字。例如輸入一個長度為9的陣列{1,2,3,2,2,2,5,4,2}。由於數字2在陣列中出現了5次,超過陣列長度的一半,因此輸出2。如果不存在則輸出0。 如果使用時間複雜度為O(n),可以構建

leetcode 合併k個排序列表解法

題目描述 合併 k 個排序連結串列,返回合併後的排序連結串列。請分析和描述演算法的複雜度。 解法1 建立一個數組,將所有元素儲存,排序之後,再連結成一個連結串列 class Solution: def mergeKLists(self, lists):

LeetCode演算法題-First Bad VersionJava實現-解法

這是悅樂書的第200次更新,第210篇原創 01 看題和準備 今天介紹的是LeetCode演算法題中Easy級別的第66題(順位題號是278)。您是產品經理,目前領導團隊開發新產品。不幸的是,您產品的最新版本未通過質量檢查。由於每個版本都是基於以前的版本開發的,因此壞版本之後的所有版本也是壞的。 假設

LeetCode演算法題-Move ZeroesJava實現-解法

這是悅樂書的第201次更新,第211篇原創 01 看題和準備 今天介紹的是LeetCode演算法題中Easy級別的第67題(順位題號是283)。給定一個數組nums,寫一個函式將所有0移動到它的末尾,同時保持非零元素的相對順序。例如: 輸入:[0,1,0,3,12] 輸出:[1,3,12,0,0]

LeetCode 923. 數之和的多種可能機智題 解法

題意: 給定一個整數陣列 A,以及一個整數 target 作為目標值,返回滿足 i < j < k 且 A[i] + A[j] + A[k] == target 的元組 i, j, k 的數量。

K-means演算法方法

 客戶分類: 1、將客戶分為三類:超級VIP、vip、普通使用者 2、需要你將不同的類的資料,在圖上顯示出來,用不同的顏色 3、返回三個類中,各包含哪些點 import matplotlib.pyplot as plt import numpy as np

經典演算法之希爾排序實現

希爾排序的實質就是分組插入排序,該方法又稱縮小增量排序,因DL.Shell於1959年提出而得名。 該方法的基本思想是:先將整個待排元素序列分割成若干個子序列(由相隔某個“增量”的元素組成的)分別進行直接插入排序,然後依次縮減增量再進行排序,待整個序列中的元素

QML中文件的加載方法

引入 img 方法 version clas 如同 java images cap 在這裏小小總結一下QML文件中如何加載QML文件與JavaScript文件。 1、QML文件中加載JavaScript文件 語法: import <ModuleIdentifie

uva 11572 - Unique Snowflakes解法

 https://vjudge.net/problem/UVA-11572 題意:給出 n個數,找到儘量長的一個序列,使得該序列中沒有重複的元素 思路:對於該類段查詢問題可以採用經典的滑動視窗方法,即維護一個視窗,視窗的左右邊界用兩個變數L,R代表,先增加R直到出現重複數字,再增加

第五第六課------遞推+記憶話搜尋+搜尋皇后思想是做夢+各種剪枝思想

搜尋是一個漫長的過程貫徹整個oi; 八皇后------- #include <bits/stdc++.h> #define inf 0x7f using namespace std; int n,ans,a[inf],b[inf],c[inf],d[inf]; void print

vue-router如何傳遞引數方法

1.使用name傳遞 之前一直在配置路由的時候出現一個name,但不知道他具體有什麼用,在路由裡他可以用來傳遞引數。在index.js中將路由的name都寫好 接收引數: 在我們需要接收它的頁

day039mysql多表查詢方法及備份,Navicat工具,pymysql的使用

本節內容: 1、MySQL之多表查詢 2、Navicat工具 3、mysql資料庫備份 4、pymysql模組 參考文章1參考文章2 一、MySQL之多表查詢

Linux下安裝與解除安裝工具方法

三種方法為:rpm工具、yum工具、原始碼包 rpm工具(操作繁瑣) 光碟機掛載到mnt:mount /dev/cdrom /mnt/centos安裝包中就有rpm包 Packages rpm -ivh rpm包檔案 //安裝rpm -Uvh rpm包檔案 //升級rpm -e 包名 //解除安裝(包名

PAT (Basic Level) Practice 1003 我要通過!解法

乙級1003 這題和HOJ3788一樣,故把那邊的sample也拿過來: 題意好理解,就對條件3解釋一下: 如果 aPbTc 是正確的,那麼 aPbATca 也是正確的,其中 a、 b、 c 均或者是空字串,或者是僅由字母 A 組成的字串。 就相當於是先對aPbATca進行操作成aPb

Linux下安裝與卸載工具方法

ado 路徑 sha ins nag 查詢 linu ext 光驅掛載 三種方法為:rpm工具、yum工具、源碼包 rpm工具(操作繁瑣) 光驅掛載到mnt:mount /dev/cdrom /mnt/centos安裝包中就有rpm包 Packages rpm -ivh

個執行緒輪流執行順序列印ABC,依次是ABCABCABC......方式

1.使用synchronized悲觀鎖 (秋招阿里的一個筆試題,應該寫的比較複雜,然後就沒有然後了o(╥﹏╥)o) public class ThreadThreadp { private int flag = 0; public synch

【C語言】輸入一個整數,輸出該數二進位制表示中1的個數方法

輸入一個整數,輸出該數二進位制表示中1的個數。如輸入32,輸出1.程式碼實現:方法1:與運算#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; int FindOne

《程式設計師面試金典》--尋找二叉樹中兩個節點的第一個公共祖先情況

/**************************************************************************************************