Алгоритм грубой силы для создания доски судоку

Что я развиваю, так это то, что изначально вся доска судоку пуста. Одна из случайных ячеек (из 81) заполняется случайным значением (1-9).

Теперь я хочу заполнить все оставшиеся ячейки, используя подход грубой силы.
Из того, что я узнал после поиска в Google, мы должны начать с первой ячейки и заполнить ее 1 (если она действительна), а затем заполнить вторую ячейку. с 2 (если это допустимо, мы начнем проверку с числа, большего, чем последняя заполненная ячейка, которая в данном случае равна 1, как только мы достигнем 9, мы сбрасываем его с 1).

Дело в том, что он не работает должным образом!

Может ли кто-нибудь связать меня с точным алгоритмом.


person Akshay J    schedule 28.08.2010    source источник


Ответы (6)


Недавно я сделал серию статей в своем блоге о создании решателя судоку на C#; вы, вероятно, можете адаптировать простой алгоритм поиска с возвратом, который я представляю, для ваших целей.

http://blogs.msdn.com/b/ericlippert/archive/tags/graph+colouring/

person Eric Lippert    schedule 28.08.2010

Вот реализация подхода с возвратом:

import java.util.Random;

public class Sudoku {

    public static void main(String[] args) {
        Random rand = new Random();
        int r = rand.nextInt(9);
        int c = rand.nextInt(9);
        int value = rand.nextInt(9) + 1;
        Board board = new Board();
        board.set(r, c, value);
        System.out.println(board);
        solve(board, 0);
        System.out.println(board);
    }

    private static boolean solve(Board board, int at) {
        if (at == 9*9)
            return true;
        int r = at / 9;
        int c = at % 9;
        if (board.isSet(r, c))
            return solve(board, at + 1);
        for (int value = 1; value <= 9; value++) {
            if (board.canSet(r, c, value)) {
                board.set(r, c, value);
                if (solve(board, at + 1))
                    return true;
                board.unSet(r, c);
            }
        }
        return false;
    }

    static class Board {
        private int[][] board = new int[9][9];
        private boolean[][] rs = new boolean[9][10];
        private boolean[][] cs = new boolean[9][10];
        private boolean[][][] bs = new boolean[3][3][10];
        public Board() {}
        public boolean canSet(int r, int c, int value) {
            return !isSet(r, c) && !rs[r][value] && !cs[c][value] && !bs[r/3][c/3][value];
        }
        public boolean isSet(int r, int c) {
            return board[r][c] != 0;
        }
        public void set(int r, int c, int value) {
            if (!canSet(r, c, value))
                throw new IllegalArgumentException();
            board[r][c] = value;
            rs[r][value] = cs[c][value] = bs[r/3][c/3][value] = true;
        }
        public void unSet(int r, int c) {
            if (isSet(r, c)) {
                int value = board[r][c];
                board[r][c] = 0;
                rs[r][value] = cs[c][value] = bs[r/3][c/3][value] = false;
            }
        }
        public String toString() {
            StringBuilder ret = new StringBuilder();
            for (int r = 0; r < 9; r++) {
                for (int c = 0; c < 9; c++)
                    ret.append(board[r][c]);
                ret.append("\n");
            }
            return ret.toString();
        }
    }
}
person Sheldon L. Cooper    schedule 28.08.2010

Есть несколько алгоритмов, описанных в Алгоритмика судоку. То, что вы описываете, звучит как обратная обратная связь.

person Bill the Lizard    schedule 28.08.2010

Взгляните на следующее. Обратите внимание, что я не запускал его, поэтому я не могу поручиться за его утверждения:

http://www.codeproject.com/KB/game/SudokuGen.aspx

Код находится в VB.NET, но алгоритм будет таким же в C#.

Здесь есть версия С#:

http://www.codeproject.com/KB/game/sudokuincsharp.aspx

Ссылка, предоставленная @Bill the Lizard, хорошо объясняет вещи, в отличие от ссылок на реализацию, которые я предоставил выше.

person Edward Leno    schedule 28.08.2010

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

Я использовал свои глаза, чтобы проверить это, и, поскольку я не могу понять рекурсивный метод, хотя рекурсия относительно понятна:

Этот метод, который я как бы написал с некоторым руководством, имел ошибку в проверке сетки, когда я ее нашел, теперь она работает. Я утверждаю это, потому что трудно найти полный и работающий код. SDK для iOS.

