数独回溯算法

发布于 2024-12-08 22:19:42 字数 1854 浏览 1 评论 0原文

首先,我要声明这是一项大学作业,所以我不是要求别人为我编写代码,我只需要指出正确的方向。 :)

好吧,所以我需要编写一个算法来解决任意大小的任何(可解)数独板。我编写了一个递归函数,可以快速解决任何 9x9 板(约 1 毫秒),但是当我处理难以解决的较大板(16x16)时,它就很困难..我已经进行了 20 分钟的测试,但它可以'似乎没有解决它。它可以解决简单的 16x16 谜题,甚至可以解决空白的 16x16 板,所以我认为问题不是尺寸。我认为问题更可能是算法。

无论如何,这是我程序的基本逻辑..

  • 我有一个 3D 向量,用于存储每个正方形的可能值
  • 当将一个值放入一个正方形中时,它将从周围正方形、行和列的可能值中删除那么

我的解决函数基本上是:

bool solve() {

    if (there are no unfilled squares)
        return true

    if (the board is unsolvable - there are empty squares that have no possible values)
        return false

    while (there are empty squares)
    {
        int squaresFilled = fillSquaresWithOnlyOneChoice(); //this method updates the possible results vector whenever it fills a square

        if (squaresFilled == 0)
            break;
    }

    //exhausted all of the 'easy' squares (squares with only one possible choice), need to make a guess

    while (there are empty squares that have choices left) {

        find the square with the least number of choices

        if (the square with the least number of choices has 0 choices)
            return false; //not solvable.

        remove that choice from the 3D vector (vector that has the choices for each square)
        make a copy of the board and the 3D choices vector

        fill the square with the choice

        if (solve())
            return true; //we're done

        restore the board and choices vector 
        //the guess didn't work so keep looping and make a new guess with the restored board and choices -- the choice we just made has been removed though so it won't get made again.

    }

    return false; //can't go any further
}

这有什么效率低下的地方吗?有什么办法可以让它更好地工作吗?我猜测 16x16 板需要这么长的时间是因为它的决策树对于一个没有太多填充的板来说太大了。但这很奇怪,因为 9x9 板的求解速度非常快。

任何想法或建议都绝对很棒。如果有任何我错过的信息也请告诉我!

First of all, I'll state that this is a university assignment so I'm not asking for someone to write the code for me I just need to be pointed in the right direction. :)

Ok, so I need to write an algorithm to solve any (solvable) sudoku board of arbitrary size. I've written a recursive function that can solve any 9x9 board quickly (~1ms) but when I do larger boards (16x16) that are hard to solve it struggles.. I've had one test going for 20 minutes and it can't seem to solve it. It can solve easy 16x16 puzzles or even a blank 16x16 board so I don't think it's the dimensions that are the problem.. it's more likely to be the algorithm that is the problem I think.

Anyway, this is the basic logic of my program..

  • I have a 3D vector that stores the possible values of my every square
  • When a value is placed in a square then it is removed from the possible values of the surrounding square, row and column that it's in

Then my solve function is basically:

bool solve() {

    if (there are no unfilled squares)
        return true

    if (the board is unsolvable - there are empty squares that have no possible values)
        return false

    while (there are empty squares)
    {
        int squaresFilled = fillSquaresWithOnlyOneChoice(); //this method updates the possible results vector whenever it fills a square

        if (squaresFilled == 0)
            break;
    }

    //exhausted all of the 'easy' squares (squares with only one possible choice), need to make a guess

    while (there are empty squares that have choices left) {

        find the square with the least number of choices

        if (the square with the least number of choices has 0 choices)
            return false; //not solvable.

        remove that choice from the 3D vector (vector that has the choices for each square)
        make a copy of the board and the 3D choices vector

        fill the square with the choice

        if (solve())
            return true; //we're done

        restore the board and choices vector 
        //the guess didn't work so keep looping and make a new guess with the restored board and choices -- the choice we just made has been removed though so it won't get made again.

    }

    return false; //can't go any further
}

Is there anything inefficient about this? Is there any way I could get it to work better? I'm guessing that a 16x16 board takes so long is because the decision tree for it is so large for a board that isn't filled in very much. It's weird though, because a 9x9 board will solve really fast.

Any ideas or suggestions would be absolutely awesome. If there's any information I've missed let me know too!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

向日葵 2024-12-15 22:19:42

解决数独的快速算法是 Donald Knuth 的算法 X。您将解决数独问题表示为精确覆盖问题,然后使用用于解决 EC 问题的算法 X。然后使用 DLX 作为算法 X 的有效实现。

