4.1 八皇后问题 - 传统dfs,爬山法,首选爬山法,模拟退火 --- 实现代码附详细注释

本文对比了传统的深度优先搜索(DFS)算法与随机重启爬山法(randomRestart)、模拟退火搜索(simulated Annealing)在解决八皇后问题中的效率和全局最优解的寻优过程。通过实例代码展示了这些算法的不同策略,以及它们在复杂性与搜索效率上的差异。
摘要由CSDN通过智能技术生成

传统解法 - 深度优先搜索(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%
在这里插入图片描述
可以看到随着退火速度变慢,算法寻找最优解的概率显著增大

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值