传统解法 - 深度优先搜索(DFS):
#include <iostream>
#include <cstdio>
using namespace std;
#define ll long long
const int maxn = 200005;
const int INF = 0x3f3f3f;
int n,a[50],ans = 0;
bool vis[3][50];
int cnt = 0;
void dfs(int i) {
++cnt;
if(i == n+1) {
++ans;
for(int _i = 1; _i <= n; ++_i){
cout << "(" << _i << "," << a[_i] << ") ";
}
cout << endl;
return;
}
for(int j = 1; j <= n; ++j)
if(!vis[0][j] && !vis[1][j+i] && !vis[2][j-i+n]) {
a[i] = j;
vis[0][j] = 1; //列
vis[1][j+i] = 1;
vis[2][j-i+n] = 1;
dfs(i+1);
vis[0][j] = 0; //列
vis[1][j+i] = 0;
vis[2][j-i+n] = 0;
}
return;
}
int main() {
cout << "输入n: "; //输入n皇后
cin >> n;
dfs(1);
cout << "共有" << ans << "种解法" << endl;
cout << "基本操作执行次数:" << cnt << endl;
return 0;
}
输出:
下山法(HILL-CLIMBING):
#include <iostream>
#include <time.h>
#include <stdlib.h>
#include <algorithm>
#include <cmath>
using namespace std;
int queens[8][8]; // 8*8棋盘
int temp[8][8];
int totalTrial; // 统计移动步数
// 随机生成初始状态
void initial() { //皇后不允许跨行移动
for (int i = 0; i < 8; ++i) {
for (int j = 0; j < 8; j++) {
queens[i][j] = 0;
}
}
for (int i = 0; i < 8; i++) {
int num = rand() % 8;
queens[i][num] = 1;
}
}
void _print() {
for (int i = 0; i < 8; ++i) {
for (int j = 0; j < 8; j++)
cout << queens[i][j] << " ";
cout << endl;
}
}
// 统计在该位置下所有皇后的冲突个数
int findCollision(int row, int col) {
int count = 0;
// 该位置为1
temp[row][col] = 1;
for (int k = 0; k < 64; k++) {
if (temp[k/8][k%8] == 1) {
for (int i = 0; i < 8; i++) // 同一列
if (i != k/8 && temp[i][k%8] == 1)
count++;
for (int i = k/8, j = k%8; i < 8 && j < 8; i++, j++) // 右下方
if (i != k/8 && temp[i][j] == 1)
count++;
for (int i = k/8, j = k%8; i >= 0 && j >= 0; i--, j--) // 左上方
if (i != k/8 && temp[i][j] == 1)
count++;
for (int i = k/8, j = k%8; i < 8 && j >= 0; i++, j--) // 左下方
if (i != k/8 && temp[i][j] == 1)
count++;
for (int i = k/8, j = k%8; i >= 0 && j < 8; i--, j++) // 右上方
if (i != k/8 && temp[i][j] == 1)
count++;
}
}
temp[row][col] = 0; // 复原位置
return count/2;
}
bool check(int h[8][8]) {
for (int i = 0; i < 8; i++) {
bool flag = false;
for (int j = 0; j < 8; j++) {
if (queens[i][j] == 1 && h[i][j] == 0) { //皇后所在位置没有冲突
flag = true;
break;
}
}
if (!flag) { // 皇后所在位置仍有冲突,还需要继续查找
return false;
}
}
return true;
}
// 爬山法
bool hillClimbing(bool b) {
// 尝试次数大于100则判定为无解
for (int trial = 0; trial <= 100; trial++) {
if(b) {cout << "trial:" << trial << endl; _print(); cout << endl;}
// 拷贝原始棋盘数据到temp
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
temp[i][j] = queens[i][j];
}
}
int h[8][8];
int minH = 9999; //代价函数h(x)的最小取值
int minX = 0, minY = 0;
int curState = 0; //当前状态的代价
bool f_curState = false;
for (int i = 0; i < 8; i++) { // 8 * 7 共56种选择
for (int j = 0; j < 8; j++) {
if(queens[i][j] == 1 && f_curState) {
h[i][j] = f_curState;
continue;
}
// 在计算h(i, j)之前,对i行所有位置赋值为0,因为皇后不能换行移动, 所以皇后只有可能出现在其所在行某一位置
for (int k = 0; k < 8; k++)
temp[i][k] = 0;
// 查找h(i, j)
h[i][j] = findCollision(i, j);
// 当前状态的h值
if (queens[i][j] == 1) {
curState = h[i][j];
f_curState = true;
}
// 先找出冲突个数最小的位置
if (h[i][j] < minH) {
minH = h[i][j];
minX = i;
minY = j;
}
// 计算h(i,j)之后要复原数据,避免计算错误
for (int k = 0; k < 8; k++)
temp[i][k] = queens[i][k];
}
}
// 将皇后放在该行冲突最少的位置处
if (curState > minH) {
for (int i = 0; i < 8; i++)
queens[minX][i] = 0;
queens[minX][minY] = 1;
}
// 判断是否找到解, 有解则返回值为真
if (check(h)) {
totalTrial += trial;
return true;
}
}
return false;
}
int HILL_CLIMBING() {
int count = 0;
for (int i = 0; i < 1000; i++) {
initial();
if (hillClimbing(0))
count++;
}
return count;
}
int main() {
srand((int)time(0));
initial();
cout << "HILL-CLIMBING searching..." << endl;
// totalTrial = 0;
// int count = HILL_CLIMBING();
// cout << "HILL-CLIMBING[success/total]: " << count << "/1000" << endl;
// cout << "average steps: " << totalTrial/count << endl;
if(hillClimbing(1)) {
cout << "Find answer!" << endl;
}
else cout << "Unsuccessful!" << endl;
}
首选爬山法:
#include <iostream>
#include <time.h>
#include <stdlib.h>
#include <algorithm>
#include <cmath>
using namespace std;
int queens[8][8]; // 8*8棋盘
int temp[8][8];
int totalTrial; // 统计移动步数
// 随机生成初始状态
void initial() { //皇后不允许跨行移动
for (int i = 0; i < 8; ++i) {
for (int j = 0; j < 8; j++) {
queens[i][j] = 0;
}
}
for (int i = 0; i < 8; i++) {
int num = rand() % 8;
queens[i][num] = 1;
}
}
void _print() {
for (int i = 0; i < 8; ++i) {
for (int j = 0; j < 8; j++)
cout << queens[i][j] << " ";
cout << endl;
}
}
// 统计在该位置下所有皇后的冲突个数
int findCollision(int row, int col) {
int count = 0;
// 该位置为1
temp[row][col] = 1;
for (int k = 0; k < 64; k++) {
if (temp[k/8][k%8] == 1) {
for (int i = 0; i < 8; i++) // 同一列
if (i != k/8 && temp[i][k%8] == 1)
count++;
for (int i = k/8, j = k%8; i < 8 && j < 8; i++, j++) // 右下方
if (i != k/8 && temp[i][j] == 1)
count++;
for (int i = k/8, j = k%8; i >= 0 && j >= 0; i--, j--) // 左上方
if (i != k/8 && temp[i][j] == 1)
count++;
for (int i = k/8, j = k%8; i < 8 && j >= 0; i++, j--) // 左下方
if (i != k/8 && temp[i][j] == 1)
count++;
for (int i = k/8, j = k%8; i >= 0 && j < 8; i--, j++) // 右上方
if (i != k/8 && temp[i][j] == 1)
count++;
}
}
temp[row][col] = 0; // 复原位置
return count/2;
}
bool check(int h[8][8]) {
for (int i = 0; i < 8; i++) {
bool flag = false;
for (int j = 0; j < 8; j++) {
if (queens[i][j] == 1 && h[i][j] == 0) { //皇后所在位置没有冲突
flag = true;
break;
}
}
if (!flag) { // 皇后所在位置仍有冲突,还需要继续查找
return false;
}
}
return true;
}
// 首选爬山法
bool firstchose(bool b) {
// 尝试次数大于100则判定为无解
for (int trial = 0; trial <= 100; trial++) {
if(b) {cout << "trial:" << trial << endl; _print(); cout << endl;}
// 拷贝原始棋盘数据到temp
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
temp[i][j] = queens[i][j];
}
}
int h[8][8], curState;
bool f_curState = false;
for (int i = 0; i < 8; i++) { // 8 * 7 共56种选择
for (int j = 0; j < 8; j++) {
if(queens[i][j] == 1 && f_curState) {
h[i][j] = f_curState;
continue;
}
// 在计算h(i, j)之前,对i行所有位置赋值为0
for (int k = 0; k < 8; k++)
temp[i][k] = 0;
// 查找h(i, j)
h[i][j] = findCollision(i, j);
// 当前状态的h值
if (queens[i][j] == 1) {
curState = h[i][j];
f_curState = false;
}
// 计算h(i,j)之后要复原数据,避免计算错误
for (int k = 0; k < 8; k++)
temp[i][k] = queens[i][k];
}
}
// 随机选取第一个优于当前状态的下一状态
bool better = false;
int next, nextState, times = 0;
while (!better) {
next = rand() % 64;
nextState = h[next/8][next%8];
if (nextState < curState) {
better = true;
}
if (++times > 100) break;
}
if (better) {
for (int i = 0; i < 8; i++)
queens[next/8][i] = 0;
queens[next/8][next%8] = 1; // 放置皇后
}
// 判断是否找到解, 有解则返回值为真
if (check(h)) {
totalTrial += trial;
return true;
}
}
return false;
}
// 首选爬山法
int firstChose() {
int count = 0;
for (int i = 0; i < 1000; i++) {
initial();
if (firstchose(0))
count++;
}
return count;
}
int main() {
srand((int)time(0));
initial();
cout << "FIRT-CHOSE searching..." << endl;
// totalTrial = 0;
// int count = firstChose();
// cout << "FIRT-CHOSE[success/total]: " << count << "/1000" << endl;
// cout << "average steps: " << totalTrial/count << endl;
if(firstchose(1)) {
cout << "Find answer!" << endl;
}
else cout << "Unsuccessful!" << endl;
}
随机重启爬山法(random restart hill climbing):
字面意思:就是不停调用爬山法,直到找到为止,因此就不贴完整代码了。
// 随机重新开始爬山法
int randomRestart() {![在这里插入图片描述](https://img-blog.csdnimg.cn/20210413232437811.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTkxOTk4NQ==,size_16,color_FFFFFF,t_70#pic_center)
bool find = false;
while (!find) {
initial();
find = hillClimbing();
}
return find;
}
模拟退火搜索(SIMULATED-ANNEALING):
久闻模拟退火搜索大名,终于碰到了
#include <iostream>
#include <time.h>
#include <stdlib.h>
#include <algorithm>
#include <cmath>
using namespace std;
int queens[8][8]; // 8*8棋盘
int temp[8][8];
int totalTrial; // 统计移动步数
// 随机生成初始状态
void initial() { //皇后不允许跨行移动
for (int i = 0; i < 8; ++i) {
for (int j = 0; j < 8; j++) {
queens[i][j] = 0;
}
}
for (int i = 0; i < 8; i++) {
int num = rand() % 8;
queens[i][num] = 1;
}
}
void print() {
for (int i = 0; i < 8; ++i) {
for (int j = 0; j < 8; j++)
cout << queens[i][j] << " ";
cout << endl;
}
}
// 统计在该位置下所有皇后的冲突个数
int findCollision(int row, int col) {
int count = 0;
// 该位置为1
temp[row][col] = 1;
for (int k = 0; k < 64; k++) {
if (temp[k/8][k%8] == 1) {
for (int i = 0; i < 8; i++) // 同一列
if (i != k/8 && temp[i][k%8] == 1)
count++;
for (int i = k/8, j = k%8; i < 8 && j < 8; i++, j++) // 右下方
if (i != k/8 && temp[i][j] == 1)
count++;
for (int i = k/8, j = k%8; i >= 0 && j >= 0; i--, j--) // 左上方
if (i != k/8 && temp[i][j] == 1)
count++;
for (int i = k/8, j = k%8; i < 8 && j >= 0; i++, j--) // 左下方
if (i != k/8 && temp[i][j] == 1)
count++;
for (int i = k/8, j = k%8; i >= 0 && j < 8; i--, j++) // 右上方
if (i != k/8 && temp[i][j] == 1)
count++;
}
}
temp[row][col] = 0; // 复原位置
return count/2;
}
bool check(int h[8][8]) {
for (int i = 0; i < 8; i++) {
bool flag = false;
for (int j = 0; j < 8; j++) {
if (queens[i][j] == 1 && h[i][j] == 0) { //皇后所在位置没有冲突
flag = true;
break;
}
}
if (!flag) { // 皇后所在位置仍有冲突,还需要继续查找
return false;
}
}
return true;
}
// 模拟退火搜索
bool simulated() {
double temperature = 5; //初始温度
int trial = 0;
while (temperature > 0.00001) {
// 拷贝原始棋盘数据到temp
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
temp[i][j] = queens[i][j];
}
}
int h[8][8], curState;
bool f_curState = false;
for (int i = 0; i < 8; i++) { // 8 * 7 共56种选择
for (int j = 0; j < 8; j++) {
if(queens[i][j] == 1 && f_curState) {
h[i][j] = f_curState;
continue;
}
// 在计算h(i, j)之前,对i行所有位置赋值为0
for (int k = 0; k < 8; k++)
temp[i][k] = 0;
// 查找h(i, j)
h[i][j] = findCollision(i, j);
// 当前状态的h值
if (queens[i][j] == 1) {
curState = h[i][j];
f_curState = false;
}
// 计算h(i,j)之后要复原数据,避免计算错误
for (int k = 0; k < 8; k++)
temp[i][k] = queens[i][k];
}
}
// 随机选取一个下一状态
bool better = false;
int next, nextState, times = 0;
next = rand() % 64;
nextState = h[next/8][next%8];
int E = nextState - curState;
if (E < 0) { //下一状态代价更小, 爬山
better = true;
}
else if (exp((-1)*E/temperature) > ((double)(rand() % 1000) / 1000)) { //e^E/T 随温度降低越来越小->慢慢降低摇晃的强度
//((double)(rand() % 1000) / 1000) 生成<1的随机小数
better = true;
}
if (better) {
for (int i = 0; i < 8; i++)
queens[next/8][i] = 0;
queens[next/8][next%8] = 1; // 放置皇后
trial++;
}
// 判断是否找到解, 有解则返回值为真
if (check(h)) {
totalTrial += trial;
return true;
}
temperature *= 0.99; //降温
}
return false;
}
// 模拟退火搜索
int simulatedAnnealing() {
int count = 0;
for (int i = 0; i < 1000; i++) {
initial();
if (simulated())
count++;
}
return count;
}
int main(int argc, char const *argv[]) {
srand((int)time(0));
totalTrial = 0;
cout << "simulated-Annealing searching..." << endl;
int count = simulatedAnnealing();
cout << "simulated-Annealing[success/total]: " << count << "/1000" << endl;
cout << "average steps: " << totalTrial/count << endl;
return 0;
}
可以看到找到退火控制在每次下降1%还是会有部分解跳了出去, 退火的原因时间明显增长, 但是在更精确的退货时间控制的情况下,算法找多全局最优解的概率接近于1
退火速度 * 99%
退火速度 * 99.5%
可以看到随着退火速度变慢,算法寻找最优解的概率显著增大