#define WIDTH 9
#define HEIGHT 9


@interface ViewController ()
//- (BOOL) numberConflicts:(int)testNum;
- (BOOL) number:(int)n conflictsWithRow:(int)r;
- (BOOL) number:(int)n conflictsWithColumn:(int)c;
- (BOOL) number:(int)n conflictsWithSquareInPointX:(int)x andPointY:(int)y;
- (BOOL) number:(int)n conflictsAtGridPointX:(int)xPoint andPointY:(int)yPoint;
- (int) incrementSudokuValue:(int)v;
@end


static int sudoku[WIDTH][HEIGHT];

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    /// Initialize it
    for (int x = 0; x < WIDTH; x++)
    {
        for (int y = 0; y < HEIGHT; y++)
        {
            sudoku[x][y] = 0;
        }
    }
    ///




    int tries = 0;
    for (int j = 0; j < HEIGHT; j++)
    {
        for (int i = 0; i < WIDTH; i++)
        {
            int num = arc4random()%9 + 1;
            while ([self number:num conflictsAtGridPointX:i andPointY:j])
            {
                num = [self incrementSudokuValue:num];
                tries++;
                if (tries > 10) { //restart the column
                    tries = 0;

                    for(int count = 0; count < WIDTH; count++)
                    {
                        sudoku[count][j] = 0;

                    }

                    i = 0;

                }
            }
            if(sudoku[i][j] == 0)
               sudoku[i][j] = num; 

            tries = 0;
            for (int y = 0; y < HEIGHT; y++)
            {
                for (int x = 0; x < WIDTH; x++)
                {
                    printf("%i ", sudoku[x][y]);
                }

                printf("\n");
            }

            printf("\n");

        }
    }

    for (int x = 0; x < WIDTH; x++)
    {
        for (int y = 0; y < HEIGHT; y++)
        {
            printf("%i ", sudoku[y][x]);
        }
        printf("\n"); //newline
    }

    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}



- (BOOL) number:(int)n conflictsWithRow:(int)r;
{
    for (int y = 0; y < HEIGHT; y++) {
        if (sudoku[y][r] == n) {
            return YES;
        }
    }

    return NO;
}

- (BOOL) number:(int)n conflictsWithColumn:(int)c;
{
    for (int x = 0; x < WIDTH; x++) {
        if (sudoku[c][x] == n) {
            return YES;
        }
    }

    return NO;
}

- (BOOL) number:(int)n conflictsAtGridPointX:(int)xPoint andPointY:(int)yPoint;
{
    if ([self number:n conflictsWithRow:yPoint])
    {
        return YES;
    }

    if ([self number:n conflictsWithColumn:xPoint])
    {
        return YES;
    }

    if ([self number:n conflictsWithSquareInPointX:xPoint andPointY:yPoint]) {
        return YES;
    }


    return NO;
}

- (BOOL) number:(int)n conflictsWithSquareInPointX:(int)x andPointY:(int)y;
{
    int leftX = x - (x % 3); //used to use int division
    // leftX *= 3;
    int topY = y - (y % 3);
    // topY *= 3;

    int rightX = leftX + 2;
    int bottomY = topY + 2;

    for(int subY = topY; subY <= bottomY; subY++) //bug was here, used < instead of less N equal to...
    {
        for ( int subX = leftX; subX <= rightX; subX++)
        {
            if (sudoku[subX][subY] == n) {
                return YES;
            }
        }
    }

    NSLog(@"Testing grid at %i, %i", x/3, y/3);
    NSLog(@"LeftX: %i TopY: %i", leftX, topY);

    return NO;
}

- (int) incrementSudokuValue:(int)v;
{
    if (v < 9) {
        v++;
        return v;
    }
    return 1;
}

Примечание. Файл заголовка пуст, вставьте его в приложение iOS single View, если хотите.

Предупреждение: может быть бесконечный цикл (иногда это происходит, но очень быстро), может потребоваться еще одна более глобальная переменная «попытки» и перезапустить алгоритм в качестве безопасности или дать ему семя / сделать и то, и другое.

редактировать: ниже должно быть безопасно от бесконечных циклов, если исходная сетка разрешима (или не существует)


#define WIDTH 9
#define HEIGHT 9

