超过1000皇后问题的快速启发式算法

5

我写了两个程序:

  1. 使用回溯算法将n个皇后放置在棋盘上,使它们互相之间没有威胁。但是对于大的n值,这个程序非常耗时。最后,你可以运行100个皇后。
  2. 使用爬山算法将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;
}

2
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Jason L
你能给我解释一下函数 generateBoard 是如何工作的吗? - Giuseppe
1个回答

10

注意:本答案假设您希望找到一个有效的解决方案。如果您需要找到所有解决方案,则此方法无法帮助您。

人工智能:一种现代方法,Russell&Norvig著,第二版第143页有一张表,比较了各种约束满足问题算法的不同任务。(最新版本为第三版,看起来约束满足问题现在是第6章。)

根据他们的结果,在n皇后问题上测试的算法中,最小冲突局部搜索启发式得分最高,平均需要4K次检查,而回溯和前向检查则需要超过40,000K次检查。

该算法非常简单:

  • 选择皇后的初始(随机或预选)分配
  • 当存在威胁的皇后时(或者直到您厌倦尝试...值得将其放入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) - Ben Voigt
@BenVoigt 看起来是这样的...如果你将问题规模加倍,运行时间会增加约8倍。至少在这个有限的数据集上是这样的。 - beaker
我尝试了你建议的方法,确实非常好用。但是在我的实现中,如果你随机选择初始配置,它往往会陷入局部最小值。为了克服这个问题,在冲突列中重新随机放置皇后,然后再次尝试,如果这样做没有帮助,那么就尝试另一个随机配置。你遇到过这个问题(局部最小值)吗?如果有,你是如何解决的? - John Donn
@JohnDonn 我记得我遇到的问题是在两个配置之间摆动。我想我所做的是强制不同的皇后在每一步中移动,假设多个皇后有相同数量的冲突,或确保具有最大冲突的单个皇后不会移动到其当前或先前的位置。在实践中,更容易在重试之前限制给定起始配置的移动次数。这个算法非常有趣的一点是所需的移动次数似乎收敛。作者声称1M皇后的平均移动次数仅为50次。 - beaker
1
@MattG 是的,初始配置的质量将决定所需交换的次数。我从几年前的最新实现中提取了数据,对于10,000个皇后,将皇后分配到随机行(每个皇后在其自己的列中)平均需要6224次交换才能达到解决方案。使用行号的排列只需要4703次交换。将皇后分配到具有最小冲突数量的行仅需要48次交换。 - beaker
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接