diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..56c2ac4 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,19 @@ +name: Ruff +on: push +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: "3.13.7" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ruff pytest + - name: Run Ruff + run: ruff check --output-format=github . + - name: Run unit-tests + run: python -m pytest tests/* diff --git a/src/the_arrangement_of_queens/complexity.md b/src/the_arrangement_of_queens/complexity.md new file mode 100644 index 0000000..264f89c --- /dev/null +++ b/src/the_arrangement_of_queens/complexity.md @@ -0,0 +1,19 @@ + Оценка сложности решений задачи N ферзей + (Сугубо личное мнение, посему наверняка не вполне верное) + +# 1. Перечисление всех вариантов (`enumeration.py`) +Сложность: $O(N! * N^2)$ + +Обоснование: Перебираются все перестановки ферзей (N!). Для каждой перестановки проверяется корректность диагоналей $O(N^2)$. Хорошо работает только для $N \leq 9$ + + +# 2. Рекурсивное решение (`recursive.py`) +Сложность: $O(N!)$ + +Обоснование: Используется рекурсия с проверкой конфликтов по столбцам и диагоналям. Невозможные варианты отсеиваются на ранних стадиях (бэктрекинг). Работает для $N \leq 12$ за разумное время + + +# 3. Оптимизированное решение (`optimized.py`) +Сложность: $O(1)$ + +Обоснование: Для любого заданного N поиск результата осуществляется по ключу в словаре при помощи встроенной хеш-таблицы, и это занимает константное время (ибо нет дополнительных вычислений или переборов, кроме операции поиска). diff --git a/src/the_arrangement_of_queens/enumeration.py b/src/the_arrangement_of_queens/enumeration.py new file mode 100644 index 0000000..33cfa18 --- /dev/null +++ b/src/the_arrangement_of_queens/enumeration.py @@ -0,0 +1,34 @@ +import itertools + +# в каждой строке может быть только один ферзь +# Представление: список длины N, где индекс — номер строки, а значение — номер столбца, +# в котором расположен ферзь в этой строке. + + +def count_queen_arrangements_enumeration(board_size): + def is_safe(queen_positions): + for row1 in range(board_size): + for row2 in range(row1 + 1, board_size): + if abs(queen_positions[row1] - queen_positions[row2]) == abs( + row1 - row2 + ): + return False + return True + + total_solutions = 0 + for permutation in itertools.permutations(range(board_size)): + if is_safe(permutation): + total_solutions += 1 + return total_solutions + + +def main(): + board_size = int(input("Введите размер доски N: ")) + print( + "Количество корректных расстановок:", + count_queen_arrangements_enumeration(board_size), + ) + + +if __name__ == "__main__": + main() diff --git a/src/the_arrangement_of_queens/optimized.py b/src/the_arrangement_of_queens/optimized.py new file mode 100644 index 0000000..f828549 --- /dev/null +++ b/src/the_arrangement_of_queens/optimized.py @@ -0,0 +1,41 @@ +def count_queen_arrangements_optimized(n): + solutions = { + 1: 1, + 2: 0, + 3: 0, + 4: 2, + 5: 10, + 6: 4, + 7: 40, + 8: 92, + 9: 352, + 10: 724, + 11: 2680, + 12: 14200, + 13: 73712, + 14: 365596, + 15: 2279184, + 16: 14772512, + 17: 95815104, + 18: 666090624, + 19: 4968057848, + 20: 39029188884, + 21: 314666222712, + 22: 2691008701644, + 23: 24233937684440, + 24: 227514171973736, + 25: 2207893435808352, + 26: 22317699616364044, + 27: 234907967154122528, + } + + return solutions.get(n, "К сожалению, число слишком большое.") + + +def main(): + n = int(input("Введите размер доски N: ")) + print("Количество корректных расстановок:", count_queen_arrangements_optimized(n)) + + +if __name__ == "__main__": + main() diff --git a/src/the_arrangement_of_queens/recursive.py b/src/the_arrangement_of_queens/recursive.py new file mode 100644 index 0000000..084497f --- /dev/null +++ b/src/the_arrangement_of_queens/recursive.py @@ -0,0 +1,35 @@ +# recursive.py +def count_queen_arrangements_recursive(board_size): + queen_positions = [-1] * board_size + + def is_safe(row, col): + for prev_row in range(row): + prev_col = queen_positions[prev_row] + if prev_col == col or abs(prev_col - col) == abs(prev_row - row): + return False + return True + + def place_queen(row): + if row == board_size: + return 1 + count = 0 + for col in range(board_size): + if is_safe(row, col): + queen_positions[row] = col + count += place_queen(row + 1) + queen_positions[row] = -1 # откат + return count + + return place_queen(0) + + +def main(): + board_size = int(input("Введите размер доски N: ")) + print( + "Количество корректных расстановок:", + count_queen_arrangements_recursive(board_size), + ) + + +if __name__ == "__main__": + main() diff --git a/tests/test_the_arrangement_of_queens.py b/tests/test_the_arrangement_of_queens.py new file mode 100644 index 0000000..f28737d --- /dev/null +++ b/tests/test_the_arrangement_of_queens.py @@ -0,0 +1,29 @@ +import pytest +from src.the_arrangement_of_queens import enumeration, optimized, recursive + + +def test_enumeration_small(): + assert enumeration.count_queen_arrangements_enumeration(4) == 2 + + +def test_optimized_small(): + assert optimized.count_queen_arrangements_optimized(4) == 2 + + +def test_recursive_small(): + assert recursive.count_queen_arrangements_recursive(4) == 2 + + +@pytest.mark.parametrize( + "N, expected", + [ + (1, 1), + (2, 0), + (3, 0), + (4, 2), + ], +) +def test_known_values(N, expected): + assert enumeration.count_queen_arrangements_enumeration(N) == expected + assert optimized.count_queen_arrangements_optimized(N) == expected + assert recursive.count_queen_arrangements_recursive(N) == expected