维基百科上有关于如何应用精确覆盖来解决数独的详细解释。

我可以告诉您,DLX 求解数独的速度非常快,通常用于最快的算法。

http://www.setbb.com/phpbb/index.php?mforum=sudoku 是很棒的论坛,可能有最好的数独程序员。

Fast algorhitm for solving sudoku is Algorithm X by Donald Knuth. You represent solving sudoku as exact cover problem and then use Algorithm X for solving EC problem. Then use DLX as efficient implementation of Algorithm X.

There is great explanation on wikipedia on how to apply exact cover for solving sudoku.

I can tell you that DLX is extremely fast fost solving sudoku in is commonly used in fastest algorhitm.

http://www.setbb.com/phpbb/index.php?mforum=sudoku is great forum whit probably best sudoku programmers.

梦里°也失望 2024-12-15 22:19:42

在仅用一种选择填充方格和在棋盘上完全递归之间,您可以执行更高级的操作。假设“区域”是一行、一列或一个正方形区域(3x3 或 4x4)。

策略 1

如果一个区域中有 K 个方格只能取相同的 K 个数字(例如两个方格只能取 2 和 5,或者三个方格只能取 1、7 和 8),则该区域中的所有其他方格无法接受那些具体数字。您需要迭代每个区域以清除“已采用”的数字,这样您就可以找到一个只有一个逻辑选择的方格(例如,具有 2、4 和 5 的第三个方格逻辑上只能采用 4,或者具有 1、3、 7和8逻辑上只能取3)。

如果考虑以下示例,则必须通过迭代来解决此问题。一个区域有包含以下可能数字的方块:

A: 1 2 3
乙:2 3
C: 2 3 4 5
深:4 5
E: 4 5

该算法应检测到方格 D 和 E 包含数字 4 和 5,因此 4 和 5 被排除在该区域的其他方格之外。然后,算法检测到方格 B 和 C 包含数字 2 和 3,因此将它们从其他方格中排除。这使得方格 A 只剩下数字 1。

策略 2

如果一个数字出现在该区域中仅一个方格中,则从逻辑上讲,该方格保存该数字。

策略3

策略1和2只是策略3的特例,其中K个方格只有K个相同的数字。您可以拥有 K 个方格和一组 K 个数字,并且这些 K 个方格可以容纳这 K 个数字的任何子集。考虑以下区域示例:

A: 1 2
乙:2 3
C:1 3
D: 1 2 3 4

方格 A、B 和 C 只能容纳数字 1、2 和 3。这就是 K 代表 K。这意味着任何其他方格在逻辑上都不能容纳这些数字,这使得方格 D 中只剩下数字 4。

当 K = N - 1 时,策略 2 是策略 3 的特例。

策略 4

利用区域重叠。假设某个数字只能存在于该区域的某些方格中。如果所有这些方块都属于另一个重叠区域,那么该数字应该从该其他区域中的所有其他方块中排除。

策略 5

缓存结果。所有区域都应该有一个“脏”标志,表示该区域中的某些内容自上次处理该区域以来已发生变化。您不必处理未设置此标志的区域。


人类使用所有这些策略,并且真的讨厌猜测数字,因为回溯真的很痛苦。实际上,棋盘的难度是用解决该棋盘所需的最少猜测次数来衡量的。对于大多数“极端”主板来说,一个好的猜测就足够了。

Between filling the squares with only one choice and going full recursive on the board there are more advanced actions you can do. Lets take that "region" is one row, or one column, or one square region (3x3 or 4x4).

Tactic 1

If there are K squares in a region that can take only identical K numbers (for instance two squares that can take only 2 an 5, or three squares that can take only 1, 7 and 8) then all other squares in that region can't take those specific numbers. You need to iterate each region to weed out "taken" numbers, so you can find a square with only one logical choice (for instance third square with 2, 4 and 5 logically can take only 4, or fourth square with 1, 3, 7 and 8 logically can take only 3).

This has to bi solved with iteration if you consider the following example. A region has squares with this possible numbers:

A: 1 2 3
B: 2 3
C: 2 3 4 5
D: 4 5
E: 4 5

The algorithm should detect that squares D and E hold numbers 4 and 5, so 4 and 5 are excluded from other squares in the region. The algorithm then detects that squares B and C hold numbers 2 and 3, and so excludes them from other squares. This leaves square A with only number 1.

Tactic 2

If a number occurs in the region in only one square then logically that square holds that number.

Tactic 3

