我很难理解递归和回溯,虽然我做了一些简单的练习(例如Fibonacci)。所以请允许我在这里展示我的“脑力流”:
我阅读了教科书并知道如果当前位置消除了将下一个女王放入下一栏的可能性,你可以使用回溯删除之前的女王。所以这似乎很简单,我需要做的就是删除它,让程序决定下一个可能的位置。
过了一会儿,我发现程序在第6位女王停滞不前,所以我发现如果我只是删除了第5位女王,程序就会把它放回到当前位置(即给出前4位女王第5位)女王总是落在同一个地方,这并不奇怪)。所以我认为我需要跟踪最后一位女王的位置。
这是我困惑的时候。如果我要跟踪最后一位女王的位置(这样当我回溯该程序时,不允许将女王放在同一个地方),有两个潜在的困难:
a)假设我删除了第五位女王,我必须编写代码来决定其下一个可能的位置。这可以通过忽略其当前位置(删除之前)来解决,并继续在以下行中查找可能的位置。
b)我应该追踪所有以前的皇后吗?似乎是这样。让我们说实际上我必须删除不是一个女王,而是两个女王(甚至更多),我当然需要跟踪他们当前的所有位置。但这比它看起来要复杂得多。
http://www.geeksforgeeks.org/backtracking-set-3-n-queen-problem/
这让我感到非常惊讶,因为这很简单但却有效!唯一的回溯部分是删除最后一个女王!所以问题是:以下代码如何确保当给定4个皇后的位置时,第5个皇后并不总是一次又一次地落入同一个地方?我认为我不明白的是你如何递归回溯(比如你需要删除更多的皇后)。递归前进时我没有问题,但我怎样才能递归地向后移动?
/* A recursive utility function to solve N Queen problem */
bool solveNQUtil(int board[N][N], int col)
{
/* base case: If all queens are placed then return true */
if (col >= N)
return true;
/* Consider this column and try placing this queen in all rows
one by one */
for (int i = 0; i < N; i++)
{
/* Check if queen can be placed on board[i][col] */
if ( isSafe(board, i, col) )
{
/* Place this queen in board[i][col] */
board[i][col] = 1;
/* recur to place rest of the queens */
if ( solveNQUtil(board, col + 1) == true )
return true;
/* If placing queen in board[i][col] doesn't lead to a solution
then remove queen from board[i][col] */
board[i][col] = 0; // BACKTRACK
}
}
/* If queen can not be place in any row in this colum col
then return false */
return false;
}
行。现在我有一些代码可以工作,但我大部分修改了我自己的代码,所以我很不稳定:
bool EightQueen(int& numQueen) {
if (numQueen == 8) {
return true;
}
if (numQueen == 0) {
PlaceQueen(0, 0);
numQueen ++;
EightQueen(numQueen);
}
int row = 0;
for (row = 0; row <= 7; row ++) {
if (CheckThis(row, numQueen)) { //Check if next queen can be put
PlaceQueen(row, numQueen); //Place next queen
numQueen ++;
DisplayQueen();
cout << endl;
if (EightQueen(numQueen)) { //Try next queen
return true;
}
ClearQueen(numQueen - 1);
numQueen --;
}
}
return false;
}
假设numQueen是5,所以在for循环中我们将检查是否可以放置第6个女王。我们知道这对于所有行都是不可能的,因此该函数返回false。我假设它然后“缩小”回到它被调用的地方,也就是当numQueen为4.所以调用ClearQueen(4)并移除最后一个女王(第5个)。显然for循环还没有完成,所以我们将尝试下一行,看它是否允许进一步开发。即我们检查第五皇后是否可以放在下一排,如果是,我们将进一步检查第六皇后是否可以放置等等。
是的,似乎有效,嗯,是的。
答案 0 :(得分:8)
考虑你的初始董事会:
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
当您第一次调用函数时,算法会在第0列和第0行放置一个女王,因为您使用col = 0
调用它,因为for (int i = 0; i < N; i++)
从0开始。您的电路板变为
+---+---+---+---+---+---+---+---+
| Q | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
然后,您使用col = 1
以递归方式调用该函数,因此您将尝试在col=1
和line=0
处设置一个女王。你得到一个不可能的位置,因为皇后可以互相接受,所以你继续for (int i = 0; i < N; i++)
循环并最终成功地将一个女王放在col=1
和line=2
,你得到这个板:
+---+---+---+---+---+---+---+---+
| Q | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | Q | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
现在你继续这样做,每次增加col
。最终,你将进入这个董事会:
+---+---+---+---+---+---+---+---+
| Q | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | Q | | | | |
+---+---+---+---+---+---+---+---+
| | Q | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | Q | | | |
+---+---+---+---+---+---+---+---+
| | | Q | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | Q | | |
+---+---+---+---+---+---+---+---+
| | | | | | | | |
+---+---+---+---+---+---+---+---+
| | | | | | | Q | |
+---+---+---+---+---+---+---+---+
你在这里遇到问题,因为这个委员会不承认第7栏中的女王位置。你将不得不回溯。发生的情况是,在尝试列中的所有位置并且未找到位置之后,递归函数仅到达return false;
。当函数返回false时,前一个函数调用将在行
if ( solveNQUtil(board, col + 1) == true )
由于调用返回true,for循环体的其余部分将被执行,i
将递增,算法将继续尝试位置。可以把它想象成一个巨大的嵌套for循环:
for(int i = 0; i < 8; ++i) {
for(int j = 0; j < 8; ++j) {
//Snip 6 other fors
board[0, i] = 1;
board[1, j] = 1;
//Snip
if(isValid(board)) return board;
//Snip clean up
}
}
用递归函数调用替换。这说明“回溯”实际上只是让先前的递归级别迭代到下一次尝试。在这种情况下,它意味着尝试新的位置,而在其他应用程序中,它将尝试下一个生成的移动。
我认为您需要了解的是,当您再次调用相同的函数时,前一个递归调用的状态不会丢失。当你到达 p>行
if ( solveNQUtil(board, col + 1) == true )
当前函数的状态仍然在堆栈上,并为solveNQUtil
的新调用创建了一个新的堆栈帧。当该函数返回时,前一个函数可以继续执行,并且在这种情况下,递增它试图将女王放入哪一行。
希望这会有所帮助。围绕这些东西的最佳方法是将问题减少到较小的域(比如少量的皇后),并使用调试器逐步执行。
答案 1 :(得分:2)
你问题的直接答案很简单:你的立场和 在一个循环中删除女王。下次循环,你 将尝试下一个位置。
这让我想到了下一点:你说的是教科书
没有回溯代码,只有递归代码。
递归代码是回溯代码。递归时,
函数的每个实例都有自己的完整变量集。
所以在这种情况下,当调用solveNQUtil
时,问题就出现了
已经为第一个col - 1
列解决了问题。该
函数遍历行,每次测试是否
可以放置一个女王,如果是这样,放置它,并递归。该
迭代确保将检查所有可能的位置(如果
必要的 - 一旦我们找到解决方案,你的代码就会终止。)
答案 2 :(得分:2)
你必须记住,有一个两个理由让女王下台:
你的节目在Queen 5停滞不前,因为它未能解释第二个条件。詹姆斯说,没有必要跟踪位置,每个递归调用都隐含了对它必须放置的女王的跟踪。
尝试设想调用堆栈(实际上,您可以修改程序以生成相同类型的输出):
Queen 1 is safe on row 1
Queen 2 is safe on row 3
Queen 3 is safe on row 5
Queen 4 is safe on row 2
Queen 5 is safe on row 4
No more rows to try for Queen 6. Backtracking...
Queen 5 is safe on row 8
No more rows to try for Queen 6. Backtracking...
No more rows to try for Queen 5. Backtracking...
Queen 4 is safe on row 7
Queen 5 is safe on row 2
Queen 6 is safe on row 4
Queen 7 is safe on row 6
No more rows to try for Queen 8. Backtracking...
每次你回溯时,都会意识到你回到上一个函数调用,处于相同的状态你离开了它。所以当你到达女王6并且没有可能性时,该函数返回false,这意味着你已经完成 {Queen 1的solveNQUtil(board, col + 1)
调用。你回到了{{1} Queen 5的循环,接下来会发生的事情是for
增加,你试图将Queen 5放在第5行,依此类推......
我建议您使用this demo(尝试放置控件:“手动帮助”选项),我们的大脑在视觉上更好地理解事物。代码太抽象了。