@interface ViewController ()
//- (BOOL) numberConflicts:(int)testNum;
- (BOOL) number:(int)n conflictsWithRow:(int)r;
- (BOOL) number:(int)n conflictsWithColumn:(int)c;
- (BOOL) number:(int)n conflictsWithSquareInPointX:(int)x andPointY:(int)y;
- (BOOL) number:(int)n conflictsAtGridPointX:(int)xPoint andPointY:(int)yPoint;
- (int) incrementSudokuValue:(int)v;
@end

static int sudoku[WIDTH][HEIGHT];

@implementation ViewController

- (BOOL) fillGridWithNext:(int)next;
{

    for (int y = 0; y < HEIGHT; y++)
    {
        for (int x = 0; x < WIDTH; x++)
        {
            if (sudoku[x][y] != 0)
            {
                if (x == 8 && y == 8) {
                    return YES;
                }
                continue;
            }

            for (int count = 0; count < (HEIGHT-1); count++)
            {
                if ([self number:next conflictsAtGridPointX:x andPointY:y])
                {
                    next = [self incrementSudokuValue:next];
                }
                else
                {
                    sudoku[x][y] = next;
                    if( [self fillGridWithNext:arc4random()%9+1])
                    {
                        return YES;
                    }

                }
            }
            sudoku[x][y] = 0;
            return NO;
        }
    }

    return NO;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    /// Initialize it
    for (int x = 0; x < WIDTH; x++)
    {
        for (int y = 0; y < HEIGHT; y++)
        {
            sudoku[x][y] = 0;
        }
    }

    sudoku[0][0]=9;
    int next;
    next = (arc4random()%9)+1;

    if( [self fillGridWithNext:next]) //seeded
    {
        NSLog(@"Solved");
    }
    else
    {
        NSLog(@"No solution");
    }


    for (int x = 0; x < WIDTH; x++)
    {
        for (int y = 0; y < HEIGHT; y++)
        {
            printf("%i ", sudoku[y][x]);
        }
        printf("\n"); //newline
    }
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}



- (BOOL) number:(int)n conflictsWithRow:(int)r;
{
    for (int y = 0; y < HEIGHT; y++) {
        if (sudoku[y][r] == n) {
            return YES;
        }
    }

    return NO;
}

- (BOOL) number:(int)n conflictsWithColumn:(int)c;
{
    for (int x = 0; x < WIDTH; x++) {
        if (sudoku[c][x] == n) {
            return YES;
        }
    }

    return NO;
}

- (BOOL) number:(int)n conflictsAtGridPointX:(int)xPoint andPointY:(int)yPoint;
{
    if ([self number:n conflictsWithRow:yPoint])
    {
        return YES;
    }

    if ([self number:n conflictsWithColumn:xPoint])
    {
        return YES;
    }

    if ([self number:n conflictsWithSquareInPointX:xPoint andPointY:yPoint]) {
        return YES;
    }


    return NO;
}

- (BOOL) number:(int)n conflictsWithSquareInPointX:(int)x andPointY:(int)y;
{
    int leftX = x - (x % 3); //used to use int division
    // leftX *= 3;
    int topY = y - (y % 3);
    // topY *= 3;

    int rightX = leftX + 2;
    int bottomY = topY + 2;

    for(int subY = topY; subY <= bottomY; subY++) //bug was here, used < instead of less N equal to...
    {
        for ( int subX = leftX; subX <= rightX; subX++)
        {
            if (sudoku[subX][subY] == n) {
                return YES;
            }
        }
    }

    NSLog(@"Testing grid at %i, %i", x/3, y/3);
    NSLog(@"LeftX: %i TopY: %i", leftX, topY);

    return NO;
}

- (int) incrementSudokuValue:(int)v;
{
    if (v < 9) {
        v++;
        return v;
    }
    return 1;
}

@end

Резюме: первая версия имеет недостатки, но (в основном) выполняет свою работу. Он генерирует каждую строку случайным образом, если строка недействительна, она стирается и начинается заново. Это уничтожит исходные сетки и может продолжаться вечно, но работает в большинстве случаев.

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

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

person Stephen J    schedule 21.11.2012
comment
Чтобы предотвратить блокировку алгоритма, когда он находится в 3-й строке любого набора из 3 строк, я предполагаю, что он должен использовать существующие номера двух других сеток в качестве доступных номеров. Это предотвращает его «запирание». Если это верно, этот алгоритм становится совершенно случайным генератором. Я думаю, что решатель - это просто вопрос заполнения этой сетки. Однако он не оптимизирован. Такое изменение сделало бы уравнение идеальным, исключив все возможные блокировки для продвижения вперед и необходимость использования секции try++. Я не могу это доказать, извините, еще не математический человек - person Stephen J; 21.11.2012

