数独Sudoku(DFS)

题目:

数独是一种填数字游戏,英文名叫 Sudoku。
玩家需要根据 9×9盘面上的已知数字,推理出所有剩余位置的数字,并满足每一行、每一列、每一个粗线九宫格内的数字包含有 1-9 的数字,且不重复。
在这里插入图片描述
输入描述:
多测试用例,输入T表示测试用例个数。
输入每一组数据,共 9 行 9 列,表示初始数独(其中 0 表示数独中的空位)。
输出描述:
输出共 9 行 9 列,表示数独的解。
Sample Input:
1
103000509
002109400
000704000
300502006
060000050
700803004
000401000
009205800
804000107
Sample Output:
143628579
572139468
986754231
391542786
468917352
725863914
237481695
619275843
854396127

代码如下:
hang[ i ][ j ]表示第i行的数j是否已填,lie[ i ][ j ]表示第i列的数j是否已填,dyg[ i ][ j ]表示第i个3×3单元格的数j是否已填。

/*140ms*/
#include<cstdio>
#include<cstring>
using namespace std;
int G[10][10];
bool hang[10][10],lie[10][10],dyg[10][10],flag;///hang表示行,lie表示列,dyg表示3×3的单元格
void _dfs(int x,int y)///x为行,y为列
{
    if(flag)return;
    if(x==9){ ///全部搜完
        flag=1;
        for(int i=0;i<9;i++){
            for(int j=0;j<9;j++){
                printf("%d",G[i][j]);
            }
            printf("\n");
        }
        return;
    }
    else{
        if(G[x][y]){///该位置已经有数字的情况
            if(y<8)_dfs(x,y+1);///未到最后一列,则继续搜索下一列
            else _dfs(x+1,0);///已到最后一列,则搜索下一行的第一列
        }
        else{///该位置没有数字的情况
            for(int i=1;i<=9;i++){
                if(!hang[x][i]&&!lie[y][i]&&!dyg[x/3*3+y/3][i]){
                    G[x][y]=i;///保存搜到的数
                    hang[x][i]=lie[y][i]=dyg[x/3*3+y/3][i]=true;///标记
                    if(y<8)_dfs(x,y+1);///未到最后一列,则继续搜索下一列
                    else _dfs(x+1,0);///已到最后一列,则搜索下一行的第一列
                    G[x][y]=0;///回溯,该位置重新标为没有放入数字
                    hang[x][i]=lie[y][i]=dyg[x/3*3+y/3][i]=false;///回溯
                }
            }
        }
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
    	/*初始化*/
        memset(hang,false,sizeof(hang));
        memset(lie,false,sizeof(lie));
        memset(dyg,false,sizeof(dyg));
        flag=0;
        /*输入初始数独*/
        for(int i=0;i<9;i++)
        for(int j=0;j<9;j++){
            scanf("%1d",&G[i][j]);///输入没有包含空格,所有要用到%1d这个操作
            if(G[i][j]!=0){
                int v=G[i][j];
                hang[i][v]=true;
                lie[j][v]=true;
                dyg[i/3*3+j/3][v]=true;///或者可以用个三维数组dyg[i/3][j/3][v]来标记3×3的单元格
            }
        }
        _dfs(0,0);///第1行第1列
    }
    return 0;
}

E.数独挑战——2019年华南理工大学程序设计竞赛(春季赛):

链接:https://ac.nowcoder.com/acm/contest/625/E
单测试用例,题意与上述题目相同。
输入样例:
5 3 0 0 7 0 0 0 0
6 0 0 1 9 5 0 0 0
0 9 8 0 0 0 0 6 0
8 0 0 0 6 0 0 0 3
4 0 0 8 0 3 0 0 1
7 0 0 0 2 0 0 0 6
0 6 0 0 0 0 2 8 0
0 0 0 4 1 9 0 0 5
0 0 0 0 8 0 0 7 9
输出样例:
5 3 4 6 7 8 9 1 2
6 7 2 1 9 5 3 4 8
1 9 8 3 4 2 5 6 7
8 5 9 7 6 1 4 2 3
4 2 6 8 5 3 7 9 1
7 1 3 9 2 4 8 5 6
9 6 1 5 3 7 2 8 4
2 8 7 4 1 9 6 3 5
3 4 5 2 8 6 1 7 9

以下做法不用另开几个标记数组,但运行时间慢于上面那份代码,代码如下:

