n皇后問題是經典的回溯解題的案例,回溯通常用在有多個解的算法中,回溯的核心是窮舉,通常經過必要的減枝提升效率(減小重複計算等),獲得一個解後,把當前解進行保存,而後將當前解標記爲未解決,繼續嘗試下一個可能知足條件的解,即回溯ios
窮舉解有利於理解問題的本質,回溯解提升解題效率算法
題目參考:leetcode-cn.com/problems/n-… ui
皇后的個數和行數相等spa
每行一定有且只有一個皇后, 若是用row1, row2表示兩個皇后所處的行,一定存在row1 != row2code
兩個皇后一定存在不在同一條斜線上, [row1, col1], [row2, col2],cdn
一定存在 | row1 - row2 | != | col1 -col2 |blog
或者 左右對角線分開判斷 左對角線 row1 - col1 == row2 - col2 右對角線 row1 + col1 = row2 + col2索引
兩個皇后一定不在同一列上,一定存在col1 != col2ip
要找到全部皇后的集合,一定在找到知足的一個皇后後,不能停,須要繼續查找下一個皇后leetcode
知足以上條件,便可求得n皇后的解
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
#define QUEUE_NUM 8
int usedRowAndCol[QUEUE_NUM + 1] = {0}; // 已經使用的行和列, 0表示未使用,有值表示列值, 鍵是行 值是列
vector< vector<string> > result; // 存儲n皇后全部可能結果
void findAQueue(int);
void recordAnswer();
void printAnswer();
void findAQueue(int row) {
// row這行確定是能夠用的,主要是獲取可用的列,列也有8列,因此進行窮舉, 判斷row行的col列是否可用
for (int col = 1; col <= QUEUE_NUM; col ++) {
// 檢查列是否可用 1. 兩個皇后一定不在同一列上,一定存在col1 != col2
// 2. 兩個皇后一定存在不在同一條斜線上, [row1, col1], [row2, col2],一定存在 | row1 - col1 | != | row2 -col2 |
// 判斷當前行的,當前col列在不在對角線上已經佔用了
bool canPlaceInThisCol = true;
for (int currentRow = 1; currentRow < row; currentRow ++) {
// currentRow行的col列用過了
if (usedRowAndCol[currentRow] == col) {
canPlaceInThisCol = false;
break;
}
// 左對角線有用過了
if (currentRow - usedRowAndCol[currentRow] == row - col) {
canPlaceInThisCol = false;
break;
}
// 右對角線是否用過了
if (currentRow + usedRowAndCol[currentRow] == row + col) {
canPlaceInThisCol = false;
break;
}
}
if (canPlaceInThisCol) {
// 列可用,進行記錄
usedRowAndCol[row] = col;
// 找下一行的那列可使用
if (row < QUEUE_NUM) {
findAQueue(row + 1);
}
if (row == QUEUE_NUM) { // 若是當前是最後一行了,就不用再繼續找了,記錄答案
recordAnswer();
}
}
}
}
void recordAnswer() {
vector<string> oneResult;
for (int i = 1; i <= QUEUE_NUM; i ++) {
string row(QUEUE_NUM, '.');
row[usedRowAndCol[i] - 1] = 'Q';
oneResult.push_back(row);
}
result.push_back(oneResult);
}
void printAnswer() {
for (int i = 0; i < result.size(); i ++) {
for (int j = 0; j < result[i].size(); j ++) {
cout << result[i][j] << endl;
}
cout << "---------sep----------" << endl;
}
}
int main(int argc, char *argv[]) {
findAQueue(1);
cout << result.size() << endl;
}
複製代碼
這裏爲了提升代碼的可讀性,使用了分別排除左對角線和右對角線的方法排除皇后,也能夠直接使用絕對值方法
把代碼稍做修改填到leetcode上便可
排列出全部的組合,挨個判斷每一個組合是否知足n皇后的條件
#include <iostream>
#include <vector>
using namespace std;
#define QUEUE_NUM 4
int QUEUE_LIST[QUEUE_NUM];
vector< vector<string> > result; // 存儲n皇后全部可能結果
bool is_ok(int row) {
if (row == QUEUE_NUM - 1) {
return true;
}
int first = QUEUE_LIST[row];
bool ok = true;
for (int currentRow = row + 1; currentRow < QUEUE_NUM; currentRow ++) {
// 不能在同一列
if (QUEUE_LIST[currentRow] == first) {
ok = false;
break;
}
// 左對角線有用過了
if (currentRow - QUEUE_LIST[currentRow] == row - first) {
ok = false;
break;
}
// 右對角線是否用過了
if (currentRow + QUEUE_LIST[currentRow] == row + first) {
ok = false;
break;
}
}
// 檢查 row + 1行和 row + 2 -> QUEUE_NUM行是否知足條件
if (ok) {
ok = is_ok(row + 1);
}
return ok;
}
void recordAnswer() {
vector<string> oneResult;
for (int i = 0; i < QUEUE_NUM; i ++) {
string row(QUEUE_NUM, '.');
row[QUEUE_LIST[i]] = 'Q';
oneResult.push_back(row);
}
result.push_back(oneResult);
}
void printAnswer() {
for (int i = 0; i < result.size(); i ++) {
for (int j = 0; j < result[i].size(); j ++) {
cout << result[i][j] << endl;
}
cout << "---------sep----------" << endl;
}
}
void queue(int row) {
for (int i = 0; i < QUEUE_NUM; i ++) {
QUEUE_LIST[row] = i;
if (row == QUEUE_NUM - 1) {
// 找到一個組合,進行判斷是否知足8皇后定義
if (is_ok(0)) {
// 進行記錄
recordAnswer();
}
continue;
}
queue(row + 1);
}
}
int main(int argc, char *argv[]) {
queue(0);
printAnswer();
}
複製代碼
因爲窮舉解多出了不少無效的組合,lc上直接超時了...,能夠在本地計算機上自行驗證
暴力解是先求出全部的皇后的組合,而後逐個判斷皇后是否知足位置條件
回溯解就是在求皇后組合是否知足條件時,提早進行判斷,提早剪掉了這部分皇后的組合
回溯法的col, row值從1開始是爲了不usedRowAndCol默認值的0和col, row索引的0衝突,致使的判斷異常