Задача N-Queens з використанням зворотного відстеження в Java/C++

Вступ

Завдання N-Ферзів є відомою головоломкою в галузі обчислювальної геометрії, яка вимагає розміщення N ферзів на шахівниці розміром NxN таким чином, щоб жоден з них не загрожував іншому. Ферзі можуть загрожувати один одному по горизонталі, вертикалі та діагоналі. Ця задача, запропонована Готхольдом Ейзенштейном у 1850 році, досі залишається цікавою як для любителів головоломок, так і для дослідників в академічних колах.

У цьому огляді ми розглянемо вирішення задачі N-Ферзів з використанням алгоритму зворотного відстеження, продемонстрованого на прикладах Java та C++. Спершу ми детально розглянемо принцип зворотного відстеження, а потім перейдемо до реалізації цього алгоритму в обох мовах програмування. Також ми надамо приклади коду для наочної ілюстрації процесу розв’язання задачі та обговоримо способи його оптимізації.

Суть Алгоритму Зворотного Відстеження

Зворотне відстеження є алгоритмом для вирішення комбінаторних завдань оптимізації та пошуку. Його дія базується на рекурсивному дослідженні всіх можливих комбінацій, при цьому зберігаються потенційні розв’язки, а ті, що не відповідають умовам, відкидаються. Алгоритм зворотного відстеження включає такі ключові складові:

  • Кандидати: Набір усіх можливих варіантів дій на кожному етапі алгоритму.
  • Розв’язки: Список знайдених конфігурацій, що задовольняють задані обмеження.
  • Конфігурація: Поточна комбінація обраних варіантів, від якої алгоритм продовжує пошук.
  • Обмеження: Набір правил, що використовуються для перевірки придатності кожного потенційного розв’язку.

Реалізація на Java

Реалізація алгоритму зворотного відстеження на Java для розв’язання задачі N-Ферзів може бути представлена наступним чином:

import java.util.ArrayList;
import java.util.List;

public class NQueens {
    private static List<List<Integer>> solutions = new ArrayList<>();

    public static void main(String[] args) {
        int n = 8;
        solveNQueens(n);
        // Друк усіх знайдених рішень (не обов'язково для кожного n, можна прибрати або переробити)
        for(List<Integer> solution : solutions) {
            System.out.println(solution);
        }
    }

    public static void solveNQueens(int n) {
        int[] board = new int[n];  // Масив для збереження позицій ферзів у рядках
        solveNQueens(board, 0);
    }

    private static void solveNQueens(int[] board, int row) {
        // Якщо всі ферзі розміщені, записуємо рішення
        if (row == board.length) {
            solutions.add(copyBoard(board));
            return;
        }

        // Перебираємо всі можливі стовпці для поточного рядка
        for (int col = 0; col < board.length; col++) {
            if (isSafe(board, row, col)) {
                board[row] = col;  // Розміщуємо ферзя
                solveNQueens(board, row + 1);  // Рекурсивний виклик для наступного рядка
                board[row] = 0;  // Видаляємо ферзя після повернення з рекурсії
            }
        }
    }

    // Перевірка безпеки позиції (row, col) для розміщення ферзя
    private static boolean isSafe(int[] board, int row, int col) {
        // Перевірка вертикального конфлікту
        for (int i = 0; i < row; i++) {
            if (board[i] == col) {
                return false;
            }
        }

        // Перевірка конфлікту по діагоналі (північний-схід)
        int i = row - 1;
        int j = col + 1;
        while (i >= 0 && j < board.length) {
            if (board[i] == j) {
                return false;
            }
            i--;
            j++;
        }

        // Перевірка конфлікту по діагоналі (північний-захід)
        i = row - 1;
        j = col - 1;
        while (i >= 0 && j >= 0) {
            if (board[i] == j) {
                return false;
            }
            i--;
            j--;
        }

        return true; // Позиція безпечна
    }

    // Копіювання масиву позицій ферзів
    private static List<Integer> copyBoard(int[] board) {
        List<Integer> copy = new ArrayList<>();
        for (int i : board) {
            copy.add(i);
        }
        return copy;
    }
}

Реалізація на C++

Аналогічна реалізація алгоритму зворотного відстеження на C++ для вирішення задачі N-Ферзів виглядає так:

#include <iostream>
#include <vector>

using namespace std;

vector<vector<int>> solutions;  // Список рішень

bool isSafe(int board[][N], int row, int col) {
    // Перевірка конфлікту по вертикалі
    for (int i = 0; i < row; i++) {
        if (board[i][col] == 1) {
            return false;
        }
    }

    // Перевірка конфлікту по діагоналі (північний-схід)
    int i = row - 1;
    int j = col + 1;
    while (i >= 0 && j < N) {
        if (board[i][j] == 1) {
            return false;
        }
        i--;
        j++;
    }

    // Перевірка конфлікту по діагоналі (північний-захід)
    i = row - 1;
    j = col - 1;
    while (i >= 0 && j >= 0) {
        if (board[i][j] == 1) {
            return false;
        }
        i--;
        j--;
    }

    return true;
}

void solveNQueens(int board[][N], int row) {
    // Якщо всі ферзі розміщені, записуємо рішення
    if (row == N) {
        vector<int> solution;
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                if (board[i][j] == 1) {
                    solution.push_back(j + 1);
                }
            }
        }
        solutions.push_back(solution);
        return;
    }

    // Перебираємо всі можливі стовпці для поточного рядка
    for (int col = 0; col < N; col++) {
        if (isSafe(board, row, col)) {
            board[row][col] = 1;  // Розміщуємо ферзя
            solveNQueens(board, row + 1);  // Рекурсивний виклик для наступного рядка
            board[row][col] = 0;  // Видаляємо ферзя після повернення з рекурсії
        }
    }
}

int main() {
    const int n = 8; // розмір дошки
    int board[n][n] = {0};
    solveNQueens(board, 0);

        // Друк усіх знайдених рішень (не обов'язково для кожного n, можна прибрати або переробити)
       for (const auto& solution : solutions) {
            for (int pos : solution) {
                cout << pos << " ";
            }
            cout << endl;
       }

    return 0;
}

Висновок

Задача N-Ферзів є класичним прикладом завдання, яке можна успішно вирішити за допомогою алгоритму зворотного відстеження. Наведені реалізації на Java та C++ наочно демонструють ефективність цього підходу та можуть бути легко адаптовані для інших комбінаторних задач оптимізації та пошуку.

Зворотне відстеження є потужним інструментом для розв’язання задач із скінченною кількістю розв’язків, але його складність може зростати експоненційно в найгіршому випадку. Для великих значень N існує ряд оптимізаційних технік, що дозволяють скоротити час виконання.