c2java 回溯之数独

本文介绍了一种通过深度优先搜索解决数独问题的方法,并提出了两种不同的搜索策略:一种是基本的搜索策略(P1),另一种是对搜索过程进行了优化的策略(P2)。文中详细解释了这两种策略的具体实现方式,并对比了它们的性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数独
9x9的格子均分为3x3块,其中一些格子填有1~9之间的数字。请把空格填满,使得
每行,每列和每个块中,1~9每个数字只出现一次。                                                                                                                                   
直接dfs, 可能要检查9^81个。
P1. 从左上角第一个空白开始,在搜索过程中,填每行,每列,每块没有出现过的数字。如果没有,就不要继续向下搜了。
        
P2. 如果某一行只有一个空白,或者某个格子可填的数只有一个,优先处理。
 如果某个空白的候选数字比较少,优先处理;因而一开始不必从左上角开始。
    
P1 作为基本的参考实现,否则程序都停不下来。搜索路径:从左上角到右下角先行后列填空白。
P1(x, y)表示以(x, y)为左上角开始找空格。

P2 作为优化实现,搜索路径不再是顺序的,到下一个空格的位置是动态计算的。
但是下面的P2()实现花的时间是P1()的4倍。一个改进办法是维护一个全局的每个空格的候选集。
因为当前的输入测试,P1()检查的节点数是291406, P2()是298451, 即使再改时间估计不会小于P1。

就不改了。可能对于 poj 3076 16x16 有用些。


语言注记: System.out.printf() 打印实在是影响运行时间,测试速度时应当把不必要的打印去掉。

代码:

import java.util.Scanner;
import java.util.Arrays;

public class Sudo{
    static final int N = 9, M = 3;
    static char[][] grid = null;
    static int cnt = 0;

    static boolean P2()
    {
        int i;
        int[] pos = new int[2];
        ++cnt;
        if(!hasBlank()){
            dump();
            return true;    
        }
        char[] cdt = getLeastCandidate(pos);
        if(0 == cdt.length)return false;
        for(i = 0; i < cdt.length; ++i){
            grid[pos[0]][pos[1]] = cdt[i];      
            if( P2()) return true;
            grid[pos[0]][pos[1]] = '0';
        }

        return false;
    }

    static boolean hasBlank()
    {
        int i, j;
        for(i = 0; i < N; ++i){
            for(j = 0; j < N; ++j){
                if(grid[i][j] == '0')return true;
            }
        }
        return false;
    }

    static char[] getLeastCandidate(int[] pos)
    {
        int i, j, k, ii = -1, jj = -1;
        int min = N+1, tmp;
        char[] cdt = new char[N];
        char[] bak = new char[N];                      
            for(i = 0; i < N; ++i){
            for(j = 0; j < N; ++j){
                if(grid[i][j] == '0'){
                    tmp = getCandidates(i, j, cdt); 
                    if(tmp < min){
                        min = tmp;
                        for(k = 0; k < min; ++k)bak[k] = cdt[k];
                        ii = i; jj = j;
                    }
                }
            }
        }
        pos[0] = ii; pos[1] = jj;
        bak = Arrays.copyOf(bak, min);  
        return bak;
    }

    static int getCandidates(int x, int y, char[] cdt)
    {
        int i, j, k, l;
        boolean[] mask = new boolean[N];

        for(i = 0; i < N; ++i)if(grid[x][i] != '0')mask[grid[x][i] - '1'] = true;
        for(i = 0; i < N; ++i)if(grid[i][y] != '0')mask[grid[i][y] - '1'] = true;       

        k = (x/3)*3; l = (y/3)*3;
        for(i = 0; i < M; ++i)for(j = 0; j < M; ++j)
            if(grid[k+i][l+j] != '0')mask[ grid[k+i][l+j]  - '1' ] = true;

        for(k = 0, i = 0; i < N; ++i)if(!mask[i]){
            cdt[k++] = (char)('1' + i);
        }   
        return k;
    }

    static boolean canFill(int x, int y, char c)
    {
        int i, j, k, l;

        for(j = 0; j < N; ++j)if(grid[x][j] == c)return false;
        for(i = 0; i < N; ++i)if(grid[i][y] == c)return false;  

        k = (x/3)*3; l = (y/3)*3;
        for(i = 0; i < M; ++i)for(j = 0; j < M; ++j)    
                if(grid[k+i][l+j] == c)return false;
        return true;
    }

    static boolean P1(int x, int y)
    {
        int ii, jj, i = -1, j = -1;
        int found = 0;
        char c;

        ++cnt;

        for(i = x, j = y; i < N && j < N; ){
            if('0' == grid[i][j]){
                found = 1; 
                break;
            }else{
                j = j + 1;
                if(j == N){i = i + 1; j = 0;}
            }
        }

        //if(i >= 7)System.out.printf("handle %d %d ij %d %d %n", x, y, i, j);
        if(0 == found){
            dump(); 
            return true;
        }
        for(c = '1'; c <= '9'; ++c){
            if(canFill(i, j, c)){
                grid[i][j] = c;

                if(j < N-1){ii = i; jj = j +1;}else{ii = i + 1; jj = 0;}
                if(P1(ii, jj))return true;
                grid[i][j] = '0'; /*还原,尝试下一个数字*/
            }
        }
        return false;   
    }

    static void dump()
    {
        int i, j;
        System.out.printf("grid:%n");
        for(i = 0; i < N; ++i){
            for(j = 0; j < N; ++j){           
                    System.out.printf("%c", grid[i][j]);
            }
            System.out.printf("%n");
        }
    }

    public static void main(String[] arg) throws Exception
    {
        Scanner in = new Scanner(System.in);
        grid = new char[N][N];
        int i, j;

        for(i = 0; i < N; ++i){
            String line = in.nextLine();
            for(j = 0; j < N; ++j){
                grid[i][j] = line.charAt(j);
            }
        }
        in.close(); 
        //P1(0, 0);
        P2();
        System.out.printf("nodes %d%n", cnt);
    }   
}

/*
$ cat in.txt 
000000520
080400000
030009000
501000600
200700000
000300000
600010000
000000704
000000030
$ time cat in.txt  | java Sudo
grid:
416837529
982465371
735129468
571298643
293746185
864351297
647913852
359682714
128574936

real    0m0.386s
user    0m0.471s
sys     0m0.043s
*/                                     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值