#include<bits/stdc++.h>
using namespace std;
int G[10][10],flag;
bool check(int x,int y,int k)
{
    for(int i=0;i<9;i++)
        if(G[x][i]==k)return false;///该行有重复的数
    for(int j=0;j<9;j++)
        if(G[j][y]==k)return false;///该列有重复的数
    int a=x/3*3,b=y/3*3;///先除3是计算属于哪个宫内的,再乘3是计算该宫第一个数的下标
    for(int i=a;i<a+3;i++)
        for(int j=b;j<b+3;j++)
            if(G[i][j]==k)return false;///该3*3宫内有重复
    return true;
}
void dfs(int x,int y)
{
	if(flag) return;
    if(x==9&&y==0){///完整搜到了最后一行最后一列,特别注意搜完最后一列后是dfs(9,0)即跳到了第10行的第1列
        for(int i=0;i<9;i++){
            for(int j=0;j<9;j++){
                printf("%d ",G[i][j]);
            }
            puts("");
        }
        flag = 1;
        return;
    }
    if(y>8)dfs(x+1,0);///到达最后一列,搜索下一行
    else{///未到最后一列的情况
        if(G[x][y])dfs(x,y+1);///若该位置已经有数字,则继续下一列
        else{///该位置没数字的情况
            for(int k=1;k<=9;k++){
                if(check(x,y,k)){
                    G[x][y]=k;///保存搜到的数
                    dfs(x,y+1);///继续搜索下一列
                    G[x][y]=0;///回溯,把该格重新标为0即没有放入数字
                }
            }
        }
    }
}
int main()
{
	flag=0;
    for(int i=0;i<9;i++)
    for(int j=0;j<9;j++)
        scanf("%d",&G[i][j]);
    dfs(0,0);///从第1行第1列开始搜
    return 0;
}

数独的舞蹈链(Dancing Links)做法:

(比dfs快很多,贴一下别人代码,待以后学习)

