Почему моя программа судоку не возвращает результат?

Итак, я попытался реализовать судоку с помощью алгоритма поиска с возвратом. Я не понимаю, почему мой код не дает ожидаемого результата.

Что я сделал, так это то, что я создал цикл, в котором он проверяет наличие пустой ячейки (обозначенной 0) в судоку. Когда он его находит, координаты передаются в функцию под названием possibleEntriescheck (). Эта функция записывает в глобально объявленный массив с именем possibleEntries [9] цифры, которые, возможно, могут быть заполнены в ячейке, координаты которой передаются изначально.

Я узнал этот алгоритм из этих видео: https://www.youtube.com/watch?v=NuodN41aK3g https://www.youtube.com/watch?v=QI0diwmx3OY

Ожидаемый результат - решенная судоку. Это не работает ожидаемо. Скорее зависает. Небольшая помощь значила бы очень много. Спасибо.

#include <stdio.h>
#include <stdlib.h>
int board[9][9] = {
                  {3, 0, 6, 5, 0, 8, 4, 0, 0},
                  {5, 2, 0, 0, 0, 0, 0, 0, 0},
                  {0, 8, 7, 0, 0, 0, 0, 3, 1},
                  {0, 0, 3, 0, 1, 0, 0, 8, 0},
                  {9, 0, 0, 8, 6, 3, 0, 0, 5},
                  {0, 5, 0, 0, 9, 0, 6, 0, 0},
                  {1, 3, 0, 0, 0, 0, 2, 5, 0},
                  {0, 0, 0, 0, 0, 0, 0, 7, 4},
                  {0, 0, 5, 2, 0, 6, 3, 0, 0},
                  };
int possibleEntries[9];
void possibleEntriescheck(int i, int j)
{
    int x,a=0,k,l,y;
    for(x=0;x<9;x++)
        possibleEntries[x]=0;
    for(x=0;x<9;x++)
    {
        if(board[i][x]!=0)
            possibleEntries[board[i][x]-1]=1;
    }

    for(x=0;x<9;x++)
    {
        if(board[x][j]!=0)
            possibleEntries[board[x][j]-1]=1;
    }

    if(i==0 || i==1 || i==2)
        k=0;
    else if(i==3 || i==4 || i==5)
        k=3;
    else
        k=6;

    if(j==0 || j==1 || j==2)
        l=0;
    else if(j==3 || j==4 || j==5)
        l=3;
    else
        l=6;

    for(x=k;x<k+3;x++)
    {
        for(y=l;y<l+3;y++)
            if(board[x][y]!=0)
                possibleEntries[board[x][y]-1]=1;
    }
    for(x=0;x<9;x++)
    {
        if(possibleEntries[x]==0)
            possibleEntries[x]=x+1;
        else
            possibleEntries[x]=0;
    }
}
int isFull()
{
    int i,j;
    for(i=0;i<9;i++)
    {
        for(j=0;j<9;j++)
        {
            if(board[i][j]==0)
                return 0;
        }
    }
    return 1;
}
void solveSudoku()
{
    int i,j,x,b=0,k;
    if(isFull())
    {
        printf("The sudoku board is:\n");
        for(i=0;i<9;i++)
        {   
            for(j=0;j<9;j++)
                printf("\t%d",board[i][j]);
            printf("\n");
        }
    }
    else
    {
        for(i=0;i<9;i++)
        {
            for(j=0;j<9;j++)
            {
                if(board[i][j]==0)
                {
                    possibleEntriescheck(i,j);
                    for(x=0;x<9;x++)
                    {
                        if(possibleEntries[x]!=0)
                        {
                            board[i][j]=possibleEntries[x];
                            solveSudoku();
                            board[i][j]=0;
                        }
                    }   
                }
            }
        }
    }
    return;
}
int main()
{
    solveSudoku();
}

person Mohanish Manker    schedule 20.02.2018    source источник
comment
Вы пробовали отладку?   -  person MrSmith42    schedule 20.02.2018
comment
Не могли бы вы попробовать пройтись по вашему possibleEntriescheck(), описывая каждый шаг функции на английском языке и сопоставив его с тем местом в коде, где это происходит? (Возможно, с комментариями к коду? :))   -  person BadZen    schedule 20.02.2018
comment
@BadZen В этом видео объясняется эта функция youtube.com/watch?v=NuodN41aK3g   -  person Mohanish Manker    schedule 20.02.2018
comment
possibleEntries[board[i][x]-1]=1; что происходит, когда board[i][x] ==0.   -  person drescherjm    schedule 20.02.2018
comment
Неподвижные изображения кода достаточно плохи, я ни за что не смотрю видео, чтобы узнать для себя, что вы должны уметь выражать в тексте (в идеале, исходный код). Научитесь пользоваться отладчиком. Как минимум минимум научитесь вставлять операторы печати, чтобы вы могли наблюдать, как это работает.   -  person Useless    schedule 20.02.2018
comment
Хотя вам не нужен return в конце main, обычно оператор return используется для возврата значения из программы.   -  person Thomas Matthews    schedule 20.02.2018
comment
Глобальные переменные и рекурсия! Ой! Несомненно, это, по крайней мере, частично отвечает за ваши проблемы (вы выполняете итерацию по глобальному массиву при вызове рекурсивных методов, которые хранят данные и изменяют указанный глобальный массив).   -  person Garrett Gutierrez    schedule 20.02.2018


Ответы (1)


Вы неправильно реализовали возврат с возвратом. Как также объясняется в видео, реальный алгоритм должен выглядеть так:

solve():
    if the sudoku is solved
        print field
        terminate

    x,y = the next vacant field

    for each possible value in that field
        assign value to x,y
        call solve() recursively to try with the assigned value

    clear vacant field

Теперь то, что делает ваш код,

solve():
    if the sudoku is solved
        print field
        return

    for each field in the sudoku
        if field is vacant
            for each possible value
                assign value
                solve recursively
                reset field to unassigned

Теперь это действительно действительно решает судоку. Но у этого подхода есть две проблемы:
A: он не прекращается после того, как решит судоку. Собственно эта ошибка была и в коде, представленном на видео. Простой return в рекурсивном вызове завершит метод текущего вызова и продолжит рекурсию «на один вызов выше». Таким образом, алгоритм решает судоку всеми способами (при условии, что их несколько, в противном случае он просто пробует любой возможный способ присвоения значений).
B: Это путь серьезнее. Ваш алгоритм не только генерирует все возможные решения, но также пробует каждый порядок присвоения значений, которые он может найти. Накладные расходы огромны и причина, по которой ваш код просто не завершается. Решение судоку один раз уже занимает довольно много времени, но ваш код делает это миллионы раз.

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

solve(vacant, count):
    if count == 0
        print the field
        terminate

    x, y = vacant[count]
    count++

    for each possible value assignable to the field
        assign value to x, y
        call solve(vacant, count) recursively

    clear field

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

int possibleEntries[9];

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

solve
 |
 ---> board empty? Nope
      x,y <- next vacant field
possible values <- possible values for x, y
      field[x, y] <- first value from possible values
      solve
       |
       ---> board empty? Nope
            x, y <- next vacant field
possible values <- possible values for x, y (overwrites global variable!!!)
           field[x, y] <- first value from possible values
           solve
            |
            ---> ...
       <--- return
       field[x, y] <- second value from possible values (WRONG!!!)
       ... 

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

  • Итерируйте от 1 до 9 и проверяйте для каждого числа отдельно, можно ли его присвоить полю.
  • Ведение отдельного списка для каждого уровня рекурсии
person Paul    schedule 20.02.2018