Tactics 1 and 2 are only special cases of Tactic 3 having K squares with only K identical numbers. You can have K squares and a set of K numbers and those K squares can hold any subset of those K numbers. Consider the following example of a region:

A: 1 2
B: 2 3
C: 1 3
D: 1 2 3 4

Squares A, B and C can hold only numbers 1, 2 and 3. That's K for K. That means that any other square can't logically hold these numbers, which leaves square D with only number 4.

Tactic 2 is special case of Tactic 3 when K = N - 1.

Tactic 4

Take advantage of regions overlap. Suppose that some number can exist only in certain squares of the region. If all those squares belong to another overlapping region then that number should be excluded from all other squares in this other region.

Tactic 5

Cache results. All regions should have a "dirty" flag that denotes that something in the region has changed from the last time the region is processed. You don't have to process the region with this flag not set.


Human beings use all those tactics, and really hate to guess a number, because backtracking is a real pain. Actually, the difficulty of a board is measured with the minimum number of guesses one has to make to solve the board. For most "extreme" boards one good guess is enough.

只怪假的太真实 2024-12-15 22:19:42

我不知道你之前是否看过这个链接。
而且在效率上它可能无法与跳舞链接算法相媲美
因为它使用朴素的回溯形式。不管怎样,它有效。
看看它是否对你有用!

您也可以添加几行来解决“简单”的方块。
http://edwinchan.wordpress.com/ 2006/01/08/sudoku-solver-in-c-using-backtracking/

I dont know if you have seen this link before.
Also it may not be comparable to dancing links algorithm in efficiency
as it uses naive form of backtracking. Anyway it works.
See if it can be useful for you!

Also may be you can add a few lines for solving 'easy' squares.
http://edwinchan.wordpress.com/2006/01/08/sudoku-solver-in-c-using-backtracking/

孤独患者 2024-12-15 22:19:42

你的算法看起来不错。问题是你使用的是暴力方法,因此你的运行时间是(字符数)^(板的大小) - 所以对于 9x9 有 9^9=387420489 而对于 16x16 运行时间是 16^ 16=18446744073709551616。你可以看到差异。

尝试寻找动态编程方法

  • 如果您是一年级/二年级学生,请坚持您的想法有(并检查正确性)。你的老师不会期望更多。

You're algorithm looks fine. The problem is that you are using a brute-force approach, and therefore your running time is (number of characters)^(size of board) - so for 9x9 there are 9^9=387420489 and for 16x16 the running time is 16^16=18446744073709551616. you can see the difference.

Try finding a Dynamic programing approach

  • If you are a first\second year student, just stay with what you have (and check correctness). Your teacher won't expect more.
不忘初心 2024-12-15 22:19:42

没有必要将只有一个可能数字的单元格视为特殊的。您已经首先访问了可能性最少的单元格。

另外:当您“从 3D 矢量中删除该选择”时,您还可以将其从同一 {row, columns,box} 上的其他单元格中删除。位掩码可能会很好地适应。 (但是回溯会变得有点困难)

There is no need to treat the cells with only one possible number as special. You already visit the cells with the fewest number of possibilities first.

Also: when you "remove that choice from the 3D vector", you could also remove it from the other cells on the same {row, columns,box}. Bitmasks will probably fit in in nicely. (but the backtracking will become a bit harder)

青芜 2024-12-15 22:19:42

正如 @ralu 提到的,解决数独最快的算法是使用 DLX。下面是我过去写的一个程序。它可以在1秒内解决4*4数独。

假设输入如下:

--A----C-----O-I
-J--A-B-P-CGF-H-
--D--F-I-E----P-
-G-EL-H----M-J--
----E----C--G---
-I--K-GA-B---E-J
D-GP--J-F----A--
-E---C-B--DP--O-
E--F-M--D--L-K-A
-C--------O-I-L-
H-P-C--F-A--B---
---G-OD---J----H
K---J----H-A-P-L
--B--P--E--K--A-
-H--B--K--FI-C--
--F---C--D--H-N-


#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;

const int INF=1<<30;
const int N=4;
const int MAXR=N*N*N*N*N*N+10;
const int MAXC=N*N*N*N*4+10;
const int SIZE=MAXR*MAXC/N/N;

int n,m;
int mat[MAXR][MAXC],r,c,ans;
int L[SIZE],R[SIZE],U[SIZE],D[SIZE],S[MAXC],C[SIZE],RH[SIZE],O[MAXC];
int a[MAXR];
char b[MAXR];
char s[N*N+10][N*N+10];
int head,cnt;