Этот простой алгоритм случайного блуждания тоже должен работать (но он неэффективен — используйте на свой страх и риск!!!):

EDIT: добавлено исправление для неразрешимых решений.

For each empty cell in grid
    array = Get_Legal_Numbers_for_cell(row,col);
    If (array is empty) {
        Clear_All_cells()
    } else {
        number = Random_number_from(array);
        Put_Number_in_Cell(number);
    }

ИЗМЕНИТЬ 2

Если кому-то интересно здесь описаны методы решения судоку со случайным поиском .

person Agnius Vasiliauskas    schedule 28.08.2010
comment
Нет, это не должно работать. Для ячейки может не быть разрешенных номеров. Рассмотрим этот пример: 123456789 456123___ Какие допустимые числа для пустых ячеек второй строки? - person Sheldon L. Cooper; 31.08.2010
comment
Теперь это должно работать, - нам просто нужно перезапустить алгоритм, если для ячейки нет допустимых номеров. Конечно, это очень неэффективно. И да, я знаю другие эффективные подходы, такие как поиск с возвратом и тому подобное... - person Agnius Vasiliauskas; 31.08.2010
comment
На самом деле это не простой алгоритм — для начала, это случайный алгоритм, а не грубая сила (поэтому он может никогда не найти правильного решения). Кроме того, когда вы очищаете все ячейки и перезапускаете, как Get_Legal_Numbers_for_cell решает, какое значение изменить? Потому что одно из введенных вами значений n может быть неправильным, но как узнать, какое из них нужно изменить? К тому времени, когда вы конкретизируете свои методы, я думаю, вы обнаружите, что это далеко не просто. - person Dan Puzey; 31.08.2010
comment
@Dan Puzey Get_Legal_Numbers_for_cell вообще не вычисляет, какое значение нужно изменить. Он просто получает ВСЕ допустимые числа для текущей пустой ячейки. Если таких чисел нет - очищаем всю сетку и перезапускаем алгоритм. В чем проблема с этим методом случайного блуждания? - person Agnius Vasiliauskas; 31.08.2010
comment
Итак, вы очищаете сетку и перезапускаетесь случайным образом — что, если вы снова и снова запускаете одну и ту же комбинацию? Что делать, если вы никогда не найдете решение? Я имею в виду, что есть перебор всех доступных комбинаций, но ваш алгоритм на самом деле менее эффективен! - person Dan Puzey; 31.08.2010
comment
О повторении одной и той же комбинации снова и снова - согласно теории вероятностей, общая вероятность того, что первая строка будет сгенерирована так же, как и раньше, составляет: 1/9 * 1/8 *... * 1/2 * 1/1 ~ 0,0000027. это - ОЧЕНЬ маловероятно, что второй перезапуск алгоритма снова и снова будет давать одно и то же тупиковое решение. Насчет никогда не находить решения - для исследования этого вопроса необходимо провести эксперимент с этим алгоритмом, чтобы выяснить соотношение тупиковых решений/общего_поколения_попыток. Как вы определили, что этот алгоритм менее эффективен, чем грубая сила? - person Agnius Vasiliauskas; 31.08.2010
comment
См. мой РЕДАКТИРОВАТЬ 2 - я почти уверен - если есть методы стохастического решения судоку, то должны быть аналогичные стохастические методы для создания полной доски судоку. - person Agnius Vasiliauskas; 31.08.2010
comment
@ Дэн Пьюзи, это судоку, а не латинские квадраты. - person Sheldon L. Cooper; 31.08.2010
comment
@Sheldon: акк, извини - так оно и есть :-) facepalm - person Dan Puzey; 31.08.2010
comment
К вашему сведению, я сгенерировал 10 000 судоку с помощью этого метода. Для каждого поколения в среднем он посещал 10389,64 ячеек при 319,66 перезапусках. Моя реализация заполняет ячейки сверху вниз и слева направо. - person Sheldon L. Cooper; 31.08.2010
comment
Эти цифры дают оценку ок. 1 шанс из 320 найти решение при каждой попытке. - person Sheldon L. Cooper; 31.08.2010
comment
Я впечатлен, кажется, случайное блуждание не так уж и плохо :) - person Agnius Vasiliauskas; 31.08.2010