【问题标题】:Backtracking sudoku solver in CC中的回溯数独求解器
【发布时间】:2014-02-07 12:22:33
【问题描述】:

这是我被要求为一堂课做的一个程序,它必须解决一个 nxn 大小的数独难题 使用回溯算法,但不需要检查子方块,只检查行和列。 我的程序的问题是回溯部分永远不会触发:(每当它到达无法继续的点时,它就会停止并打印板,而不是擦除最后一次尝试并尝试其他东西。 提前抱歉缺少 cmets,我目前只能访问代码(不在家里),但它主要是非常简单的函数,问题出在 aux_solveSudoku 和 sudoku 函数中,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define UNASSIGNED 0

bool aux_SolveSudoku(int n, int array[][n]);
bool SolveSudoku(int n, int array[][n], int row, int col);
bool check(int n, int array[][n], int row, int col, int number);
bool CheckRow(int n, int array[][n], int row, int number);
bool CheckCol(int n, int array[][n], int col, int number);
bool CheckIfFull(int n, int array[][n]);
void printArray(int n, int array[][n]);

int main()
{
    int n;
    scanf(" %d", &n);
    int array[n][n];

    for(int i=0; i<n; i++){
        for(int j=0; j<n; j++){
            scanf(" %d",&array[i][j]);
        }
    }
    if (aux_SolveSudoku(n, array) == true)
          printArray(n, array);
    else
         printf("No Solution!");


  return 0;
}

bool aux_SolveSudoku(int n, int array[][n]){
    for (int row = 0; row < n; row++){
        for (int col = 0; col < n; col++){
            if (SolveSudoku(n, array, row, col) && CheckIfFull(n, array))
                return true;}
    }
    return false;
}

bool SolveSudoku(int n, int array[][n], int row, int col){
    if(array[row][col] != UNASSIGNED)
        return true;

    for(int i=1; i<=n; i++){
        if(check(n, array, row, col, i)){
            array[row][col] = i;

            if(SolveSudoku(n, array, row, col))
                return true;

            array[row][col] = UNASSIGNED;
        }
    }    
    return false;
}

bool check(int n, int array[][n], int row, int col, int number){
    return (!CheckRow(n, array, row, number) && !CheckCol(n, array, col, number));
} 

bool CheckRow(int n, int array[][n], int row, int number){
    for (int col = 0; col < n; col++){
        if (array[row][col] == number){
            return true;}
    }
    return false;
}

bool CheckCol(int n, int array[][n], int col, int number){
    for (int row = 0; row <  n; row++){
        if (array[row][col] == number){
            return true;}
    }
    return false;

}

bool CheckIfFull(int n, int array[][n]){
    for (int i = 0; i < n; i++){
            for (int j = 0; j < n; j++){
                if(array[i][j] == UNASSIGNED)
                    return false;
                    }
            }
    return true;
}

void printArray(int n, int array[][n]){
    for (int row = 0; row < n; row++){
            for (int col = 0; col < n; col++){
                printf("%d ", array[row][col]);}
            printf("\n");
        }
}

这是程序的一组示例数据:

5
0 0 0 2 4
3 1 0 0 2
0 2 1 0 5
2 0 3 0 0
0 0 0 0 0

这显然有一个解决方案:

1 3 5 2 4
3 1 4 5 2
4 2 1 3 5
2 5 3 4 1
5 4 2 1 3

不幸的是,程序没有找到解决方案,但我不明白为什么没有。

【问题讨论】:

  • 只是让您知道那不是有效的 c 代码。它是使用 c 函数的 C++。 c 中没有 bool 类型。您必须改用 int。
  • @chasep255:C99和C11中有_Bool类型;如果使用标头&lt;stdbool.h&gt;,还有bool 类型,代码使用&lt;stdbool.h&gt;(C++ 代码不会这样做)。不要忘记,MSVC 编译器实现的标准已经有将近 25 年的历史(今年晚些时候,它将有 25 年的历史),而不是该标准的最新版本。
  • 实际上,这不是 C++ 代码还有另一个原因;它使用 C99 VLA(可变长度数组),它不是任何标准 C++ 版本的一部分(尽管 G++ 编译器确实接受它们作为扩展)。
  • 首先要做的事情之一是确保输入的数据与您认为输入的数据相符;在致电aux_SudokuSolver() 之前打印数独板。调用入口点SudokuSolver() 和递归函数aux_SudokuSolver() (反转当前角色)可能更传统。然而,这是琐事。你在 1x1 和 2x2 板上测试过吗? 3x3呢?代码为我编译干净(好)。我想在 SudokuSolver() 的每个(递归)条目上打印正在处理的板,这样我就可以看到它在做什么。

标签: c recursion backtracking solver


【解决方案1】:

您需要提供一个处理不当的数独板示例。我的(诚然压力不是很大)测试表明它表现良好,尽管它似乎没有在 SudokuSolver() 中递归,这可能是您关注的要点。

3x3 可解

3
0 1 0
2 0 1
0 0 0

检测程序的输出

-->> SolveSudoku (3x3) - r=0, c=0, level = 1:
0 1 0 
2 0 1 
0 0 0 
-->> SolveSudoku (3x3) - r=0, c=2, level = 1:
3 1 0 
2 0 1 
0 0 0 
-->> SolveSudoku (3x3) - r=1, c=1, level = 1:
3 1 2 
2 0 1 
0 0 0 
-->> SolveSudoku (3x3) - r=2, c=0, level = 1:
3 1 2 
2 3 1 
0 0 0 
-->> SolveSudoku (3x3) - r=2, c=1, level = 1:
3 1 2 
2 3 1 
1 0 0 
-->> SolveSudoku (3x3) - r=2, c=2, level = 1:
3 1 2 
2 3 1 
1 2 0 
Solution:
3 1 2 
2 3 1 
1 2 3 

4x4 可解

4
4 0 1 0
3 2 0 0
0 0 0 3
0 0 0 0

预期的解决方案

4 3 1 2
3 2 4 1
1 4 2 3
2 1 3 4

检测程序的输出

-->> SolveSudoku (4x4) - r=0, c=1, level = 1:
4 0 1 0 
3 2 0 0 
0 0 0 3 
0 0 0 0 
-->> SolveSudoku (4x4) - r=0, c=3, level = 1:
4 3 1 0 
3 2 0 0 
0 0 0 3 
0 0 0 0 
-->> SolveSudoku (4x4) - r=1, c=2, level = 1:
4 3 1 2 
3 2 0 0 
0 0 0 3 
0 0 0 0 
-->> SolveSudoku (4x4) - r=1, c=3, level = 1:
4 3 1 2 
3 2 4 0 
0 0 0 3 
0 0 0 0 
-->> SolveSudoku (4x4) - r=2, c=0, level = 1:
4 3 1 2 
3 2 4 1 
0 0 0 3 
0 0 0 0 
-->> SolveSudoku (4x4) - r=2, c=1, level = 1:
4 3 1 2 
3 2 4 1 
1 0 0 3 
0 0 0 0 
-->> SolveSudoku (4x4) - r=2, c=2, level = 1:
4 3 1 2 
3 2 4 1 
1 4 0 3 
0 0 0 0 
-->> SolveSudoku (4x4) - r=3, c=0, level = 1:
4 3 1 2 
3 2 4 1 
1 4 2 3 
0 0 0 0 
-->> SolveSudoku (4x4) - r=3, c=1, level = 1:
4 3 1 2 
3 2 4 1 
1 4 2 3 
2 0 0 0 
-->> SolveSudoku (4x4) - r=3, c=2, level = 1:
4 3 1 2 
3 2 4 1 
1 4 2 3 
2 1 0 0 
-->> SolveSudoku (4x4) - r=3, c=3, level = 1:
4 3 1 2 
3 2 4 1 
1 4 2 3 
2 1 3 0 
Solution:
4 3 1 2 
3 2 4 1 
1 4 2 3 
2 1 3 4 

4x4 无法解决

4
4 0 1 0
3 2 0 0
0 0 0 2
0 0 0 0

这是无法解决的,因为没有地方可以在第一行放置 2。

-->> SolveSudoku (4x4) - r=0, c=1, level = 1:
4 0 1 0 
3 2 0 0 
0 0 0 2 
0 0 0 0 
-->> SolveSudoku (4x4) - r=0, c=3, level = 1:
4 3 1 0 
3 2 0 0 
0 0 0 2 
0 0 0 0 
-->> SolveSudoku (4x4) - r=1, c=2, level = 1:
4 3 1 0 
3 2 0 0 
0 0 0 2 
0 0 0 0 
-->> SolveSudoku (4x4) - r=1, c=3, level = 1:
4 3 1 0 
3 2 4 0 
0 0 0 2 
0 0 0 0 
-->> SolveSudoku (4x4) - r=2, c=0, level = 1:
4 3 1 0 
3 2 4 1 
0 0 0 2 
0 0 0 0 
-->> SolveSudoku (4x4) - r=2, c=1, level = 1:
4 3 1 0 
3 2 4 1 
1 0 0 2 
0 0 0 0 
-->> SolveSudoku (4x4) - r=2, c=2, level = 1:
4 3 1 0 
3 2 4 1 
1 4 0 2 
0 0 0 0 
-->> SolveSudoku (4x4) - r=3, c=0, level = 1:
4 3 1 0 
3 2 4 1 
1 4 3 2 
0 0 0 0 
-->> SolveSudoku (4x4) - r=3, c=1, level = 1:
4 3 1 0 
3 2 4 1 
1 4 3 2 
2 0 0 0 
-->> SolveSudoku (4x4) - r=3, c=2, level = 1:
4 3 1 0 
3 2 4 1 
1 4 3 2 
2 1 0 0 
-->> SolveSudoku (4x4) - r=3, c=3, level = 1:
4 3 1 0 
3 2 4 1 
1 4 3 2 
2 1 0 0 
No Solution!

仪表化代码

#include <stdio.h>
#include <stdbool.h>

#define UNASSIGNED 0

bool aux_SolveSudoku(int n, int array[][n]);
bool SolveSudoku(int n, int array[][n], int row, int col);
bool check(int n, int array[][n], int row, int col, int number);
bool CheckRow(int n, int array[][n], int row, int number);
bool CheckCol(int n, int array[][n], int col, int number);
bool CheckIfFull(int n, int array[][n]);
void printArray(int n, int array[][n]);

int main(void)
{
    int n;
    scanf(" %d", &n);
    int array[n][n];

    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
            scanf(" %d", &array[i][j]);
    }

    if (aux_SolveSudoku(n, array) == true)
    {
        printf("Solution:\n");
        printArray(n, array);
    }
    else
        printf("No Solution!\n");

    return 0;
}