int node(int up,int down,int left,int right) {
    U[cnt]=up;D[cnt]=down;L[cnt]=left;R[cnt]=right;
    D[up]=U[down]=L[right]=R[left]=cnt;
    return cnt++;
}

void init() {
    cnt=0;
    head=node(0,0,0,0);
    for (int i=1;i<=c;i++) {
        C[i]=node(cnt,cnt,L[head],head);
        S[i]=0;
    }
    for (int i=1;i<=r;i++) {
        int rowh=-1;
        for (int j=1;j<=c;j++) if (mat[i][j]) {
            if (rowh==-1) {
                rowh=node(U[C[j]],C[j],cnt,cnt);
                RH[rowh]=i;
                C[rowh]=C[j];
                S[j]++;
            }
            else {
                int k=node(U[C[j]],C[j],L[rowh],rowh);
                RH[k]=i;
                C[k]=C[j];
                S[j]++;
            }
        }
    }
}

void remove(int col) {
    L[R[col]]=L[col];
    R[L[col]]=R[col];
    for (int i=D[col];i!=col;i=D[i])
        for (int j=R[i];j!=i;j=R[j]) {
            U[D[j]]=U[j];
            D[U[j]]=D[j];
            S[C[j]]--;
        }
}

void resume(int col) {
    for (int i=U[col];i!=col;i=U[i])
        for (int j=L[i];j!=i;j=L[j]) {
            S[C[j]]++;
            U[D[j]]=j;
            D[U[j]]=j;
        }
    L[R[col]]=col;
    R[L[col]]=col;
}

bool dfs(int k) {
    if (R[head]==head) {
        ans=k;
        return true;
    }
    int mins=INF,cur=0;
    for (int i=R[head];i!=head;i=R[i])
        if (S[i]<mins) {
            mins=S[i];
            cur=i;
        }
    remove(cur);
    for (int i=D[cur];i!=cur;i=D[i]) {
        O[k]=RH[i];
        for (int j=R[i];j!=i;j=R[j])
            remove(C[j]);
        if (dfs(k+1)) return true;
        for (int j=L[i];j!=i;j=L[j])
            resume(C[j]);
    }
    resume(cur);
    return false;
}

void makegraph() {
    r=0;
    for (int i=0;i<N*N;i++)
        for (int j=0;j<N*N;j++) {
            int t=(i/N)*N+(j/N);
            int p=i*N*N+j;
            if (s[i][j]=='-') {
                for (int k=1;k<=N*N;k++) {
                    r++;
                    mat[r][i*N*N+k]=1;
                    mat[r][N*N*N*N+j*N*N+k]=1;
                    mat[r][2*N*N*N*N+t*N*N+k]=1;
                    mat[r][3*N*N*N*N+p+1]=1;
                    a[r]=p;
                    b[r]='A'+k-1;
                }
            }
            else {
                int k=s[i][j]-'A'+1;
                r++;
                mat[r][i*N*N+k]=1;
                mat[r][N*N*N*N+j*N*N+k]=1;
                mat[r][2*N*N*N*N+t*N*N+k]=1;
                mat[r][3*N*N*N*N+p+1]=1;
                a[r]=p;
                b[r]=s[i][j];
            }
        }
}

int main() {
    freopen("sudoku.txt","r",stdin);
    freopen("sudoku_sol.txt","w",stdout);
        for (int i=0;i<N*N;i++)
            scanf("%s",s[i]);
        memset(mat,0,sizeof(mat));
        makegraph();
        c=N*N*N*N*4;

        init();
        ans=INF;
        dfs(0);
        for (int i=0;i<ans;i++)
            for (int j=i+1;j<ans;j++)
                if (a[O[i]]>a[O[j]]) swap(O[i],O[j]);

        for (int i=0;i<ans;i++) {
            printf("%c",b[O[i]]);
            if ((i+1)%(N*N)==0) printf("\n");
        }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

你可以尝试一下:)

as @ralu mentioned, the fastest algorithm to solve sudoku is using DLX. below is a program i wrote in the past. it can solve 4*4 sudoku within 1 second.

suppose the input is like:

--A----C-----O-I
-J--A-B-P-CGF-H-
--D--F-I-E----P-
-G-EL-H----M-J--
----E----C--G---
-I--K-GA-B---E-J
D-GP--J-F----A--
-E---C-B--DP--O-
E--F-M--D--L-K-A
-C--------O-I-L-
H-P-C--F-A--B---
---G-OD---J----H
K---J----H-A-P-L
--B--P--E--K--A-
-H--B--K--FI-C--
--F---C--D--H-N-


