Sudoku Solver 数独自动计算工具

Sudoku Solver

Sudoku Solver 是由C++编写的数独计算工具,使用了基于MRV策略的Forward Checking算法,是典型的CSP问题的实例。
读者可以复制代码编译使用。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int SIZE = 9;

struct Do {
    int val; // value
    int row, col; // position
    bool curdom[SIZE]; // 1~SIZE's availablity
    bool assigned;
};

struct sudoku {
    Do board[SIZE][SIZE];
    bool RowCheck(sudoku* board, Do* m);
    bool ColCheck(sudoku* board, Do* m);
    bool BoxCheck(sudoku* board, Do* m);
    int CDcount(Do* m);
    bool Goal(sudoku* board);
    bool FCCheck(sudoku* board, int c, Do* m);
    void Copyboard(sudoku* dest, const sudoku* src);
    Do* heuristicpick(sudoku* board);
    void propagete(sudoku* board, Do* m);
};

bool FC(sudoku* board, int level);
void propagate(sudoku* board, Do* m);
void display(sudoku* board);


int main() {
    sudoku SUDK;
    sudoku* ptr = &SUDK;
    for (int i = 0;i<SIZE;i++) {
        for (int j = 0;j<SIZE;j++) {
            SUDK.board[i][j].val = 0;
            SUDK.board[i][j].row = i;
            SUDK.board[i][j].col = j;
            SUDK.board[i][j].assigned = 0;
            memset(SUDK.board[i][j].curdom, 0, sizeof(SUDK.board[i][j].curdom));
        }
    }
    cout << "Input Presets: " << endl;
    cout << "In the form as (row, col, value):" << endl;
    cout << "E.g.: 1 2 5" << endl;
    cout << "End with (0, 0, 0)" << endl;
    while(1) {
        int a, b, v;
        cin >> a >> b >> v;
        if (a+b+v == 0) break;
        SUDK.board[a-1][b-1].val = v;
        SUDK.board[a-1][b-1].assigned = 1;
        propagate(ptr, &SUDK.board[a-1][b-1]);
    }
    display(ptr);
    FC(ptr, 0);
    return 0;
}

/* ###################################################################### */

bool RowCheck(sudoku* board, Do* m) {
    // return false: constraint falsified;
    //         true: NO falsification.
    int Row[SIZE];
    for(int i = 0;i<SIZE;i++) {
        Row[i] = board->board[m->row][i].val;
    }
    // Check Constraints
    sort(Row, Row + SIZE);
    for(int i = 0;i<SIZE-1;i++) {
        if (!Row[i]) continue;
        if (Row[i] == Row[i+1]) return false;
    }
    return true;
}

bool ColCheck(sudoku* board, Do* m) {
    // return false: constraint falsified;
    //         true: NO falsification.
    int Col[SIZE];
    for(int i = 0;i<SIZE;i++) {
        Col[i] = board->board[i][m->col].val;
    }
    // Check Constraints
    sort(Col, Col + SIZE);
    for(int i = 0;i<SIZE-1;i++) {
        if (!Col[i]) continue;
        if (Col[i] == Col[i+1]) return false;
    }
    return true;
}

bool BoxCheck(sudoku* board, Do* m) {
    int v = m->val;
    int r1, r2, c1, c2;
    if (m->row % 3 == 1) {
        r1 = 1; r2 = 1;
    }
    else if (m->row % 3 == 0) {
        r1 = 1; r2 = -2;
    }
    else {
        r1 = -1; r2 = 2;
    }
    if (m->col % 3 == 1) {
        c1 = 1; c2 = 1;
    }
    else if (m->col % 3 == 0) {
        c1 = 1; c2 = -2;
    }
    else {
        c1 = -1; c2 = 2;
    }
    // Check Constraints
    if (board->board[m->row][m->col+c1].assigned && v == board->board[m->row][m->col+c1].val) return false;
    if (board->board[m->row][m->col-c2].assigned && v == board->board[m->row][m->col-c2].val) return false;
    if (board->board[m->row+r1][m->col].assigned && v == board->board[m->row+r1][m->col].val) return false;
    if (board->board[m->row+r1][m->col+c1].assigned && v == board->board[m->row+r1][m->col+c1].val) return false;
    if (board->board[m->row+r1][m->col-c2].assigned && v == board->board[m->row+r1][m->col-c2].val) return false;
    if (board->board[m->row-r2][m->col].assigned && v == board->board[m->row-r2][m->col].val) return false;
    if (board->board[m->row-r2][m->col+c1].assigned && v == board->board[m->row-r2][m->col+c1].val) return false;
    if (board->board[m->row-r2][m->col-c2].assigned && v == board->board[m->row-r2][m->col-c2].val) return false;
}

