Приветствуется оптимизация алгоритма генератора судоку

Я создал рекурсивный алгоритм DFS для создания/решения досок судоку на Java, но его завершение занимает вечность, и объяснение/оптимизация будут приветствоваться. Я не могу себе представить, что создание доски судоку займет так много времени, особенно со всеми приложениями (хотя у них может быть база данных).

По сути, я просматриваю все ячейки, проверяя, удовлетворяет ли какая-либо из [1-9] ограничениям судоку, и возвращаюсь к тупиковым ветвям. Чтобы сохранить память и избежать копирования 2D-массива, который служит доской, при каждом вызове рекурсивного метода (а в этом дереве потенциально 81 * 9! листьев, если я не ошибаюсь...), я создал 2D матрица целочисленных стеков, в которой элемент помещается каждый раз, когда исследуется ветвь, и извлекается, если это тупик.

Ниже приведена реализация. Любые советы по ускорению приветствуются. Я делаю это как личное упражнение, и мне интересно, существует ли что-то асимптотически лучше.

Надеюсь, это не ужасно читать ниже .. Спасибо!

1) Реализация алгоритма: (обратите внимание, что значения находятся в "перемешанном" массиве [1-9] для создания уникальных досок.)

/**
 * Provides one solution to a board with an initial configuration, or <code>null</code> if there is none.
 * The search is randomized, s.t. the algorithm can serve to populate an empty board. 
 * 
 * @param initial The initial board given to solve.
 * @return The fully solved board, or null if no solution found.
 */
public static int[][] solveBoard (int[][] initial){
    return solveBoard(new StackedBoard(initial), 0, 0);
}

private static int[][] solveBoard (StackedBoard board, int xPos, int yPos){

    // base case - success
    int remaining = 81;
    for (int x = 0; x < 9; x++){
        for (int y = 0; y < 9; y++){
            if (board.peekAt(x, y) != Board.EMPTY){
                remaining--;
            }
        }
    }
    if (remaining == 0){
        return board.flatView();// the only creation of an array.
    }

    // recursive case
    for (int x = 0; x < 9; x++){
        for (int y = 0; y < 9; y++){
            if (board.peekAt(x, y) == Board.EMPTY){
                for (int val : getJumbledRandomVals()){
                    if (isMoveLegal(board, x, y, val)){
                        board.pushAt(x, y, val);
                        int[][] leafBoard = solveBoard(board, x, y);
                        if (leafBoard != null) {
                            return leafBoard;
                        }
                    }
                }
            }
        }
    }

    // base case - dead branch
    board.popAt(xPos, yPos);
    return null;
}

2) Реализация StackedBoard:

/**
 * Represents square boards with stacked int elements.
 */
class StackedBoard {

    ArrayList<ArrayList<Stack<Integer>>> xaxis = new ArrayList<ArrayList<Stack<Integer>>>();

    /**
     * 
     * @param init A square array - both dimensions of equal length, or <code>null</code> if no initialization.
     */
    public StackedBoard (int[][] init) {
        for (int i = 0; i < 9; i++){
            ArrayList<Stack<Integer>> yaxis = new ArrayList<Stack<Integer>>();
            xaxis.add(yaxis);

            for (int j = 0; j < 9; j++){
                Stack<Integer> stack = new Stack<Integer>();
                yaxis.add(stack);
            }
        }

        if (init != null){
            for (int x = 0; x < init.length; x++){
                for (int y = 0; y < init.length; y++){
                    getStackAt(x, y).push(init[x][y]);
                }
            }   
        }
    }

    public Stack<Integer> getStackAt (int x, int y){
        return xaxis.get(x).get(y);
    }

    public int peekAt (int x, int y){
        return getStackAt(x, y).peek();
    }

    public void pushAt (int x, int y, int value){
        getStackAt(x, y).push(value);
    }

    public Integer popAt (int x, int y){
        try {
            return getStackAt(x, y).pop();  
        } catch (EmptyStackException e){
            // shhhhh!
            return Board.EMPTY;
        }

    }

    /**
     * Flat view of the stacked-board; peek of the top elements.
     */
    public int[][] flatView (){
        int[][] view = new int[xaxis.size()][xaxis.size()];

        for (int x = 0; x < xaxis.size(); x++){
            for (int y = 0; y < xaxis.size(); y++){
                view[x][y] = getStackAt(x, y).peek();
            }
        }

        return view;
    }
}

3) Реализация функции ограничений:

/**
 * Is the move legal on the suggested board?
 * 
 * @param board The board.
 * @param x The X coordinate, starts with 0.
 * @param y The Y coordinate, starts with 0.
 * @param value The value.
 * @return <code>true</code> iff the move is legal.
 */
private static boolean isMoveLegal (StackedBoard board, int x, int y, int value){
    // by value
    if (1 > value || value > 9){
        return false;
    }

    // by column
    for (int i = 0; i < 9; i++){
        if (board.peekAt(i, y) == value){
            return false;
        }
    }

    // by row
    for (int i = 0; i < 9; i++){
        if (board.peekAt(x, i) == value){
            return false;
        }
    }

    // by lil square
    int lowerX = x < 3 ? 0 : (x < 6 ? 3 : 6);
    int upperX = lowerX + 2;
    int lowerY = y < 3 ? 0 : (y < 6 ? 3 : 6);
    int upperY = lowerY + 2;

    for (int i = lowerX; i <= upperX; i++){
        for (int j = lowerY; j <= upperY; j++){
            if (board.peekAt(i, j) == value){
                return false;
            }
        }
    }

    return true;
}

person vivri    schedule 01.11.2013    source источник
comment
асимптотически лучше?   -  person Paul Draper    schedule 01.11.2013
comment
Позвольте мне на секунду включить свой собственный пост в блоге. Судоку легко решается очень быстро, как точная обложка: gieseanw.wordpress.com /2011/06/16/разгадывание-судоку-пересмотрено   -  person AndyG    schedule 01.11.2013
comment
этот вопрос, вероятно, должен быть на codereview.stackexchange.com   -  person asermax    schedule 01.11.2013


Ответы (1)


Если вы готовы сделать полный левый поворот, есть гораздо лучшие алгоритмы для генерации/решения судоку. Известно, что алгоритм танцующих ссылок Дона Кнута чрезвычайно хорош в быстром переборе всех решений судоку (после того, как они переформулирован как примеры задачи точного покрытия) и обычно используется в качестве основного алгоритма в Решатели судоку, и на это стоит обратить внимание. Это требует много указателя/справочной гимнастики, но относительно коротко для кода.

Если вы хотите придерживаться своего существующего подхода, полезной оптимизацией будет всегда выбирать наиболее ограниченную ячейку в качестве следующего значения для заполнения. Это, вероятно, вызовет каскад «принудительных перемещений», которые помогут вам уменьшить размер вашего пространство поиска, хотя это только эвристика.

Надеюсь это поможет!

person templatetypedef    schedule 01.11.2013
comment
Благодарю вас! Я рассмотрю оба решения! - person vivri; 01.11.2013