n 个皇后 (n > 1000) 的快速启发式算法

Posted

技术标签:

【中文标题】n 个皇后 (n > 1000) 的快速启发式算法【英文标题】:fast heuristic algorithm for n queens (n > 1000) 【发布时间】:2014-12-30 00:17:54 【问题描述】:

我写了两个程序:

    将 n 个皇后放在棋盘中,不会受到回溯算法的威胁。但这对于 big n 来说非常沉重。最后你可以运行 100 个皇后。 将 n 个皇后放在棋盘中,而不会受到爬山算法的威胁。该算法比过去的解决方案更好,但 300 个皇后需要 2 分钟,而这一次呈指数增长!

但我没想到这么快!我想要更快的算法。

我希望以更快的方式尽快解决 1000 个皇后的问题。

这是我的爬山代码:

// N queen - Reset Repair Hill Climbing.cpp
// open-mind.ir

#include "stdafx.h"
#include <vector>
#include <iostream>
#include <fstream>
#include <time.h>
#include <iomanip>


using namespace std;

//print solution in console
void printBoardinTerminal(int *board, int len)

    for (int i = 0; i < len; i++)
    
        for (int j = 0; j < len; j++)
        
            if (j == board[i])
            
                cout << 1 << " ";
            
            else
            
                cout << 0 << " ";
            
        
        cout << endl;
    


//print solution in File
void printBoardinFile(int *board, int len)

    ofstream fp("output.txt", ios::out);

    fp << "Answer for " << len << " queen: \n \n";

    for (int i = 0; i < len; i++)
    
        for (int j = 0; j < len; j++)
        
            fp << "----";
        
        fp << "\n|";

        for (int j = 0; j < len; j++)
        
            if (j == board[i])
            
                fp << setw(4) << "* |" ;
            
            else
            
                fp << setw(4) << "  |";
            
        
        fp << "\n";
    


//The number of queens couples who are threatened themself
int evaluate(int *board, int len)

    int score = 0;
    for (int i = 0; i < len - 1; i++)
    
        for (int j = i + 1; j < len; j++)
        
            if (board[i] == board[j])
            
                score++;
                continue;
            
            if (board[i] - board[j] == i - j)
            
                score++;
                continue;
            
            if (board[i] - board[j] ==  j - i)
            
                score++;
                continue;
            
        
    
    return score;


//generate new state from current state 
int* generateBoard(int *board,int len)

    vector <int> choice;

    int temp;
    int score;
    int eval = evaluate(board, len);
    int k;

    int *boardOut;
    boardOut = new int [len];


    for (int i = 0; i < len; i++)
    
            boardOut[i] = board[i];
    

    for (int i = 0; i < len; i++)
    
        choice.clear();

        choice.push_back(boardOut[i]);
        temp = boardOut[i];

        for (int j = 0; j < len; j++)
        
            boardOut[i] = j;

            k = evaluate(boardOut, len);

            if (k == eval)
            
                choice.push_back(j);
            

            if (k < eval)
            
                choice.clear();
                choice.push_back(j);
                eval = k;
            
        
        boardOut[i] = choice[rand() % choice.size()];
    

    return boardOut;


//in this function , genarate new state by pervious function and if it has better value then replaces that by current state
bool findNextState(int *board, int len)

    int maineval = evaluate(board, len);

    int *tempBoard;

    tempBoard = generateBoard(board, len);

    if (evaluate(tempBoard, len) < maineval)
    
        for (int p = 0; p < len; p++)
        
            board[p] = tempBoard[p];
        

        return  true;
    

    return false;


// make random initial state , put one queen in each row
void initialRandomBoard(int * board, int len)

    bool access;
    int col;

    for (int i = 0; i < len; i++)
    
        board[i] = rand() % len;
    


//this function include a loop that call findNextState function , and do that until reach solution
//if findNextState function return NULL then we reset current state
void SolveNQueen(int len)

    cout << "The program is under process! wait!" << endl;

    int *board;
    board = new int[len];


    initialRandomBoard(board, len);

    while (evaluate(board, len) != 0)
    
        if (!findNextState(board, len))
        
            initialRandomBoard(board, len);
        
    


    //
    cout << endl << "Anwser for " << len << " queens: "<< endl << endl;
    printBoardinTerminal(board, len);
    printBoardinFile(board, len);
    //



