Я пытаюсь решить n queens problem
с использованием матрицы для представления шахматной доски. Это мое первое решение:Почему сложность этого решения для n королев настолько велика?
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define N 13
void printTable(int table[N][N], int size)
{
for(int i = 0; i < size; i++)
{
for(int j = 0; j < size; j++)
{
printf("%d ", table[i][j]);
}
printf("\n");
}
printf("\n");
}
bool isSafe(int table[N][N], int row, int column, int size)
{
// check the main diagonal
// we add +1 because otherwise we would be comparind against the base
// element on that line
for(int i = row + 1, j = column + 1; i < size && j < size; i++, j++)
{
if(table[i][j] == 1)
return false;
}
// check the secondary diagonal
for(int i = row + 1, j = column - 1; i < size && j >= 0; i++, j--)
{
if(table[i][j] == 1)
return false;
}
// check the column
for(int i = row + 1, j = column; i < size; i++)
{
if(table[i][j] == 1)
return false;
}
return true;
}
bool isSafeTable(int table[N][N], int size)
{
for(int i = 0; i < size; i++)
{
for(int j = 0; j < size; j++)
{
if(table[i][j] == 1)
{
if(!isSafe(table, i, j, size))
{
return false;
}
}
}
}
return true;
}
void getQueens(int table[N][N], int size, int queens, int row)
{
if(queens == size)
{
if(isSafeTable(table, size))
{
printTable(table, size);
}
return;
}
for(int i = 0; i < size; i++)
{
table[row][i] = 1;
if(isSafeTable(table, size))
{
getQueens(table, size, queens + 1, row + 1);
}
table[row][i] = 0;
}
}
int main()
{
int table[N][N] =
{
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
getQueens(table, 4, 0, 0);
return 0;
}
Как вы можете видеть, я использую большой массив массивов Интс представлять шахматную доску. Размер матрицы равен 13 x 13
. Чтобы решить проблему с менее чем 13
королевами, я работаю над подмножеством этой большой матрицы.
Как вы можете видеть, я использую функцию isSafeTable
на каждом шаге, чтобы проверить правильность конфигурации шахматной доски. Если он есть, я переключаюсь на следующую строку. Если это не так, я возвращаюсь.
Однако эта функция isSafeTable
имеет сложность O(n^3)
(поскольку на каждой итерации она называет isSafe
). Таким образом, я думал, что будет разумным решением отметить используемые элементы и просто проверить, доступно ли это пространство вместо проверки всей шахматной доски.
Итак, я пришел с этим решением:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define N 13
void printTable(int table[N][N], int size)
{
for(int i = 0; i < size; i++)
{
for(int j = 0; j < size; j++)
{
printf("%2d ", table[i][j]);
}
printf("\n");
}
printf("\n");
}
void _markWith(int table[N][N], int size, int row, int column, int element,
int specialCharacter)
{
for(int i = 0; i < size - row; i++)
{
int tmp = element;
// using the specialCharacter we can mark the queens with a different
// character depeneding on the calling function.
if(i == 0)
element = specialCharacter;
// mark the left diagonal
if(column - i >= 0)
table[row + i][column - i] = element;
// mark the right diagonal
if(column + i < size)
table[row + i][column + i] = element;
// mark the column
table[row + i][column] = element;
element = tmp;
}
}
// This is just a wrapper used to avoid duplicating the code for marking and
// unmarking a table.
void mark(int table[N][N], int size, int row, int column)
{
_markWith(table, size, row, column, -1, 8);
}
// See the documentation for `mark`.
void unmark(int table[N][N], int size, int row, int column)
{
_markWith(table, size, row, column, 0, 0);
}
void getQueens(int table[N][N], int size, int queens, int row)
{
if(queens == size)
{
printTable(table, size);
return;
}
for(int i = 0; i < size; i++)
{
if(table[row][i] == 0)
{
// This function call will result in pruning the column and the
// diagonals of this element. It actually replaces the 0s with -1s.
mark(table, size, row, i);
getQueens(table, size, queens + 1, row + 1 );
// Now replace the -1s with 0s.
unmark(table, size, row, i);
}
}
}
int main()
{
int table[N][N] =
{
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}
};
getQueens(table, 11, 0, 0);
return 0;
}
Функция mark
и unmark
используется для установки диагоналей и столбцов элемента в -1
. Кроме того, элемент (королева) отмечен 8 (я думал, что человеческому глазу будет легче идентифицировать ферзей таким образом при печати матрицы).
_markWith
Функция используется только, чтобы избежать перезаписи того же кода в mark
и unmark
.
Сложность этих функций - O(n)
, поэтому программа должна двигаться немного быстрее, но это не так. Первое решение на самом деле быстрее второго.
Вот некоторые статистические данные в зависимости от n
:
Время, затраченное обоими решениями в зависимости от n:
n | first solution | second solution
--+-----------------+-----------------
4 | 0.001s | 0.002s
--+-----------------+-----------------
5 | 0.002s | 0.001s
--+-----------------+-----------------
6 | 0.001s | 0.002s
--+-----------------+-----------------
7 | 0.004s | 0.003s
--+-----------------+-----------------
8 | 0.006s | 0.011s
--+-----------------+-----------------
9 | 0.025s | 0.133s
--+-----------------+-----------------
10| 0.093s | 3.032s
--+-----------------+-----------------
11| 0.581s | 1m 24.210s
Разница не то, что видно при малых значениях n
, но для более крупных довольно очевидно.
Здесь число рекурсивных вызовов, что каждая функция не зависит от n
:
n | first solution | second solution
--+-----------------+-----------------
4 | 16 | 16
--+-----------------+-----------------
5 | 53 | 65
--+-----------------+-----------------
6 | 152 | 514
--+-----------------+-----------------
7 | 551 | 7085
--+-----------------+-----------------
8 | 2 056 | 129 175
--+-----------------+-----------------
9 | 8 393 | 2 810 090
--+-----------------+-----------------
10| 35 538 | 70 159 513
--+-----------------+-----------------
11| 16 695 | 1 962 694 935
Как вы можете видеть, количество рекурсивных вызовов экспоненциально возрастает во втором растворе. Таким образом, функции mark
и unmark
не несут ответственности за медленный ход программы.
Я потратил этот день, пытаясь найти, почему второе решение делает так много рекурсивных вызовов по сравнению с первым, но я не смог ответить.
Вы можете мне помочь?
Второй список исходных кодов выглядит так же, как и первый, и отсутствует ссылки на 'mark',' unmark' и '_markWith'. – uesp
@uesp Я исправил эту проблему. – cristid9