#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;

const int INF=1<<30;
const int N=4;
const int MAXR=N*N*N*N*N*N+10;
const int MAXC=N*N*N*N*4+10;
const int SIZE=MAXR*MAXC/N/N;

int n,m;
int mat[MAXR][MAXC],r,c,ans;
int L[SIZE],R[SIZE],U[SIZE],D[SIZE],S[MAXC],C[SIZE],RH[SIZE],O[MAXC];
int a[MAXR];
char b[MAXR];
char s[N*N+10][N*N+10];
int head,cnt;

int node(int up,int down,int left,int right) {
    U[cnt]=up;D[cnt]=down;L[cnt]=left;R[cnt]=right;
    D[up]=U[down]=L[right]=R[left]=cnt;
    return cnt++;
}

void init() {
    cnt=0;
    head=node(0,0,0,0);
    for (int i=1;i<=c;i++) {
        C[i]=node(cnt,cnt,L[head],head);
        S[i]=0;
    }
    for (int i=1;i<=r;i++) {
        int rowh=-1;
        for (int j=1;j<=c;j++) if (mat[i][j]) {
            if (rowh==-1) {
                rowh=node(U[C[j]],C[j],cnt,cnt);
                RH[rowh]=i;
                C[rowh]=C[j];
                S[j]++;
            }
            else {
                int k=node(U[C[j]],C[j],L[rowh],rowh);
                RH[k]=i;
                C[k]=C[j];
                S[j]++;
            }
        }
    }
}

void remove(int col) {
    L[R[col]]=L[col];
    R[L[col]]=R[col];
    for (int i=D[col];i!=col;i=D[i])
        for (int j=R[i];j!=i;j=R[j]) {
            U[D[j]]=U[j];
            D[U[j]]=D[j];
            S[C[j]]--;
        }
}

void resume(int col) {
    for (int i=U[col];i!=col;i=U[i])
        for (int j=L[i];j!=i;j=L[j]) {
            S[C[j]]++;
            U[D[j]]=j;
            D[U[j]]=j;
        }
    L[R[col]]=col;
    R[L[col]]=col;
}

bool dfs(int k) {
    if (R[head]==head) {
        ans=k;
        return true;
    }
    int mins=INF,cur=0;
    for (int i=R[head];i!=head;i=R[i])
        if (S[i]<mins) {
            mins=S[i];
            cur=i;
        }
    remove(cur);
    for (int i=D[cur];i!=cur;i=D[i]) {
        O[k]=RH[i];
        for (int j=R[i];j!=i;j=R[j])
            remove(C[j]);
        if (dfs(k+1)) return true;
        for (int j=L[i];j!=i;j=L[j])
            resume(C[j]);
    }
    resume(cur);
    return false;
}

void makegraph() {
    r=0;
    for (int i=0;i<N*N;i++)
        for (int j=0;j<N*N;j++) {
            int t=(i/N)*N+(j/N);
            int p=i*N*N+j;
            if (s[i][j]=='-') {
                for (int k=1;k<=N*N;k++) {
                    r++;
                    mat[r][i*N*N+k]=1;
                    mat[r][N*N*N*N+j*N*N+k]=1;
                    mat[r][2*N*N*N*N+t*N*N+k]=1;
                    mat[r][3*N*N*N*N+p+1]=1;
                    a[r]=p;
                    b[r]='A'+k-1;
                }
            }
            else {
                int k=s[i][j]-'A'+1;
                r++;
                mat[r][i*N*N+k]=1;
                mat[r][N*N*N*N+j*N*N+k]=1;
                mat[r][2*N*N*N*N+t*N*N+k]=1;
                mat[r][3*N*N*N*N+p+1]=1;
                a[r]=p;
                b[r]=s[i][j];
            }
        }
}

int main() {
    freopen("sudoku.txt","r",stdin);
    freopen("sudoku_sol.txt","w",stdout);
        for (int i=0;i<N*N;i++)
            scanf("%s",s[i]);
        memset(mat,0,sizeof(mat));
        makegraph();
        c=N*N*N*N*4;

        init();
        ans=INF;
        dfs(0);
        for (int i=0;i<ans;i++)
            for (int j=i+1;j<ans;j++)
                if (a[O[i]]>a[O[j]]) swap(O[i],O[j]);

        for (int i=0;i<ans;i++) {
            printf("%c",b[O[i]]);
            if ((i+1)%(N*N)==0) printf("\n");
        }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

u can have a try :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文