int main()

    int n;
    srand(time(NULL));

    cout << "Enter  number \'N\', \'N\' indicate numbers of queens in \"N * N\" chess board: " << endl;
    cin >> n;

    if (n < 4)
    
        cout << "\'n\' must be uper than 3!" << endl;
        exit(1);
    

    SolveNQueen(n);

    cout << endl << "As well , you can see result in \"output.txt\"." << endl << endl;

    return 0;

【问题讨论】:

en.wikipedia.org/wiki/Eight_queens_puzzle 如果您只是在寻求一个解决方案(不计算解决方案的数量),那么似乎有一个放置皇后的公式。请参阅显式解决方案部分。 你能解释一下 generateBoard 函数是如何工作的吗? 【参考方案1】:

注意:此答案假设您有兴趣找到一个有效的解决方案。如果您需要找到所有解决方案,这对您没有帮助。

Artificial Intelligence: A Modern Approach,Russell & Norvig 的第二版在第 5 章:第 143 页的约束满足问题中有一个表格,比较了各种任务的各种约束满足问题算法。 (最新的版本是第三版,现在好像是Constraint Satisfaction Problems是第6章了。)

根据他们的结果,最小冲突本地搜索启发式在 n-Queens 问题上测试的算法中得分最高,与回溯和回溯和超过 40,000K 次检查相比,平均需要 4K 次检查前向检查。

算法很简单:

选择初始(随机或预选)皇后分配 当有威​​胁的皇后时(或者直到你厌倦了尝试......值得将它放在for循环中以限制尝试次数): 随机选择一个受威胁的女王 将选中的皇后移动到最小化冲突的方格

在最后一步中,我假设每个皇后都被限制在她的列中,所以她只能更改列中的行。如果有几行可以最小化当前女王的冲突,您可以在其中随机选择。

就是这样。它是完全随机的,而且效果很好。

编辑:

我在这里有一个注释,说我不记得我在实现这个算法时得到了多高n,说我知道我已经得到了超过 100。我没有找到我的旧代码,但我还是决定把东西放在一起。事实证明,这种方法比我记忆中的要有效得多。以下是 10 个皇后的结果:

Starting Configuration:
14  0  2  13  12  17  10  14  14  2  9  8  11  10  6  16  0  7  10  8  
Solution found
Ending Configuration:
17  2  6  12  19  5  0  14  16  7  9  3  1  15  11  18  4  13  8  10  
Elapsed time (sec): 0.00167
Number of moves: 227

没有尝试优化代码,以下是我针对不同问题大小得到的大致时间:

Queens      ~Time(sec)
======      ==========
  100           0.03
  200           0.12
  500           1.42
 1000           9.76
 2000          72.32
 5000        1062.39

我只为 5000 个皇后运行了最后一个,但在 18 分钟内找到解决方案比我预期的要快。

【讨论】:

看起来大致是O(N^3) ? @BenVoigt 这就是它的样子......如果你将问题规模加倍,你会运行大约 8 倍的时间。至少在这个有限的数据集上。 我试过你建议的方法;它确实工作得很好,但是在我的实现中,如果您随机选择初始配置,它往往会陷入局部最小值。在某种程度上,我克服这个问题的方法是在它们所属的列中随机重新定位冲突的皇后,然后再试一次,如果这没有帮助,那么尝试另一个随机配置。你遇到过这个问题(局部最小值)吗?如果是,你是怎么克服的? @JohnDonn 我记得,我发现的问题是在两种配置之间摇摆不定。我认为我所做的是在每一步强制移动不同的皇后,假设多个皇后有相同数量的冲突,或者确保具有最大冲突的单个皇后没有移动到当前或以前的位置。实际上,在重试之前限制给定起始配置的移动次数更容易。这个算法的一个非常有趣的事情是所需的移动次数似乎收敛。作者声称 100 万皇后平均只有 50 步。 @MattG 是的,初始配置的质量将决定所需的交换次数。我从几年前取出了我最新的实现,对于 10,000 个皇后,将皇后分配给随机行(每行在自己的列中)平均需要 6224 次交换才能达到解决方案。使用行号排列进行了 4703 次交换。将皇后分配给冲突最少的行只需要 48 次交换。

以上是关于n 个皇后 (n > 1000) 的快速启发式算法的主要内容,如果未能解决你的问题,请参考以下文章

N皇后问题

搜索还是N皇后

回溯:八皇后问题(蒟蒻)

N皇后问题(21.10.12)

八皇后问题

为啥这个解决方案对 n 个皇后的复杂性如此之大?