#include <cassert>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iterator>
#include <ctime>
#include <algorithm>
#include <bitset>
#include <deque>
#include <iostream>
#include <iomanip>
#include <limits>
#include<functional>
#include <list>
#include <map>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <utility>
#include <vector>
#include <numeric>
//#include<unordered_map>
#define inf  2e9
#define seed 131
#define pi acos(-1)
#define mem(a,b) memset(a,b,sizeof(a))
#define lowbit(x) ((x)&(-x))
#define lrtnb srand(unsigned(time(NULL)))
#define lrtlh ios::sync_with_stdio(0)
#define lowb(a,n,x) lower_bound(a,a+n,x) - a
#define uppb(a,n,x) upper_bound(a,a+n,x) - a
#define lson l,mid,i<<1;
#define rson mid+1,r,i<<1|1;
#define ALL(x) x.begin(),x.end()
#define inv inline void
#define INS(x) inserter(x,x.begin())
using namespace std;
typedef long long ll;
typedef long double ld;
typedef priority_queue<int> pqi;
typedef priority_queue<ll> pql;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef map<int, int> mii;
typedef map<ll, ll> mll;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef queue<int> qi;
typedef queue<ll> ql;
typedef stack<int> si;
typedef stack<ll> sl;
typedef set<int> Si;
typedef set<ll> Sl;
ll gcd(ll n, ll m) { long long r; if (n < m)swap(n, m); while (m) { r = n % m; n = m; m = r; }return n; }
ll lcm(ll b, ll y) { return b * y / gcd(b, y); }
double das(double x1, double y1, double x2, double y2) { double ans = (x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2); return sqrt(ans); }
double random(double start, double en) { return start + (en - start)*rand() / (RAND_MAX + 1.0); }
inline void read(int &n) { char c = '+'; int x = 0; bool flag = 0; while (c<'0' || c>'9') { c = getchar(); if (c == '-')flag = 1; }while (c >= '0'&&c <= '9')x = x * 10 + c - 48, c = getchar(); n = flag == 1 ? -x : x; }
//舞蹈链
//最大行数
const int MN = 1010;
//最大列数
const int MM = 500;
//最大点数
const int MNN = MN * MM + 10;
struct DLX
{
    //一共n行m列,s个节点
    int n, m, size;
    //交叉十字链表组成部分
    //第i个节点的上U下D左L右R,所在位置row行col列
    int U[MNN], D[MNN], L[MNN], R[MNN], row[MNN], col[MNN];
    //H数组记录行选择指针,S数组记录覆盖个数
    int H[MNN], S[MM];
    //res记录行个数,ans数组记录可行解
    int ansd, ans[MN];
    //初始化空表
    void init(int x, int y)
    {
        n = x, m = y;
        //其中0节点作为head节点,其他作为列首节点
        for (int i = 0; i <= m; ++i) {
            U[i] = D[i] = i;
            L[i] = i - 1;
            R[i] = i + 1;
        }
        R[m] = 0; L[0] = m;
        size = m;
        memset(S, 0, sizeof(S));
        memset(H, -1, sizeof(H));
    }
    void Link(int r, int c)
    {
        //节点数加一,设置s节点所处位置,以及S列覆盖个数加一
        size++; row[size] = r; col[size] = c; S[c]++;
        //将s节点插入对应列中
        D[size] = D[c]; U[D[c]] = size;
        U[size] = c; D[c] = size;
        if (H[r] < 0) {//如果该行没有元素,H[r]标记该行起始节点
            H[r] = L[size] = R[size] = size;
        }
        else {
            //将该节点插入该行第一个节点后面
            R[size] = R[H[r]];
            L[R[H[r]]] = size;
            L[size] = H[r];
            R[H[r]] = size;
        }
    }
    //精确覆盖
    void remove(int c)
    {
        //删除c列
        L[R[c]] = L[c]; R[L[c]] = R[c];
        //删除该列上的元素对应的行
        for (int i = D[c]; i != c; i = D[i]) {//枚举该列元素
            for (int j = R[i]; j != i; j = R[j]) {//枚举列的某个元素所在行遍历
                U[D[j]] = U[j];
                D[U[j]] = D[j];
                //将该列上的S数组减一
                --S[col[j]];
            }
        }
    }
    void resume(int c)
    {
        //恢复c列
        for (int i = U[c]; i != c; i = U[i]) {//枚举该列元素
            for (int j = L[i]; j != i; j = L[j]) {
                U[D[j]] = j; D[U[j]] = j;
                ++S[col[j]];
            }
        }
        L[R[c]] = c; R[L[c]] = c;
    }
    bool dancing(int deep)
    {
        //if (ansd < deep) return false;
        //当矩阵为空时,说明找到一个可行解,算法终止
        if (R[0] == 0) {
            //ansd = min(ansd, deep);
            ansd = deep;
            return true;
        }
        //找到节点数最少的列,枚举这列上的所有行
        int c = R[0];
        for (int i = R[0]; i != 0; i = R[i]) {
            if (S[i] < S[c]) {
                c = i;
            }
        }
        //删除节点数最少的列
        remove(c);
        for (int i = D[c]; i != c; i = D[i]) {
            //将行r放入当前解
 
            //行上节点对应的列上进行删除
            for (int j = R[i]; j != i; j = R[j])
                remove(col[j]);
            ans[deep] = row[i];
            //进入下一层
            if (dancing(deep + 1))return true;
            //对行上的节点对应的列进行恢复
            for (int j = L[i]; j != i; j = L[j])
                resume(col[j]);
        }
        //恢复节点数最少列
        resume(c);
        return false;
    }
 