bool aux_SolveSudoku(int n, int array[][n])
{
    for (int row = 0; row < n; row++)
    {
        for (int col = 0; col < n; col++)
        {
            if (SolveSudoku(n, array, row, col) && CheckIfFull(n, array))
                return true;
        }
    }
    return false;
}

bool SolveSudoku(int n, int array[][n], int row, int col)
{
    if (array[row][col] != UNASSIGNED)
        return true;
    static int level = 0;

    level++;
    printf("-->> SolveSudoku (%dx%d) - r=%d, c=%d, level = %d:\n", n, n, row, col, level);
    printArray(n, array);

    for (int i = 1; i <= n; i++)
    {
        if (check(n, array, row, col, i))
        {
            array[row][col] = i;

            if (SolveSudoku(n, array, row, col))
            {
                level--;
                return true;
            }

            array[row][col] = UNASSIGNED;
        }
    }
    level--;
    return false;
}

bool check(int n, int array[][n], int row, int col, int number)
{
    return(!CheckRow(n, array, row, number) && !CheckCol(n, array, col, number));
}

bool CheckRow(int n, int array[][n], int row, int number)
{
    for (int col = 0; col < n; col++)
    {
        if (array[row][col] == number)
        {
            return true;
        }
    }
    return false;
}

bool CheckCol(int n, int array[][n], int col, int number)
{
    for (int row = 0; row <  n; row++)
    {
        if (array[row][col] == number)
        {
            return true;
        }
    }
    return false;
}

