5

I write two program :

  1. put together n queens in chess board without any threatening by backtracking algorithm. but that is very heavy for big n . at last you can run that for 100 queens.
  2. put together n queens in chess board without any threatening by Hill climbing algorithm. this algorithm better than past solution but it take 2 min for 300 queens and this time increase exponentially!

But I didn't have any idea for doing that fast! I want algorithm for doing that faster .

I want faster manner to solve problem fast as possible for 1000 queens.

This is my Hill climbing Code :

// 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;
}
Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
  • 2
    http://en.wikipedia.org/wiki/Eight_queens_puzzle If you are after just a solution (not counting the number of solutions) then there seems to be a formula for placing the queens. See the Explicit Solutions section. – Jason L Dec 30 '14 at 03:18
  • can you explain me a bit how does the function generateBoard works ? – Giuseppe Apr 17 '15 at 01:49

1 Answers1

10

Note: This answer assumes you're interested in finding one valid solution. If you need to find all solutions, this won't help you.

Artificial Intelligence: A Modern Approach, Second Edition by Russell & Norvig has a table in Chapter 5: Constraint Satisfaction Problems on page 143 comparing various constraint satisfaction problem algorithms for various tasks. (The latest edition is the Third Edition, and it looks like Constraint Satisfaction Problems is now Chapter 6.)

According to their results, the minimum conflicts local search heuristic scored best out of the algorithms tested on the n-Queens problem, requiring an average of 4K checks compared with >40,000K checks for backtracking and forward-checking.

The algorithm is quite simple:

  • select an initial (random or preselected) assignment of queens
  • while there are threatened queens (or until you get tired of trying... it's worthwhile to put this in a for loop to limit the number of tries):
    • select a random threatened queen
    • move the selected queen to the square that minimizes conflicts

In that last step, I'm assuming that each queen is constrained to her column, so she can only change rows within the column. If there are several rows that minimize conflicts for the current queen, you can choose randomly among them.

That's it. It's completely random and it works beautifully.

Edit:

I had a note here about not remembering how high I got n when I implemented this algorithm, saying I knew I had got it over 100. I didn't find my old code, but I decided to throw something together anyway. It turns out that this approach is far more effective than I remembered. Here are the results for 10 queens:

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

With no attempts at optimizing the code, here are the approximate timings I'm getting for different problem sizes:

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

I only ran the last one for 5000 queens once, but to find a solution in under 18 minutes is quicker than I had expected.

beaker
  • 16,331
  • 3
  • 32
  • 49
  • Looks roughly `O(N^3)` ? – Ben Voigt Dec 30 '14 at 20:34
  • @BenVoigt That's what it's looking like... if you double the problem size you run in about 8 times the time. At least on this limited dataset. – beaker Dec 30 '14 at 20:35
  • I tried the method you suggest; it works very nicely indeed, however in my implementation it tends to get stuck at local minima if you select the initial configuration randomly. The way I got over this, to some extent, was repositioning randomly the queens in conflict in the columns to which they belong, then trying again, and if this did not help then trying another random configuration. Did you encounter this problem (of local minima)? If yes, how did you got over it? – John Donn Mar 06 '16 at 14:59
  • @JohnDonn As I remember, the problem I found was oscillating between two configurations. I think what I did was to force different queens to be moved at each step, assuming that multiple queens had the same number of conflicts, or making sure that the single queen with max conflicts didn't move to its current or previous position. In practice, it's easier to limit the number of moves for a given starting configuration before retrying. One very interesting thing about this algorithm is the number of moves required seems to *converge*. The authors claim an average of only 50 moves for 1M queens. – beaker Mar 06 '16 at 18:04
  • @beaker The convergence to 50 moves only happens if you use a greedy initialization with random tie-breaking. So it's basically `O(N)` when you account for the `N` initial placements. – MattG Feb 05 '20 at 15:54
  • 1
    @MattG Yes, the quality of the initial configuration will determine the number of swaps required. I pulled out my latest implementation from a couple of years ago and for 10,000 queens, assigning queens to random rows (each within its own column) took an average of 6224 swaps to reach a solution. Using a permutation of row numbers took 4703 swaps. Assigning queens to the row with the minimum number of conflicts only required 48 swaps. – beaker Feb 05 '20 at 16:54
  • @beaker It's a really cool problem - if I don't randomly break ties in the min-conflict greedy initialization (e.g., always grab the min-conflict row with the lowest index), it doesn't have the constant ~50 swap behaviour. And I guess it's only truly `O(N)` if some storage is used to keep track of which diagonals are filled, otherwise the diagonal conflict check time taken grows at `O(N)` making the whole thing take `O(N)`. Also, inputting a 'structured' initialization like `[0, 1, 2, ..., N-1]` produces really cool slanted diagonal bands (but takes much longer). Thanks for a great post! – MattG Feb 06 '20 at 15:54