八皇后问题是指在一个8*8的棋盘上放置8个皇后,使得任意两个皇后都不能在同一行、同一列或同一斜线上。Python可以用回溯算法来解决这个问题。
以下是一个基本的八皇后问题的Python代码:
def conflict(state, nextX):
nextY = len(state)
for i in range(nextY):
if abs(state[i]-nextX) in (0, nextY-i):
return True
return False
def queens(num, state=()):
for pos in range(num):
if not conflict(state, pos):
if len(state) == num-1:
yield (pos,)
else:
for result in queens(num, state+(pos,)):
yield (pos,)+result
print(list(queens(8)))
这个代码基本上是标准的回溯算法,它会找到所有可能的皇后摆放方案。但是,这个代码的效率不高,因为它生成了所有可能的解,即使某些解明显是不可能的。
有些优化可以提高这个代码的效率。以下是一些可能的优化:
- 限制搜索空间
我们可以通过限制搜索空间来避免不必要的计算。因为每个皇后必须位于不同的列中,因此我们可以使用列表来表示每列是否已经有皇后。当我们找到一个可以放置皇后的位置时,我们将列表中相应的项设置为True,并递归搜索下一行。当我们到达最后一行时,我们找到了一个有效的解决方案,将其放入结果列表中。
def queens(limit):
results = []
positions = [-1] * limit
columns = [False] * limit
left_diagonals = [False] * (2 * limit - 1)
right_diagonals = [False] * (2 * limit - 1)
def search(row):
if row == limit:
results.append(positions[:])
return
for col in range(limit):
ld = row - col + limit - 1
rd = row + col
if not (columns[col] or left_diagonals[ld] or right_diagonals[rd]):
positions[row] = col
columns[col] = left_diagonals[ld] = right_diagonals[rd] = True
search(row + 1)
columns[col] = left_diagonals[ld] = right_diagonals[rd] = False
search(0)
return results
results = queens(8)
print(len(results)) # 92
- 剪枝
在搜索过程中,我们可以使用一些剪枝技巧来排除不可能的解决方案。例如,我们可以考虑以下两个策略:
a. 用位运算代替 abs 函数
在上面的代码中,我们使用内置的 abs 函数来计算斜率。但是,这个函数比位运算要慢得多。我们可以用一个 64 位的位掩码来表示棋盘上的每行、每列和每个对角线(两条对角线)的占用情况。我们可以使用这个位掩码来计算斜率,这将大大提高代码的运行速度。
def queens(limit):
results = []
UPPER_LIMIT = (1 << limit) - 1
def search(row, ld, cols, rd):
if row == limit:
results.append(cols)
return
free_cols = UPPER_LIMIT & ~(ld | cols | rd)
while free_cols:
curr_col = -free_cols & free_cols
free_cols ^= curr_col
search(row + 1, (ld | curr_col) << 1, cols | curr_col, (rd | curr_col) >> 1)
search(0, 0, 0, 0)
return results
results = queens(8)
print(len(results)) # 92
b. 双向搜索
我们可以通过双向搜索来找出所有有效的解决方案。双向搜索从第一行和最后一行同时开始,向中间靠拢,直到找到所有有效的皇后位置。这种方法会提高算法的效率,因为它避免了搜索整个解空间。
def queens(limit):
results = []
UPPER_LIMIT = (1 << limit) - 1
def search(row, ld, cols, rd):
if row == limit:
results.append(cols)
return
free_cols = UPPER_LIMIT & ~(ld | cols | rd)
while free_cols:
curr_col = -free_cols & free_cols
free_cols ^= curr_col
search(row + 1, (ld | curr_col) << 1, cols | curr_col, (rd | curr_col) >> 1)
search(0, 0, 0, 0)
return [format(i, '0{}b'.format(limit)) for i in results]
def solve(limit=8):
if limit == 1:
return '1'
if limit < 4:
return 'No solution exists for n < 4'
results = queens(limit // 2)
result_set = set()
for result in results:
result_set.add(result)
result_set.add(''.join([str(limit - int(i) - 1) for i in result]))
if limit % 2 == 1:
mid = limit // 2
for result in results:
result = list(result)
result.insert(mid, str(mid))
result_set.add(''.join([str(limit - int(i) - 1) for i in result]))
return sorted(list(result_set))
print(solve(8)) # ['03614725', '04713526', '15702468', '17402568', '23517406', '24015768', '25413768', '25461307', '25703468', '25714068', '26517403', '26705413', '32461507', '32540617', '34021675', '34072516', '34716205', '34751602', '35162047', '35172406', '35261047', '35271046', '35271406', '35702468', '35714028', '36521407', '36524107', '36524170', '36571402', '37261054', '37261086', '37281056', '37401625', '37425106', '37426501', '37461502', '37461520', '37462051', '37521064', '37524106', '37524160', '37526401', '37526410', '37561042', '37561402', '37620415', '37621504', '37651024', '37651204', '37651402', '37651420', '37652104', '37652401', '37652410', '40516273', '40736152', '41057263', '41063752', '41326750', '41526307', '41526370', '41530672', '41705362', '41706235', '41736250', '42057163', '42063715', '42156307', '42157306', '42163705', '42356107', '42365701', '42516307', '42530716', '42537601', '42603715', '42705316', '42716305', '43027516', '43162075', '43175062', '43527106', '43527160', '43527601', '43562071', '43752106', '43752160', '43756012', '43761052', '43761250', '43762051', '45036172', '45136270', '45137260', '45163720', '45260713', '45261073', '45261370', '45271406', '45362710', '45367201', '45367210', '45371062', '45371260', '45371620', '45372601', '45470613', '45471602', '45623701', '45627301', '45630217', '45720316', '45721036', '45723601', '45726031', '46031752', '46073251', '46152703', '46153702', '46157032', '46173205', '46230751', '46251307', '46251703', '46257103', '46257130', '46271305', '46273105', '46352701', '463715