C 为什么n皇后的这个解决方案如此复杂?
我试图用一个矩阵来表示棋盘来解决C 为什么n皇后的这个解决方案如此复杂?,c,algorithm,recursion,time-complexity,n-queens,C,Algorithm,Recursion,Time Complexity,N Queens,我试图用一个矩阵来表示棋盘来解决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
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;
}
#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(我认为在打印矩阵时,人眼更容易通过这种方式识别皇后)
函数\u markWith
用于避免在中重写相同的代码
标记
和取消标记
这些函数的复杂性是
O(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
两种解决方案所用的时间取决于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
如您所见,在第二种解决方案中,递归调用的数量呈指数增长。因此,函数标记
和取消标记
不会导致程序移动缓慢
我花了这一天的时间试图找出为什么第二个解决方案与第一个解决方案相比会进行如此多的递归调用,但我没有找到答案
你能帮我吗?第二种解决方案是错误的。它输出比正常情况更多的解决方案。例如,对于
N=5
,它输出(除其他外):
原因是您的标记代码:
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);
}
考虑一下被两个皇后攻击的单元会发生什么情况:您将在放置第一个皇后时对其进行标记,进行递归调用(或更多,无所谓),再次标记,然后在从第二个递归调用返回时取消标记。然后您将忘记,在第一次递归调用期间放置的queen仍在攻击它
注意,在上面的每一个错误的解决方案中,一个被错误放置的皇后被另外两个攻击,并且它也被放置在攻击它的另外两个皇后之前
显然,这会导致算法找到更多的解决方案,从而产生更多的递归调用
经典解决方案
解决该问题的正确方法是使用该算法生成置换。让我们:
col[i] = the column of the queen placed on row i
然后需要在col
数组中生成有效的排列。我将把必要的条件留作练习
当然,您也可以通过递增或递减计数器来修正方法,而不是仅使用
1/0
第二个源代码列表与第一个源代码列表相同,并且缺少对标记的引用,取消标记
和\u markWith
@uesp我已修复了此问题。我做了一些更改,并提出了此解决方案,但对于n>=9的值,它给出了错误的结果。例如,对于n=9
,它输出322
解决方案,而不是352
。我该怎么办?@cristid9-标记代码看起来仍然很奇怪,很可能是这样。你能说服自己你用+/-做的是正确的吗?如果没有,我建议你尝试其他方法,比如排列算法。