bool CheckIfFull(int n, int array[][n])
{
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            if (array[i][j] == UNASSIGNED)
                return false;
        }
    }
    return true;
}

void printArray(int n, int array[][n])
{
    for (int row = 0; row < n; row++)
    {
        for (int col = 0; col < n; col++)
        {
            printf("%d ", array[row][col]);
        }
        printf("\n");
    }
}

变化基本上很小。你不需要&lt;stdlib.h&gt;;你确实需要在输出结束时换行。代码更频繁地调用printArray(),并在找到解决方案时识别解决方案(有必要避免将解决方案与跟踪混合)。它保持一个电平计数器,当为未分配的正方形输入函数时递增。

您不验证输入(但可以并且可以说应该);检测代码也不验证数据。


进一步观察

每次aux_SudokuSolver() 调用SudokuSolver(),它都应该尝试解决相同的(尚未解决的)问题。但是,如果你在函数入口处复制数组,然后在SudokuSolver()返回不成功后将副本与原始数组进行比较,你会发现数组发生了变化,所以代码并不总是解决相同的问题。您必须确保始终尝试在顶层解决相同的问题。

【讨论】:

  • 一个行为不端的例子是:5 0 0 0 2 4 3 1 0 0 2 0 2 1 0 5 2 0 3 0 0 0 0 0 0 0 这应该是可以解决的。跨度>
  • 我可以肯定 (a) 有一个解决方案 (1 3 5 2 4 3 1 4 5 2 4 2 1 3 5 2 5 3 4 1 5 4 2 1 3) 和 (b ) 上面的程序没有找到它。此外,除非我的关卡跟踪代码中存在错误,否则它不会递归。到目前为止,我没有更多要补充的。我观察到,您可以通过包含该问题失败的示例来改进您的问题——它会为调查指明方向。