int CDcount(Do* m) {
    int res = 0;
    for (int i = 0;i<SIZE;i++) {
        res += m->curdom[i];
    }
    return res;
}

bool CheckCons(sudoku* board, int c, Do* m) {
    int R = m->row, C = m->col, tot = 0;
    // c == 0 >>> row
    if (c == 0) {
        for (int i = 0;i<SIZE;i++) {
            tot += board->board[R][i].assigned;
        }
        return (tot == SIZE-1);
    }
    // c == 1 >>> col
    else if (c == 1) {
        for (int i = 0;i<SIZE;i++) {
            tot += board->board[i][C].assigned;
        }
        return (tot == SIZE-1);
    }
    // c == 2 >>> Box
    else if (c == 2) {
        int r1, r2, c1, c2;
        if (m->row % 3 == 1) {
            r1 = 1; r2 = 1;
        }
        else if (m->row % 3 == 0) {
            r1 = 1; r2 = -2;
        }
        else {
            r1 = -1; r2 = 2;
        }
        if (m->col % 3 == 1) {
            c1 = 1; c2 = 1;
        }
        else if (m->col % 3 == 0) {
            c1 = 1; c2 = -2;
        }
        else {
            c1 = -1; c2 = 2;
        }
        if (board->board[m->row][m->col+c1].assigned) tot += 1;
        if (board->board[m->row][m->col-c2].assigned) tot += 1;
        if (board->board[m->row+r1][m->col].assigned) tot += 1;
        if (board->board[m->row+r1][m->col+c1].assigned) tot += 1;
        if (board->board[m->row+r1][m->col-c2].assigned) tot += 1;
        if (board->board[m->row-r2][m->col].assigned) tot += 1;
        if (board->board[m->row-r2][m->col+c1].assigned) tot += 1;
        if (board->board[m->row-r2][m->col-c2].assigned) tot += 1;
        return (tot == SIZE-2);
    }
    return false;
}

bool FCCheck(sudoku* board, int c, Do* m) {
    // c == 0 >>> row
    if (c == 0) for (int i = 0;i<SIZE;i++) {
        if (!m->curdom[i]) {
            m->curdom[i] = 1;
            if(RowCheck(board, m)) m->curdom[i] = 0; // No falsification
        }
    }
    // c == 1 >>> col
    else if (c == 1) for (int i = 0;i<SIZE;i++) {
        if (!m->curdom[i]) {
            m->curdom[i] = 1;
            if(ColCheck(board, m)) m->curdom[i] = 0; // No falsification
        }
    }
    // c == 2 >>> Box
    else if (c == 2) for (int i = 0;i<SIZE;i++) {
        if (!m->curdom[i]) {
            m->curdom[i] = 1;
            if(BoxCheck(board, m)) m->curdom[i] = 0; // No falsification
        }
    }
    if (CDcount(m) == SIZE) return false;
    else return true;
}

bool Goal(sudoku* board) {
    for (int i = 0;i<SIZE;i++) {
        for (int j = 0;j<SIZE;j++) {
            if (!board->board[i][j].assigned) return false;
        }
    }
    return true;
}

Do* heuristicpick(sudoku* board) {
    // MRV
    Do* maxi = &board->board[0][0];
    for (int i = 0;i<SIZE;i++) {
        for (int j = 0;j<SIZE;j++) {
            if(board->board[i][j].assigned) continue;
            if(CDcount(maxi) < CDcount(&board->board[i][j]) || maxi->assigned) {
                maxi = &board->board[i][j];
                if (CDcount(maxi) == SIZE-1) return maxi;
            }
        }
    }
    return maxi;
}

