--- comments: true --- # 13.4   Задача о n ферзях !!! question Согласно правилам шахмат ферзь может атаковать фигуры, находящиеся с ним на одной строке, в одном столбце или на одной диагонали. Даны $n$ ферзей и шахматная доска размера $n \times n$. Требуется найти такие расстановки, при которых ни одна пара ферзей не может атаковать друг друга. Как показано на рисунке 13-15, при $n = 4$ существует два решения. С точки зрения поиска с возвратом доска размера $n \times n$ содержит $n^2$ клеток, которые образуют все возможные выборы `choices` . По мере поочередного размещения ферзей состояние доски непрерывно меняется, и текущее содержимое доски образует состояние `state` . ![Решения задачи о 4 ферзях](n_queens_problem.assets/solution_4_queens.png){ class="animation-figure" }

Рисунок 13-15   Решения задачи о 4 ферзях

На рисунке 13-16 показаны три ограничения этой задачи: **несколько ферзей не могут находиться на одной строке, в одном столбце или на одной диагонали**. При этом нужно помнить, что диагонали бывают двух типов: главная `\` и побочная `/` . ![Ограничения задачи о n ферзях](n_queens_problem.assets/n_queens_constraints.png){ class="animation-figure" }

Рисунок 13-16   Ограничения задачи о n ферзях

### 1.   Построчная стратегия размещения Число ферзей и число строк доски одинаково и равно $n$ , поэтому легко получить следующий вывод: **в каждой строке доски разрешено и нужно разместить ровно одного ферзя**. Иначе говоря, можно использовать построчную стратегию: начиная с первой строки, размещать по одному ферзю в каждой строке, пока не будет достигнута последняя. На рисунке 13-17 показан процесс построчного размещения для задачи о 4 ферзях. Из-за ограничений размера изображения на рисунке 13-17 показана лишь одна ветвь поиска для первой строки, а все варианты, не удовлетворяющие ограничениям по столбцам и диагоналям, были отсечены. ![Построчная стратегия размещения](n_queens_problem.assets/n_queens_placing.png){ class="animation-figure" }

Рисунок 13-17   Построчная стратегия размещения

По своей сути **построчная стратегия сама по себе выполняет роль обрезки** , потому что заранее исключает все ветви поиска, в которых в одной строке оказалось бы несколько ферзей. ### 2.   Обрезка по столбцам и диагоналям Чтобы удовлетворить ограничению по столбцам, можно использовать булев массив `cols` длины $n$ , который записывает, есть ли ферзь в каждом столбце. Перед каждым размещением мы используем `cols` для отсечения столбцов, уже занятых ферзями, а затем динамически обновляем состояние `cols` во время отката. !!! tip Обратите внимание: начало координат матрицы находится в левом верхнем углу, при этом индексы строк растут сверху вниз, а индексы столбцов - слева направо. Как теперь обработать ограничения по диагоналям? Пусть клетка на доске имеет координаты $(row, col)$ . Выбрав некоторую главную диагональ в матрице, можно заметить, что разность индексов строки и столбца одинакова для всех клеток этой диагонали, **то есть для всех клеток главной диагонали значение $row - col$ постоянно**. Это означает, что если для двух клеток выполняется равенство $row_1 - col_1 = row_2 - col_2$ , то они обязательно лежат на одной и той же главной диагонали. Используя это правило, можно с помощью массива `diags1` , показанного на рисунке 13-18, отмечать наличие ферзя на каждой главной диагонали. Аналогично **для всех клеток побочной диагонали значение $row + col$ является постоянным**. Поэтому для обработки ограничений по побочным диагоналям можно использовать еще один массив `diags2` . ![Обработка ограничений по столбцам и диагоналям](n_queens_problem.assets/n_queens_cols_diagonals.png){ class="animation-figure" }

Рисунок 13-18   Обработка ограничений по столбцам и диагоналям

### 3.   Реализация кода Заметим, что в квадратной матрице размера $n$ диапазон значений $row - col$ равен $[-n + 1, n - 1]$ , а диапазон значений $row + col$ равен $[0, 2n - 2]$ . Следовательно, число главных и побочных диагоналей равно $2n - 1$ , а значит, длины массивов `diags1` и `diags2` тоже равны $2n - 1$ . === "Python" ```python title="n_queens.py" def backtrack( row: int, n: int, state: list[list[str]], res: list[list[list[str]]], cols: list[bool], diags1: list[bool], diags2: list[bool], ): """Алгоритм бэктрекинга: n ферзей""" # Когда все строки уже обработаны, записать решение if row == n: res.append([list(row) for row in state]) return # Обойти все столбцы for col in range(n): # Вычислить главную и побочную диагонали, соответствующие этой клетке diag1 = row - col + n - 1 diag2 = row + col # Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if not cols[col] and not diags1[diag1] and not diags2[diag2]: # Попытка: поставить ферзя в эту клетку state[row][col] = "Q" cols[col] = diags1[diag1] = diags2[diag2] = True # Перейти к размещению следующей строки backtrack(row + 1, n, state, res, cols, diags1, diags2) # Откат: восстановить эту клетку как пустую state[row][col] = "#" cols[col] = diags1[diag1] = diags2[diag2] = False def n_queens(n: int) -> list[list[list[str]]]: """Решить задачу о n ферзях""" # Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку state = [["#" for _ in range(n)] for _ in range(n)] cols = [False] * n # Отмечать, есть ли ферзь в столбце diags1 = [False] * (2 * n - 1) # Отмечать наличие ферзя на главной диагонали diags2 = [False] * (2 * n - 1) # Отмечать наличие ферзя на побочной диагонали res = [] backtrack(0, n, state, res, cols, diags1, diags2) return res ``` === "C++" ```cpp title="n_queens.cpp" /* Алгоритм бэктрекинга: n ферзей */ void backtrack(int row, int n, vector> &state, vector>> &res, vector &cols, vector &diags1, vector &diags2) { // Когда все строки уже обработаны, записать решение if (row == n) { res.push_back(state); return; } // Обойти все столбцы for (int col = 0; col < n; col++) { // Вычислить главную и побочную диагонали, соответствующие этой клетке int diag1 = row - col + n - 1; int diag2 = row + col; // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Попытка: поставить ферзя в эту клетку state[row][col] = "Q"; cols[col] = diags1[diag1] = diags2[diag2] = true; // Перейти к размещению следующей строки backtrack(row + 1, n, state, res, cols, diags1, diags2); // Откат: восстановить эту клетку как пустую state[row][col] = "#"; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Решить задачу о n ферзях */ vector>> nQueens(int n) { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку vector> state(n, vector(n, "#")); vector cols(n, false); // Отмечать, есть ли ферзь в столбце vector diags1(2 * n - 1, false); // Отмечать наличие ферзя на главной диагонали vector diags2(2 * n - 1, false); // Отмечать наличие ферзя на побочной диагонали vector>> res; backtrack(0, n, state, res, cols, diags1, diags2); return res; } ``` === "Java" ```java title="n_queens.java" /* Алгоритм бэктрекинга: n ферзей */ void backtrack(int row, int n, List> state, List>> res, boolean[] cols, boolean[] diags1, boolean[] diags2) { // Когда все строки уже обработаны, записать решение if (row == n) { List> copyState = new ArrayList<>(); for (List sRow : state) { copyState.add(new ArrayList<>(sRow)); } res.add(copyState); return; } // Обойти все столбцы for (int col = 0; col < n; col++) { // Вычислить главную и побочную диагонали, соответствующие этой клетке int diag1 = row - col + n - 1; int diag2 = row + col; // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Попытка: поставить ферзя в эту клетку state.get(row).set(col, "Q"); cols[col] = diags1[diag1] = diags2[diag2] = true; // Перейти к размещению следующей строки backtrack(row + 1, n, state, res, cols, diags1, diags2); // Откат: восстановить эту клетку как пустую state.get(row).set(col, "#"); cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Решить задачу о n ферзях */ List>> nQueens(int n) { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку List> state = new ArrayList<>(); for (int i = 0; i < n; i++) { List row = new ArrayList<>(); for (int j = 0; j < n; j++) { row.add("#"); } state.add(row); } boolean[] cols = new boolean[n]; // Отмечать, есть ли ферзь в столбце boolean[] diags1 = new boolean[2 * n - 1]; // Отмечать наличие ферзя на главной диагонали boolean[] diags2 = new boolean[2 * n - 1]; // Отмечать наличие ферзя на побочной диагонали List>> res = new ArrayList<>(); backtrack(0, n, state, res, cols, diags1, diags2); return res; } ``` === "C#" ```csharp title="n_queens.cs" /* Алгоритм бэктрекинга: n ферзей */ void Backtrack(int row, int n, List> state, List>> res, bool[] cols, bool[] diags1, bool[] diags2) { // Когда все строки уже обработаны, записать решение if (row == n) { List> copyState = []; foreach (List sRow in state) { copyState.Add(new List(sRow)); } res.Add(copyState); return; } // Обойти все столбцы for (int col = 0; col < n; col++) { // Вычислить главную и побочную диагонали, соответствующие этой клетке int diag1 = row - col + n - 1; int diag2 = row + col; // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Попытка: поставить ферзя в эту клетку state[row][col] = "Q"; cols[col] = diags1[diag1] = diags2[diag2] = true; // Перейти к размещению следующей строки Backtrack(row + 1, n, state, res, cols, diags1, diags2); // Откат: восстановить эту клетку как пустую state[row][col] = "#"; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Решить задачу о n ферзях */ List>> NQueens(int n) { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку List> state = []; for (int i = 0; i < n; i++) { List row = []; for (int j = 0; j < n; j++) { row.Add("#"); } state.Add(row); } bool[] cols = new bool[n]; // Отмечать, есть ли ферзь в столбце bool[] diags1 = new bool[2 * n - 1]; // Отмечать наличие ферзя на главной диагонали bool[] diags2 = new bool[2 * n - 1]; // Отмечать наличие ферзя на побочной диагонали List>> res = []; Backtrack(0, n, state, res, cols, diags1, diags2); return res; } ``` === "Go" ```go title="n_queens.go" /* Алгоритм бэктрекинга: n ферзей */ func backtrack(row, n int, state *[][]string, res *[][][]string, cols, diags1, diags2 *[]bool) { // Когда все строки уже обработаны, записать решение if row == n { newState := make([][]string, len(*state)) for i, _ := range newState { newState[i] = make([]string, len((*state)[0])) copy(newState[i], (*state)[i]) } *res = append(*res, newState) return } // Обойти все столбцы for col := 0; col < n; col++ { // Вычислить главную и побочную диагонали, соответствующие этой клетке diag1 := row - col + n - 1 diag2 := row + col // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if !(*cols)[col] && !(*diags1)[diag1] && !(*diags2)[diag2] { // Попытка: поставить ферзя в эту клетку (*state)[row][col] = "Q" (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = true, true, true // Перейти к размещению следующей строки backtrack(row+1, n, state, res, cols, diags1, diags2) // Откат: восстановить эту клетку как пустую (*state)[row][col] = "#" (*cols)[col], (*diags1)[diag1], (*diags2)[diag2] = false, false, false } } } /* Решить задачу о n ферзях */ func nQueens(n int) [][][]string { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку state := make([][]string, n) for i := 0; i < n; i++ { row := make([]string, n) for i := 0; i < n; i++ { row[i] = "#" } state[i] = row } // Отмечать, есть ли ферзь в столбце cols := make([]bool, n) diags1 := make([]bool, 2*n-1) diags2 := make([]bool, 2*n-1) res := make([][][]string, 0) backtrack(0, n, &state, &res, &cols, &diags1, &diags2) return res } ``` === "Swift" ```swift title="n_queens.swift" /* Алгоритм бэктрекинга: n ферзей */ func backtrack(row: Int, n: Int, state: inout [[String]], res: inout [[[String]]], cols: inout [Bool], diags1: inout [Bool], diags2: inout [Bool]) { // Когда все строки уже обработаны, записать решение if row == n { res.append(state) return } // Обойти все столбцы for col in 0 ..< n { // Вычислить главную и побочную диагонали, соответствующие этой клетке let diag1 = row - col + n - 1 let diag2 = row + col // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if !cols[col] && !diags1[diag1] && !diags2[diag2] { // Попытка: поставить ферзя в эту клетку state[row][col] = "Q" cols[col] = true diags1[diag1] = true diags2[diag2] = true // Перейти к размещению следующей строки backtrack(row: row + 1, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) // Откат: восстановить эту клетку как пустую state[row][col] = "#" cols[col] = false diags1[diag1] = false diags2[diag2] = false } } } /* Решить задачу о n ферзях */ func nQueens(n: Int) -> [[[String]]] { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку var state = Array(repeating: Array(repeating: "#", count: n), count: n) var cols = Array(repeating: false, count: n) // Отмечать, есть ли ферзь в столбце var diags1 = Array(repeating: false, count: 2 * n - 1) // Отмечать наличие ферзя на главной диагонали var diags2 = Array(repeating: false, count: 2 * n - 1) // Отмечать наличие ферзя на побочной диагонали var res: [[[String]]] = [] backtrack(row: 0, n: n, state: &state, res: &res, cols: &cols, diags1: &diags1, diags2: &diags2) return res } ``` === "JS" ```javascript title="n_queens.js" /* Алгоритм бэктрекинга: n ферзей */ function backtrack(row, n, state, res, cols, diags1, diags2) { // Когда все строки уже обработаны, записать решение if (row === n) { res.push(state.map((row) => row.slice())); return; } // Обойти все столбцы for (let col = 0; col < n; col++) { // Вычислить главную и побочную диагонали, соответствующие этой клетке const diag1 = row - col + n - 1; const diag2 = row + col; // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Попытка: поставить ферзя в эту клетку state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // Перейти к размещению следующей строки backtrack(row + 1, n, state, res, cols, diags1, diags2); // Откат: восстановить эту клетку как пустую state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Решить задачу о n ферзях */ function nQueens(n) { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку const state = Array.from({ length: n }, () => Array(n).fill('#')); const cols = Array(n).fill(false); // Отмечать, есть ли ферзь в столбце const diags1 = Array(2 * n - 1).fill(false); // Отмечать наличие ферзя на главной диагонали const diags2 = Array(2 * n - 1).fill(false); // Отмечать наличие ферзя на побочной диагонали const res = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } ``` === "TS" ```typescript title="n_queens.ts" /* Алгоритм бэктрекинга: n ферзей */ function backtrack( row: number, n: number, state: string[][], res: string[][][], cols: boolean[], diags1: boolean[], diags2: boolean[] ): void { // Когда все строки уже обработаны, записать решение if (row === n) { res.push(state.map((row) => row.slice())); return; } // Обойти все столбцы for (let col = 0; col < n; col++) { // Вычислить главную и побочную диагонали, соответствующие этой клетке const diag1 = row - col + n - 1; const diag2 = row + col; // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Попытка: поставить ферзя в эту клетку state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // Перейти к размещению следующей строки backtrack(row + 1, n, state, res, cols, diags1, diags2); // Откат: восстановить эту клетку как пустую state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Решить задачу о n ферзях */ function nQueens(n: number): string[][][] { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку const state = Array.from({ length: n }, () => Array(n).fill('#')); const cols = Array(n).fill(false); // Отмечать, есть ли ферзь в столбце const diags1 = Array(2 * n - 1).fill(false); // Отмечать наличие ферзя на главной диагонали const diags2 = Array(2 * n - 1).fill(false); // Отмечать наличие ферзя на побочной диагонали const res: string[][][] = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } ``` === "Dart" ```dart title="n_queens.dart" /* Алгоритм бэктрекинга: n ферзей */ void backtrack( int row, int n, List> state, List>> res, List cols, List diags1, List diags2, ) { // Когда все строки уже обработаны, записать решение if (row == n) { List> copyState = []; for (List sRow in state) { copyState.add(List.from(sRow)); } res.add(copyState); return; } // Обойти все столбцы for (int col = 0; col < n; col++) { // Вычислить главную и побочную диагонали, соответствующие этой клетке int diag1 = row - col + n - 1; int diag2 = row + col; // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Попытка: поставить ферзя в эту клетку state[row][col] = "Q"; cols[col] = true; diags1[diag1] = true; diags2[diag2] = true; // Перейти к размещению следующей строки backtrack(row + 1, n, state, res, cols, diags1, diags2); // Откат: восстановить эту клетку как пустую state[row][col] = "#"; cols[col] = false; diags1[diag1] = false; diags2[diag2] = false; } } } /* Решить задачу о n ферзях */ List>> nQueens(int n) { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку List> state = List.generate(n, (index) => List.filled(n, "#")); List cols = List.filled(n, false); // Отмечать, есть ли ферзь в столбце List diags1 = List.filled(2 * n - 1, false); // Отмечать наличие ферзя на главной диагонали List diags2 = List.filled(2 * n - 1, false); // Отмечать наличие ферзя на побочной диагонали List>> res = []; backtrack(0, n, state, res, cols, diags1, diags2); return res; } ``` === "Rust" ```rust title="n_queens.rs" /* Алгоритм бэктрекинга: n ферзей */ fn backtrack( row: usize, n: usize, state: &mut Vec>, res: &mut Vec>>, cols: &mut [bool], diags1: &mut [bool], diags2: &mut [bool], ) { // Когда все строки уже обработаны, записать решение if row == n { res.push(state.clone()); return; } // Обойти все столбцы for col in 0..n { // Вычислить главную и побочную диагонали, соответствующие этой клетке let diag1 = row + n - 1 - col; let diag2 = row + col; // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if !cols[col] && !diags1[diag1] && !diags2[diag2] { // Попытка: поставить ферзя в эту клетку state[row][col] = "Q".into(); (cols[col], diags1[diag1], diags2[diag2]) = (true, true, true); // Перейти к размещению следующей строки backtrack(row + 1, n, state, res, cols, diags1, diags2); // Откат: восстановить эту клетку как пустую state[row][col] = "#".into(); (cols[col], diags1[diag1], diags2[diag2]) = (false, false, false); } } } /* Решить задачу о n ферзях */ fn n_queens(n: usize) -> Vec>> { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку let mut state: Vec> = vec![vec!["#".to_string(); n]; n]; let mut cols = vec![false; n]; // Отмечать, есть ли ферзь в столбце let mut diags1 = vec![false; 2 * n - 1]; // Отмечать наличие ферзя на главной диагонали let mut diags2 = vec![false; 2 * n - 1]; // Отмечать наличие ферзя на побочной диагонали let mut res: Vec>> = Vec::new(); backtrack( 0, n, &mut state, &mut res, &mut cols, &mut diags1, &mut diags2, ); res } ``` === "C" ```c title="n_queens.c" /* Алгоритм бэктрекинга: n ферзей */ void backtrack(int row, int n, char state[MAX_SIZE][MAX_SIZE], char ***res, int *resSize, bool cols[MAX_SIZE], bool diags1[2 * MAX_SIZE - 1], bool diags2[2 * MAX_SIZE - 1]) { // Когда все строки уже обработаны, записать решение if (row == n) { res[*resSize] = (char **)malloc(sizeof(char *) * n); for (int i = 0; i < n; ++i) { res[*resSize][i] = (char *)malloc(sizeof(char) * (n + 1)); strcpy(res[*resSize][i], state[i]); } (*resSize)++; return; } // Обойти все столбцы for (int col = 0; col < n; col++) { // Вычислить главную и побочную диагонали, соответствующие этой клетке int diag1 = row - col + n - 1; int diag2 = row + col; // Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if (!cols[col] && !diags1[diag1] && !diags2[diag2]) { // Попытка: поставить ферзя в эту клетку state[row][col] = 'Q'; cols[col] = diags1[diag1] = diags2[diag2] = true; // Перейти к размещению следующей строки backtrack(row + 1, n, state, res, resSize, cols, diags1, diags2); // Откат: восстановить эту клетку как пустую state[row][col] = '#'; cols[col] = diags1[diag1] = diags2[diag2] = false; } } } /* Решить задачу о n ферзях */ char ***nQueens(int n, int *returnSize) { char state[MAX_SIZE][MAX_SIZE]; // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { state[i][j] = '#'; } state[i][n] = '\0'; } bool cols[MAX_SIZE] = {false}; // Отмечать, есть ли ферзь в столбце bool diags1[2 * MAX_SIZE - 1] = {false}; // Отмечать наличие ферзя на главной диагонали bool diags2[2 * MAX_SIZE - 1] = {false}; // Отмечать наличие ферзя на побочной диагонали char ***res = (char ***)malloc(sizeof(char **) * MAX_SIZE); *returnSize = 0; backtrack(0, n, state, res, returnSize, cols, diags1, diags2); return res; } ``` === "Kotlin" ```kotlin title="n_queens.kt" /* Алгоритм бэктрекинга: n ферзей */ fun backtrack( row: Int, n: Int, state: MutableList>, res: MutableList>?>, cols: BooleanArray, diags1: BooleanArray, diags2: BooleanArray ) { // Когда все строки уже обработаны, записать решение if (row == n) { val copyState = mutableListOf>() for (sRow in state) { copyState.add(sRow.toMutableList()) } res.add(copyState) return } // Обойти все столбцы for (col in 0..>?> { // Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку val state = mutableListOf>() for (i in 0..() for (j in 0..>?>() backtrack(0, n, state, res, cols, diags1, diags2) return res } ``` === "Ruby" ```ruby title="n_queens.rb" ### Алгоритм бэктрекинга: n ферзей ### def backtrack(row, n, state, res, cols, diags1, diags2) # Когда все строки уже обработаны, записать решение if row == n res << state.map { |row| row.dup } return end # Обойти все столбцы for col in 0...n # Вычислить главную и побочную диагонали, соответствующие этой клетке diag1 = row - col + n - 1 diag2 = row + col # Отсечение: в столбце, главной диагонали и побочной диагонали этой клетки не должно быть ферзей if !cols[col] && !diags1[diag1] && !diags2[diag2] # Попытка: поставить ферзя в эту клетку state[row][col] = "Q" cols[col] = diags1[diag1] = diags2[diag2] = true # Перейти к размещению следующей строки backtrack(row + 1, n, state, res, cols, diags1, diags2) # Откат: восстановить эту клетку как пустую state[row][col] = "#" cols[col] = diags1[diag1] = diags2[diag2] = false end end end ### Решить задачу о n ферзях ### def n_queens(n) # Инициализировать доску размера n*n, где 'Q' обозначает ферзя, а '#' — пустую клетку state = Array.new(n) { Array.new(n, "#") } cols = Array.new(n, false) # Отмечать, есть ли ферзь в столбце diags1 = Array.new(2 * n - 1, false) # Отмечать наличие ферзя на главной диагонали diags2 = Array.new(2 * n - 1, false) # Отмечать наличие ферзя на побочной диагонали res = [] backtrack(0, n, state, res, cols, diags1, diags2) res end ``` ??? pythontutor "Визуализация кода"
Если размещать ферзей построчно $n$ раз, учитывая ограничение по столбцам, то начиная с первой строки и заканчивая последней мы получаем соответственно $n$, $n-1$, $\dots$, $2$, $1$ вариантов выбора, что дает $O(n!)$ времени. При записи решения нужно скопировать матрицу `state` и добавить ее в `res` , а копирование требует $O(n^2)$ времени. Следовательно, **общая временная сложность равна $O(n! \cdot n^2)$** . На практике обрезка по диагональным ограничениям дополнительно сильно уменьшает пространство поиска, поэтому фактическая эффективность часто лучше этой оценки. Массив `state` использует $O(n^2)$ пространства, а массивы `cols` , `diags1` и `diags2` используют по $O(n)$ пространства. Максимальная глубина рекурсии равна $n$ , что требует $O(n)$ памяти стека. Следовательно, **пространственная сложность равна $O(n^2)$** .