    //重复覆盖
    //将列与矩阵完全分开
    void Remove(int c)
    {
        for (int i = D[c]; i != c; i = D[i]) {
            L[R[i]] = L[i];
            R[L[i]] = R[i];
        }
    }
    void Resume(int c)
    {
        for (int i = D[c]; i != c; i = D[i]) {
            L[R[i]] = R[L[i]] = i;
        }
    }
    int vis[MNN];
    //估价函数,模拟删除列,H(),函数返回的是至少还需要多少行才能完成重复覆盖
    int A()
    {
        int dis = 0;
        for (int i = R[0]; i != 0; i = R[i]) vis[i] = 0;
        for (int i = R[0]; i != 0; i = R[i]) {
            if (!vis[i]) {
                dis++; vis[i] = 1;
                for (int j = D[i]; j != i; j = D[j]) {
                    for (int k = R[j]; k != j; k = R[k]) {
                        vis[col[k]] = 1;
                    }
                }
            }
        }
        return dis;
    }
    void dance(int deep)
    {
        if (!R[0]) {
            //cout << res << endl;
            ansd = deep;
            return;
        }
        if (deep + A() >= ansd) return;
        int c = R[0];
        for (int i = R[0]; i != 0; i = R[i]) {
            if (S[i] < S[c]) {
                c = i;
            }
        }
        for (int i = D[c]; i != c; i = D[i]) {
            //每次将第i列其他节点删除,只保留第i节点,为了找该行的节点
            Remove(i);
            //将列上的节点完全与矩阵脱离,只删列首节点是不行的
            for (int j = R[i]; j != i; j = R[j]) {
                Remove(j);
            }
            dance(deep + 1);
            for (int j = L[i]; j != i; j = L[j]) {
                Resume(j);
            }
            Resume(i);
        }
    }
};
DLX dzl;
int mp[15][15];
int x[1010];
int y[1010];
int kk[1010];
int main() {
    int s[15][15];
    for (int i = 1; i <= 9; i++) {
        for (int j = 1; j <= 9; j++)
            cin >> mp[i][j];
    }
    dzl.init(800, 324);
    int len = 1;
    for (int i = 1; i <= 9; i++) {
        for (int j = 1; j <= 9; j++) {
            if (mp[i][j] == 0) {
                for (int k = 1; k <= 9; k++) {
                    dzl.Link(len, (i - 1) * 9 + j);
                    dzl.Link(len, (i - 1) * 9 + k + 81);
                    dzl.Link(len, (j - 1) * 9 + k + 162);
                    dzl.Link(len, (((i - 1) / 3) * 3 + ((j + 2) / 3) - 1) * 9 + k + 243);
                    x[len] = i, y[len] = j, kk[len] = k;
                    len++;
                }
            }
            else {
                int k = mp[i][j];
                dzl.Link(len, (i - 1) * 9 + j);
                dzl.Link(len, (i - 1) * 9 + k + 81);
                dzl.Link(len, (j - 1) * 9 + k + 162);
                dzl.Link(len, (((i - 1) / 3) * 3 + ((j + 2) / 3) - 1) * 9 + k + 243);
                x[len] = i, y[len] = j, kk[len] = k;
                len++;
            }
        }
    }
    dzl.dancing(0);
    //cout << dzl.ansd << endl;
    for (int i = 0; i < dzl.ansd; i++) {
        int r = dzl.ans[i];
        mp[x[r]][y[r]] = kk[r];
    }
    for (int i = 1; i <= 9; i++) {
        for (int j = 1; j <9; j++) {
            cout << mp[i][j]<<" ";
        }
        cout << mp[i][9] << endl;;
    }
    system("pause");
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用MATLAB的深度优先搜索算法来实现七宫格不规则数独。具体步骤如下: ```MATLAB % 1. 定义数独的初始状态 sudoku = [0 0 0 0 0 0 0; 0 0 0 0 0 0 0; 0 0 0 0 0 0 0; 0 0 0 0 0 0 0; 0 0 0 0 0 0 0; 0 0 0 0 0 0 0; 0 0 0 0 0 0 0]; % 2. 定义数独的规则 % 七宫格不规则数独的规则可以参考引用[2]中的数字矩阵 % 将数字矩阵转换为一个包含81个元素的向量 rule = [1 1 1 2 2 2 3 3 3 ... 1 1 1 2 2 2 3 3 3 ... 1 1 1 2 2 2 3 3 3 ... 4 4 4 5 5 5 6 6 6 ... 4 4 4 5 5 5 6 6 6 ... 4 4 4 5 5 5 6 6 6 ... 7 7 7 8 8 8 9 9 9 ... 7 7 7 8 8 8 9 9 9 ... 7 7 7 8 8 8 9 9 9]; % 3. 定义深度优先搜索算法 function [sudoku, success] = dfs(sudoku, rule, index) % 如果已经填满了数独,则返回成功 if index > 81 success = true; return; end % 计算当前位置的行和列 row = ceil(index / 9); col = mod(index - 1,9) + 1; % 如果当前位置已经有数字,则跳过 if sudoku(row, col) ~= 0 [sudoku, success] = dfs(sudoku, rule, index + 1); return; end % 尝试填入数字 for num = 1:9 % 检查当前数字是否符合规则 if check(sudoku, rule, row, col, num) sudoku(row, col) = num; % 递归搜索下一个位置 [sudoku, success] = dfs(sudoku, rule, index + 1); if success return; end sudoku(row, col) = 0; end end % 如果所有数字都尝试过了,仍然无法得到解,则返回失败 success = false; end % 4. 定义检查函数 function valid = check(sudoku, rule, row, col, num) % 检查行和列是否符合规则 if any(sudoku(row, :) == num) || any(sudoku(:, col) == num) valid = false; return; end % 检查七宫格是否符合规则 region = rule((row - 1) * 9 + col); [r, c] = find(rule == region); if any(sudoku(r, c) == num) valid = false; return; end valid = true; end % 5. 调用深度优先搜索算法求解数独 [sudoku, success] = dfs(sudoku, rule, 1); % 6. 输结果 if success disp(sudoku); else disp('No solution found.'); end ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值