void propagate(sudoku* board, Do* m) {
    // Propagate to cols and rows
    for (int i = 0;i<SIZE;i++) {
        board->board[m->row][i].curdom[m->val-1] = 1;
        board->board[i][m->col].curdom[m->val-1] = 1;
    }
    // Propagate to Box
    int r1, r2, c1, c2;
    if (m->row % 3 == 1) {
        r1 = 1; r2 = 1;
    }
    else if (m->row % 3 == 0) {
        r1 = 1; r2 = -2;
    }
    else {
        r1 = -1; r2 = 2;
    }
    if (m->col % 3 == 1) {
        c1 = 1; c2 = 1;
    }
    else if (m->col % 3 == 0) {
        c1 = 1; c2 = -2;
    }
    else {
        c1 = -1; c2 = 2;
    }
    board->board[m->row][m->col+c1].curdom[m->val-1] = 1;
    board->board[m->row][m->col-c2].curdom[m->val-1] = 1;
    board->board[m->row+r1][m->col].curdom[m->val-1] = 1;
    board->board[m->row+r1][m->col+c1].curdom[m->val-1] = 1;
    board->board[m->row+r1][m->col-c2].curdom[m->val-1] = 1;
    board->board[m->row-r2][m->col].curdom[m->val-1] = 1;
    board->board[m->row-r2][m->col+c1].curdom[m->val-1] = 1;
    board->board[m->row-r2][m->col-c2].curdom[m->val-1] = 1;
}

void Copyboard(sudoku* dest, const sudoku* src) {
	memcpy(dest, src, sizeof(sudoku));
}

bool FC(sudoku* board, int level) {
    if (Goal(board)) {
        // Return when all cells are assigned.
        cout << "Goal!" << endl;
        display(board);
        return true;
    }
    Do* v = heuristicpick(board); // Pick with MRV
    v->assigned = true;
    bool dwo = false;
    int pos = 0;
    for (int i = 0;i<SIZE;i++) if (!v->curdom[i]) {
        sudoku boardcopy;
        Copyboard(&boardcopy, board);
        v->val = i+1;
        propagate(board, v);
        dwo = false;
        // row constraint
        if (!dwo && CheckCons(board, 0, v)) {
            for (int i = 0;i<SIZE;i++) if (!board->board[v->row][i].assigned) {
                dwo = !FCCheck(board, 0, &board->board[v->row][i]);
            }
        }
        // col constraint
        if (!dwo && CheckCons(board, 1, v)) {
            for (int i = 0;i<SIZE;i++) if (!board->board[i][v->col].assigned) {
                dwo = !FCCheck(board, 1, &board->board[i][v->col]);
            }
        }
        // Box constraint
        if (!dwo && CheckCons(board, 2, v)) {
            int r1, r2, c1, c2;
            if (v->row % 3 == 1) {
                r1 = 1; r2 = 1;
            }
            else if (v->row % 3 == 0) {
                r1 = 1; r2 = -2;
            }
            else {
                r1 = -1; r2 = 2;
            }
            if (v->col % 3 == 1) {
                c1 = 1; c2 = 1;
            }
            else if (v->col % 3 == 0) {
                c1 = 1; c2 = -2;
            }
            else {
                c1 = -1; c2 = 2;
            }
            int tmp_r, tmp_c;
            if (!board->board[v->row][v->col+c1].assigned) {tmp_r = v->row; tmp_c = v->col+c1;}
            else if (!board->board[v->row][v->col-c2].assigned) {tmp_r = v->row; tmp_c = v->col-c2;}
            else if (!board->board[v->row+r1][v->col].assigned) {tmp_r = v->row+r1; tmp_c = v->col;}
            else if (!board->board[v->row+r1][v->col+c1].assigned) {tmp_r = v->row+r1; tmp_c = v->col+c1;}
            else if (!board->board[v->row+r1][v->col-c2].assigned) {tmp_r = v->row+r1; tmp_c = v->col-c2;}
            else if (!board->board[v->row-r2][v->col].assigned) {tmp_r = v->row-r2; tmp_c = v->col;}
            else if (!board->board[v->row-r2][v->col+c1].assigned) {tmp_r = v->row-r2; tmp_c = v->col+c1;}
            else if (!board->board[v->row-r2][v->col-c2].assigned) {tmp_r = v->row-r2; tmp_c = v->col-c2;}
            dwo = !FCCheck(board, 2, &board->board[tmp_r][tmp_c]);
        }
        if(!dwo && FC(board, level + 1)) return true;
        Copyboard(board, &boardcopy);
    }
    v->assigned = false;
    return false;
}

