9

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

好的,所以我需要编写一个算法来解决任意大小的任何(可解决的)数独板。我编写了一个递归函数,可以快速解决任何 9x9 板(约 1 毫秒),但是当我做较大的板(16x16)时,很难解决它。我已经进行了 20 分钟的测试,它可以' t似乎解决它。它可以解决简单的 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 板会很快解决。

任何想法或建议都会非常棒。如果有任何我错过的信息,也请告诉我!

4

6 回答 6

6

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

维基百科上有很好的解释如何应用精确的覆盖来解决数独问题。

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

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

于 2011-10-08T12:43:21.857 回答
4

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

战术一

如果一个区域中有 K 个方格只能取相同的 K 个数字(例如,两个方格只能取 2 和 5,或者三个方格只能取 1、7 和 8),那么该区域中的所有其他方格都可以t 拿那些具体的数字。您需要迭代每个区域以清除“已取”数字,因此您可以找到一个只有一个逻辑选择的正方形(例如,逻辑上具有 2、4 和 5 的第三个正方形只能取 4,或带有 1、3 的第四个正方形, 7 和 8 在逻辑上只能取 3)。

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

A:1 2 3
B:2 3
C:2 3 4 5
D: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
B: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

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


人类使用所有这些策略,并且非常讨厌猜测一个数字,因为回溯是一种真正的痛苦。实际上,棋盘的难度是用一个人必须做出的最小猜测数来衡量的。对于大多数“极端”板来说,一个好的猜测就足够了。

于 2011-10-08T12:09:12.307 回答
1

我不知道你以前是否看过这个链接。此外,它在效率上可能无法与跳舞链接算法相提并论,因为它使用的是幼稚的回溯形式。无论如何它有效。看看能不能对你有用!

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

于 2011-10-08T09:56:05.200 回答
0

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

尝试寻找动态编程方法

  • 如果您是一年级/二年级的学生,请保持现有的状态(并检查正确性)。你的老师不会期望更多。
于 2011-10-08T10:00:40.347 回答
0

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

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

于 2011-10-08T12:40:27.547 回答
0

正如@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;
}

你可以试试:)

于 2012-12-30T02:22:31.923 回答