#python #algorithm #backtracking #python-chess
#python #алгоритм #отслеживание возврата #python-шахматы
Вопрос:
Я начал решать проблему 8 queens с обратным отслеживанием в Python. Все хорошо и хорошо. Он даже распечатал первый ответ. Тем не менее, он застрял при первой попытке возврата.
Задача звучала таким образом:
Реализуйте функцию Python, которая решает головоломку 8 queens. Головоломка с 8 ферзями состоит из размещения 8 ферзей на шахматной доске так, чтобы ни одна из ферзей не могла захватить другую. Обратите внимание, что королевы могут перемещаться ортогонально или по диагонали в любом направлении.
Вы должны реализовать функцию solve(), которая при вызове печатает первое решение головоломки, а затем ожидает ввода. Как только пользователь нажимает ‘ввод’, печатается следующее решение и так далее.
— Ваша программа должна быть в состоянии найти все решения для головоломки и каждое решение только один раз. ‘
— Должно быть легко модифицировать вашу программу, чтобы она работала для разных размеров платы. Подсказки:
— В любой строке есть ровно одна королева. Следовательно, все, что вам нужно вычислить, это столбец, в который может быть помещен каждый из 8 ферзей.
— Вы должны реализовать рекурсивную функцию solve (n), которая находит место для n-го 1 ферзя, а затем вызывает себя рекурсивно для n 1 ферзя (если не были размещены все ферзи). Он должен систематически исследовать все возможности, используя отслеживание возврата.
— Вам разрешено (и рекомендуется) определять дополнительные функции (кроме solve() ) для улучшения качества вашего кода, если это необходимо.
import numpy as np
grid = np.zeros((8, 8), dtype = int)
def possible(y, n):
global solved
global grid
for i in range(0, 8):
if grid[y][i] == n:
return False
try:
for item in solved[str(y)]:
if grid[y].all() == item.all():
return False
except KeyError:
return True
return True
max_y = 7
max_x = 7
def print_grid():
global grid
for line in grid:
for square in line:
if square == 0:
print(".", end = " ")
else :
print("Q", end = " ")
print()
solved = {}
def prefilled_solved():
global solved
for i in range(0, len(grid[0])):
solved[f"{str(i)}"] = []
def solve(y=0):
global grid
global solved
while y < 8:
for x in range(0, 8):
if grid[y][x] == 0:
if possible(x, 1):
grid[y][x] = 1
solved[f"{str(y)}"].append(grid[y])
y = 1
solve(y)
#y -= 1 or y = 0 or y -=2
# backtracking - bad choice
# grid[y][x] = 0
print_grid()
print(grid)
return
input("More?")
if __name__ == '__main__':
prefilled_solved()
solve()
Я последовал совету @mkam, теперь у меня есть случайное созвездие Queen, но я полностью избавился от рекурсии.
```import numpy as np
grid = np.zeros((8, 8), dtype = int)
from random import randint, shuffle, choice
from itertools import permutations
constellations_drawn = []
def print_grid():
global grid
for line in grid:
for square in line:
if square == 0:
print(".", end = " ")
else :
print("Q", end = " ")
print()
solved = []
def prefilled_solved():
global solved
new_board = ['1', '2', '3', '4', '5', '6', '7', '8']
new_board_i = ''.join(new_board)
solved = permutations(new_board_i, 8)
def solve(y=0):
global grid
global solved
global constellations_drawn
list_solved = list(solved)
len_solved = len(list_solved)
board_drawn = list_solved[randint(0, len_solved-1)]
board_drawn_str = ''.join(board_drawn)
while board_drawn_str in constellations_drawn:
board_drawn = list_solved[randint(0, len_solved - 1)]
new_board_list = [int(item) for item in board_drawn]
for i, x in enumerate(new_board_list):
if grid[i-1][x-1] == 0:
grid[i-1][x-1] = 1
#y = 1
#solve(y)
#y -= 1 or y = 0 or y -=2
# backtracking - bad choice
# grid[y][x] = 0
constellations_drawn.append(board_drawn_str)
print_grid()
print(grid)
return
input("More?")
if __name__ == '__main__':
prefilled_solved()
solve()
I've merged the code of @mkam and mine. And it works. I still use numpy ndarray.
import numpy as np
from numpy.core._multiarray_umath import ndarray
def print_grid(solutions_found, board) -> None:
line: ndarray
len_board = len(board)
grid: ndarray = np.zeros((len_board, len_board), dtype=int)
for i, number in enumerate(board):
grid[i - 1][number - 1] = 1
for line in grid:
for square in line:
if square == 0:
print(".", end=" ")
else:
print("Q", end=" ")
print()
print(f'Solution - {solutions_found}')
def solve(boardsize, board=[], solutions_found=0):
if len(board) == boardsize:
solutions_found = 1
print_grid(solutions_found, board)
else:
for q in [col for col in range(1, boardsize 1) if col not in board]:
if is_safe(q, board):
solutions_found = solve(boardsize, board [q], solutions_found)
return solutions_found
def is_safe(q, board, x=1):
if not board:
return True
if board[-1] in [q x, q - x]:
return False
return is_safe(q, board[:-1], x 1)
if __name__ == '__main__':
solve(8)
Комментарии:
1. Почему вы используете
numpy
для этого? Это не является требованием задачи, и конфигурацию платы лучше всего представить в виде списка из 8 номеров строк, по одному для каждого столбца. Например,grid = [1,3,6,4,2,7,5,8]
представьте доску с Q в 1-й строке столбца 1, 3-й строке столбца 2, 6-й строке столбца 3, 4-й строке столбца 4 и т. Д.2. Привет, @mkam! Теперь я определенно полностью избавился от рекурсии. Может быть, yield подойдет?
3. Я все еще не могу понять, зачем вам
numpy
это нужно или почему вам нужно избавиться от рекурсии. Рекурсивное решение очень естественно для этой проблемы. Я опубликую пример решения в качестве ответа, чтобы вы могли увидеть, как вы можете сделать это рекурсивно, используя простой одномерный список для представления доски
Ответ №1:
Это пример того, как проблема с 8 королевами может быть решена рекурсивно, используя простой список для представления доски. Список, такой как [8, 4, 1, 3, 6, 2, 7, 5] представляет 8 рядов шахматной доски сверху вниз, с Q в 8-м столбце верхнего ряда, 4-м столбце 7-го ряда, 1-м столбце 6-го ряда... и 5-й столбец нижней строки.
Решение строится, начиная с пустой доски []
, путем размещения Q в следующей строке в позиции столбца, где она не может быть взята. Возможные позиции - это столбцы, которые еще не были заняты ранее (это цикл for в функции solve
). Для каждой из этих возможных позиций столбца функция issafe
проверяет, безопасна ли позиция от взятия по диагонали Qs, уже находящимися на доске. Если позиция безопасна, доска решений расширяется еще на одну строку, и решение повторяется до тех пор, пока доска не будет заполнена ( len(board) == boardsize
), после чего количество решений увеличивается и отображается доска.
Обратите внимание, что функция solve
работает для любого размера квадратной шахматной доски - желаемый размер передается в качестве параметра solve
, и функция возвращает общее количество найденных решений.
Надеюсь, это поможет объяснить, как проблема с 8 королевами может быть решена рекурсивно БЕЗ numpy
.
def display(solution_number, board):
row = '| ' * len(board) '|'
hr = ' ---' * len(board) ' '
for col in board:
print(hr)
print(row[:col*4-3],'Q',row[col*4:])
print(f'{hr}n{board}nSolution - {solution_number}n')
def issafe(q, board, x=1):
if not board: return True
if board[-1] in [q x,q-x]: return False
return issafe(q, board[:-1], x 1)
def solve(boardsize, board=[], solutions_found=0):
if len(board) == boardsize:
solutions_found = 1
display(solutions_found, board)
else:
for q in [col for col in range(1,boardsize 1) if col not in board]:
if issafe(q,board):
solutions_found = solve(boardsize, board [q], solutions_found)
return solutions_found
if __name__ == '__main__':
solutions = solve(8)
print(f'{solutions} solutions found')
Вы упоминаете использование yield
- это также возможно и преобразуется solve
в генератор, производящий одно решение за раз. Затем ваша программа может использовать for
цикл для получения каждого решения по очереди и обработки его по мере необходимости. Следующее yield
решение работает с Python v.3.3 и далее, поскольку оно использует yield from
:
def display(solution_number, board):
row = '| ' * len(board) '|'
hr = ' ---' * len(board) ' '
for col in board:
print(hr)
print(row[:col*4-3],'Q',row[col*4:])
print(f'{hr}n{board}nSolution - {solution_number}n')
def issafe(q, board, x=1):
if not board: return True
if board[-1] in [q x,q-x]: return False
return issafe(q, board[:-1], x 1)
def solve(boardsize, board=[]):
if len(board) == boardsize:
yield board
else:
for q in [col for col in range(1,boardsize 1) if col not in board]:
if issafe(q,board):
yield from solve(boardsize, board [q])
if __name__ == '__main__':
for solutionnumber, solution in enumerate(solve(8)):
display(solutionnumber 1, solution)
Если рекурсивная функция issafe
кажется запутанной, вот нерекурсивная версия:
def issafe(q, board):
x = len(board)
for col in board:
if col in [q x,q-x]: return False
x -= 1
return True
Комментарии:
1. Я объединил ваш код со своим, и он работает. Тем не менее, я все еще не совсем понимаю, как работает функция issafe. Я бы, вероятно, запрограммировал его итеративно, проверяя во всех возможных измерениях, если "диагональ" свободна, не могли бы вы быть так любезны и попытаться объяснить это мне?
2.
issafe
принимает частично заполненную доску, например [8, 4, 1] и возможную позицию q в следующем ряду, например 6. Это отвечает на вопрос: "Безопасно ли помещать ферзя в столбец 6 строки 4, учитывая, что в столбцах 8, 4 и 1 строк 1, 2 и 3 есть ферзи? Ему нужно проверять только по диагонали, посколькуsolve
выбираются только возможные позиции столбцов, которые еще не использовались. Таким образом, он проверяет список назад по диагонали, чтобы увидеть, является ли последний элемент в списке q-1 или q 1, затем предыдущий элемент q-2 или q 2, затем предыдущий q-3 или q 3. Надеюсь, это поможет.