【解决方案2】:

由于这部​​分代码,无法回溯。

bool SolveSudoku(int n, int array[][n], int row, int col){
    if(array[row][col] != UNASSIGNED)
        return true;

    for(int i=1; i<=n; i++){
        if(check(n, array, row, col, i)){
            array[row][col] = i;

            if(SolveSudoku(n, array, row, col))
                return true;

            array[row][col] = UNASSIGNED;
        }
    }    
    return false;
}

具体来说,当 check 方法返回 true 时,array[row][col] 被设置为 i。

在下一条语句中,SolveSudoku 被调用,但 row 和 col 的值相同。

由于这个值不是未赋值的,SolveSudoku 将返回 true。由于它返回 true,因此每个位置只尝试一个值。没有以正确的方式利用递归。

我希望这可以解释为什么您的代码中不会发生回溯。祝你好运!如果您还有其他问题,请告诉我...

这里以我用 Java 编写的示例为例。 solve 方法不会在嵌套的 for 循环中调用,因此某些结束条件会以不同的方式进行评估。你只需用 0, 0 调用这个方法,它就从那里开始......

void solve(int r, int c)
{
    if(board[r][c] == 0)
    {
        for(int i = 1; i < 10; i++)
        {
            if (!inRow(i, r) && !inCol(i, c) && !inSqr(i, r, c))
            {
                board[r][c] = i;
                if(r == 8 && c == 8)
                {
                    printBoard();
                    System.exit(0);
                }
                else if(c == 8) solve(r + 1, 0);
                else solve(r, c + 1);
            }
        }
        board[r][c] = 0;
    }
    else if(r == 8 && c == 8)
    {
        printBoard();
        System.exit(0);
    }
    else if(c == 8) 
    {
        solve(r + 1, 0);
    }
    else 
    {
        solve(r, c + 1);
    }
}

【讨论】:

  • 非常感谢您的意见,我想我更清楚地了解我的问题在哪里,但我仍然无法解决:( 我尝试根据您的解决方案进行更正(我什至尝试应对解决方案并使其适应我的代码)但现在不是不回去继续超过 n,例如,如果板是 5x5 并且无法解决,它开始分配 6s 来解决它,即使 i 循环在函数内停止在 5,并且对于一些输入由于某种原因进入无限循环。
  • 问题在于 aux_SolveSudoku 和 SolveSudoku 之间的交互。在我的代码中,请注意我“启动”了求解方法,它负责调用自身。这是一种管理递归级别的简单方法。这有帮助吗?
猜你喜欢
  • 2023-03-27
  • 1970-01-01
  • 2017-12-16
  • 1970-01-01
  • 1970-01-01
  • 2016-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多