void display(sudoku* board) {
	cout << "\t----------------------------------------" << endl;
    for (int i = 0;i<SIZE;i++) {
        cout << "\t|";
        for (int j = 0;j<SIZE;j++) {
            cout << " " << board->board[i][j].val << "  ";
            if (j%3 == 2) cout << "|";
        }
        cout << endl;
        if (i%3 == 2) cout << "\t----------------------------------------" << endl;
    }
}


2019/10 Karl

数独算法说明:用三个二维数组记录数独每个点的状态,SD(i, j)显示数值,也是真实数值(1到9)。ST(i, j)状态,1可由用户输入,2是题目给定的,不能改。SY(i, j)这符串,记录每个点中可能的值。 1、在进行自动计算时,只计算ST(i, j)为1的点,首先将所有状态为1的点的SY(i, j)值全部设为"123456789",SD(i, j)值全部设为0 2、逐点扫描,找到一个点,然后将该点所在的行、列、区域中已存在的SD(x, y)值从SY(i, j)中删除,因为数独规则是一个数值,在行、列、区域都不重复。 3、经第二步处理后,SY(i, j)为空,说明题目错误,SY(i, j)值为一位数字,就说明该点的值是唯一的,可以确定了。 4、剩余的SY(i, j)值最少也是二个数字的,或更多位数。随机从这些两位数的SY(i, j)中选取一个点。取其中的一位确定为该点的值后,重复第2步。如果错误遇错,则重复执行第4步。直到所有点都被确定。 注意:第2步是需要多次重复执行的,所有可用递归函数完成。如果执行结果出现错误(某数出现重复,或某点无值),需要对该过程所执行的所有操作进行回退。 第4步也是需要重复执行的。本和序用Goto跳转方式实现多次执行。 简单的数独,要么所有的点都具有独一值,第1步执行完成后,就已全部完成。或者具有多个解,随意猜测一个二位数的SY(i, j)的值都能成功。 难的数独,是可唯一确定的点很少,大部分点都有两种或多种可能的值,但最终正确答案只有一种或很少种解。 软件在自动计算过程中,具有很大的偶然性,对于骨灰级的数独题目在计算过程中,时间短的可能不到1秒就能完成,长的可能要几分钟,需要将各种可能性都测试一遍才有结果。 只要题目正确,多计算几次就能得到答案。 程序只处理有两种可能值的情况,对只存在三种可能值的情况未进一步处理,该情况非常极端了。 软件中包含网上下载的200个数独题目。
一、数独说明:数独由九行,九列组成,又分为顺序排列的九宫。每行、每列及每宫都包含九个格,九个格中填放1到9的不重复的数字。 二、自动计算原理(三步法): 1、基础法:找出空格中唯一可填的数字。方法是,先假设某空格中可填入九个数字,然后去掉所在行、所在列、所在宫中的已有数字。余下如果是唯一一个数字,那么这个数字就是结果 2、找唯一法:例如果某行、某列或某宫中只剩一个空格,那么九个数字中缺少的那个就是结果。 3、求唯余法:对于存在多个可能值的空格,循环取其中一个作为假设值,然后反复利用方法1和方法2去测试,如果出错冲突或导致别的空格无值可填时,说明假设的值是错误的。并对别剩余未找到唯一值的空格进行同样操作,直至找到可行的一个方案。 三、自动出题,是自动求解的反向过程,先给出答案,再组合题目: 1、数独难易程度跟数独已知的数字个数有一定关系,但不是必然关系。可分为四级,根据网友“数独难度分级”的文章https://wenku.baidu.com/view/af550ed51a37f111f1855ba0.html,结果是分布在0到1之间的一系列值,值越少越容易。 2、出题时,先利用随机数往81个空格中填入不冲突的值。方法是,因为对角线的三宫中的数字互不干扰,用随机数填充这三宫,然后根据数独规则要求随机填入另外六宫。 3、这是最终结果,然后根据难易要求,随机将结果中的一定数量(可以用随机数)的方格清空。数独题已经形成。再根据网友提供的级别计算公式,计算形成的数独题的难易程度是否符合要求。(此时的数独答案不是唯一的) 4、难易程度具体计算公式是:两个空格所有可能值如果有依赖关系值为1,没依赖关系值为0。如此汇总所有空格之间的关系值为A,再除以空格个数B的18倍。即A/(18*B)。0—0.25为0级,0.25—0.5为1级,0.5—0.75为2级,0.75—1为3组。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值