答题
N皇后问题要求在一个N×N的棋盘上放置N个皇后,使得它们互不攻击,即任何两个皇后都不能处于同一行、同一列或同一斜线上。
先放代码
python
def solveNQueens(n):
# isSafe函数用于检查在放置第n个皇后时,是否有任何冲突
def isSafe(row, col, board):
# 检查之前的行,对于每一个已放置的皇后
for i in range(row):
# 检查竖直方向、两个对角线方向是否安全
# board[i] == col 检查同列冲突
# board[i] - i == col - row 检查主对角线冲突
# board[i] + i == col + row 检查副对角线冲突
if board[i] == col or \
board[i] - i == col - row or \
board[i] + i == col + row:
return False
return True
# backtrack是递归函数,用于尝试在棋盘的每一行放置皇后
def backtrack(row, board):
# 如果当前行是棋盘之外,意味着我们成功放置了所有的皇后
if row == n:
# 将当前板的副本添加到结果中
result.append(board[:])
return
# 尝试在当前行的每一列放置皇后
for col in range(n):
if isSafe(row, col, board):
# 在当前位置放置皇后
board[row] = col
# 递归到下一行
backtrack(row + 1, board)
# 回溯:移除当前行的皇后,尝试下一个位置
board[row] = -1
result = [] # 用于存储所有的解
# 调用backtrack从第0行开始尝试放置皇后
backtrack(0, [-1]*n)
return result
# 打印解决方案的辅助函数
def printSolution(solutions):
for solution in solutions:
for i in solution:
row = ["."] * len(solution)
row[i] = "Q"
print(" ".join(row))
print("\n")
# 解决N=4的N皇后问题并打印解
n = 4
solutions = solveNQueens(n)
printSolution(solutions)
解题思路
- 整体逻辑:我们从棋盘的第一行开始,尝试在每一列放置一个皇后。对于每一个位置,我们检查放置皇后后是否冲突。如果没有冲突,我们递归地在下一行进行相同的尝试。当我们放置了最后一个皇后且无冲突时,我们找到了一个解,并将其添加到解的集合中。
- 回溯:当我们在当前行的所有列都尝试过但没有找到可行解时,我们回到上一行,改变上一行皇后的位置,然后再次尝试。这个过程会一直重复,直到我们尝试了所有可能的排列组合。
isSafe
函数:此函数是核心,用于检查当前选定的行和列是否安全放置皇后,即检查是否有任何形式的攻击(同行、同列、对角线)。backtrack
函数:这是一个递归函数,负责尝试在棋盘上放置皇后并回溯。- 结果存储 :
result
列表用于存储所有找到的解,其中每个解是一个列表,表示每一行皇后的位置。
回溯和递归
回溯和递归是两个在算法设计和编程中常见的概念,它们在很多场景下都会一起使用,但它们指的是不同的概念。
递归 (Recursion)
递归是一种解决问题的方法,它将问题分解为更小的子问题,然后再解决这些子问题。递归方法通常包括两个主要部分:
- 基本情况(Base Case):这是递归调用停止的条件,不再进一步分解问题的点。
- 递归步骤(Recursive Step):在这一步中,函数直接或间接地调用自身,以解决更小的问题。
递归是一种非常强大的编程技术,它可以用简洁的代码解决复杂的问题,特别是那些可以自然地分解成相似子问题的问题,如树遍历、排序算法等。
回溯 (Backtracking)
回溯是一种通过探索所有可能的候选解来寻找所有解的算法策略。在探索过程中,当发现当前候选解不可能是一个有效解时,算法会丢弃该解("回溯"),回到上一步作出不同的选择。回溯算法通常用于解决组合问题、划分问题、排列问题、子集问题、棋盘问题等。
回溯算法的特点是试错,它尝试分步去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答时,它将取消上一步甚至是上几步的计算,再通过其他可能的分步解答再次尝试寻找问题的答案。
区别和联系
- 递归是一种方法或过程,用于分解和解决问题。它是一种编程技术,不限于特定类型的问题。
- 回溯是一种算法策略,使用试错的思想来解决问题,特别适合于搜索和约束满足问题。回溯经常使用递归来实现,通过递归进行深度优先搜索。
简而言之,递归是一种程序结构和编程技巧,而回溯是一种算法思想和解决问题的策略。在实现回溯算法时,经常使用递归作为技术手段来进行深度优先搜索。
在一个二维矩阵(如棋盘)中,主对角线和副对角线是两个重要的概念,特别是在处理像N皇后问题这样的问题时。
主对角线和副对角线
主对角线(Main Diagonal)
主对角线是从矩阵的左上角到右下角的一条线。在一个N x N
的棋盘上,主对角线上的所有元素都有一个共同的特性:它们的行号和列号是相同的。也就是说,对于棋盘上的任何一个点(i, j)
,如果i == j
,那么这个点就位于主对角线上。
css
cssCopy code
[Q, -, -, -]
[-, Q, -, -]
[-, -, Q, -]
[-, -, -, Q]
在上面的示例中,Q
标记的位置代表放置皇后的地方,它们都位于主对角线上。
副对角线(Anti-Diagonal)
副对角线是从矩阵的右上角到左下角的一条线。在副对角线上的元素有一个特性:每个元素的行号和列号之和等于一个常数。在一个N x N
的棋盘上,这个常数通常是N-1
(如果我们从0开始计数)。也就是说,对于棋盘上的任何一个点(i, j)
,如果i + j == N - 1
(或者一个固定的常数,取决于矩阵的大小和起始索引),那么这个点就位于副对角线上。
css
cssCopy code
[-, -, -, Q]
[-, -, Q, -]
[-, Q, -, -]
[Q, -, -, -]
在上面的示例中,Q
标记的位置代表放置皇后的地方,它们都位于副对角线上。
在N皇后问题中的应用
在N皇后问题中,我们需要确保没有两个皇后攻击彼此。这意味着没有两个皇后可以位于同一行、同一列、主对角线或副对角线上。通过检查行号和列号的关系,我们可以判断两个皇后是否在同一对角线上:
- 如果两个皇后的行号和列号相等(
i1 == j1
和i2 == j2
),则它们在同一主对角线上。 - 如果两个皇后的行号和列号之和相等(
i1 + j1 == i2 + j2
),则它们在同一副对角线上。
理解主对角线和副对角线的概念对于解决N皇后问题至关重要,因为它们帮助我们在放置每个皇后时避免冲突。
对应到我们具体的代码:
python
# board[i] - i == col - row 检查主对角线冲突
# board[i] + i == col + row 检查副对角线冲突
这两个条件是用来检查在N皇后问题中,一个皇后是否放置在另一个皇后的对角线上的。这里的board[i]
表示第i
行皇后所在的列编号,而col
和row
表示当前尝试放置皇后的列编号和行编号。
条件解析
-
board[i] - i == col - row
检查主对角线:- 这个条件用于判断两个皇后是否在主对角线上。在主对角线上的任意两点,它们的行号和列号的差是相等的。举个例子,如果一个皇后位于
(2, 2)
,另一个皇后位于(4, 4)
,它们都在主对角线上,因为4-4 == 2-2
。所以,如果当前尝试放置的皇后位置(row, col)
与棋盘上任一已放置的皇后i
满足board[i] - i == col - row
,则表示它们在同一主对角线上。
- 这个条件用于判断两个皇后是否在主对角线上。在主对角线上的任意两点,它们的行号和列号的差是相等的。举个例子,如果一个皇后位于
-
board[i] + i == col + row
检查副对角线:- 这个条件用于判断两个皇后是否在副对角线上。在副对角线上的任意两点,它们的行号和列号的和是相等的。例如,如果一个皇后位于
(1, 3)
,另一个皇后位于(2, 2)
,它们都在同一副对角线上,因为1+3 == 2+2
。因此,如果当前尝试放置的皇后位置(row, col)
与棋盘上任一已放置的皇后i
满足board[i] + i == col + row
,则表示它们在同一副对角线上。
- 这个条件用于判断两个皇后是否在副对角线上。在副对角线上的任意两点,它们的行号和列号的和是相等的。例如,如果一个皇后位于
为什么这样检查
这两个条件的核心原理是基于对角线的性质:在主对角线上的任意两点,行号与列号的差是恒定的;在副对角线上的任意两点,行号与列号的和是恒定的。利用这一点,我们可以轻松检查放置的皇后是否会与任何已放置的皇后冲突(即是否在同一对角线上)。这是解决N皇后问题的关键步骤之一,确保了皇后们不会相互攻击。