Java解数独算法(非舞蹈链)

前言:这是一篇算法题的AC代码。

我首先是在leetcode上刷到这道题,然而用例过少,因此在51nod上又做了一遍。作为Java代码,运行时间大约在900ms,效率无疑是十分低下的。

然而想要更进一步的优化,就只能选择舞蹈链,我尚未读懂……严格来说,思路是读懂了,代码还没有……所以先贴上这份AC代码作为日记,其中注释很详细,内置了若干数据,欢迎大神指点。

原题:51nod 题号1211 解数独。

import java.io.*;
import java.util.*;

// 本方案充分利用整数的数位进行判断,从而可以简单地通过一次合并逻辑与得到可选数值
// 但是在数据检测的重复性上处理存在重大缺失,尽管通过廉价的布尔值判断可以过滤掉
// 无意义的大量的重复的判定依然消费了太多计算量
// 接下来要做的事情就是读懂舞蹈链解数独的真正算法
public class Solution {

    // 解决方案
    List backdate = new ArrayList<>();
    int[][] board;
    public boolean solveSudoku(int[][] board) {
        this.init(board);
        if(this.tryBack(0) != 1) {
            return false;
        }
        return true;
    }
    
    // 主递归函数,即便不增加剪枝函数也一样能完成任务,不过效率会低很多
    private int tryBack(int index) {
        if(index==backdate.size()) {
            // 递归到列的终点说明成功解得一个数组
           this.loadBoard();
           return 1;
        }
        // e 是坐标约定,其二进制最后四位表示j,五到八位表示i
        int e = backdate.get(index);
        if(isExisted(e)) return tryBack(index+1);
        
        // 数独中可选数值的数位表示
        int selection = this.get(e);
        // 约定 t 是数位表示转化为实值的结果,记录为0~8
        int t = 0;
        boolean hasAnswer = false;
        while(selection>0) {
            // 末位为0意味着这个数不可选
            if((selection&1)==0) {
                selection >>= 1;
                t++;
                continue;
            }
            // 尝试选中当前值
            this.push(e, t);
            // 尝试剪枝
            if(!this.fastScaning(e)) {
                // 剪枝失败时重置判定集已经在剪枝函数中完成,这里只需要重置当前选中值
                this.pop(e);
                selection >>= 1;
                t++;
                continue;
            }
            // 尝试递归
            int tryNext = tryBack(index+1);
            // 根据提议要求,多解、无解的情况都返回 No Solution 当存在多解时无需再对数据做任何处理
            if(tryNext == -1) return -1;
            if(tryNext == 1 && hasAnswer) return -1;
            if(tryNext == 1) hasAnswer = true;
            // 重置判定集,进入下一次尝试
            this.clearResults();
            this.pop(e);
            selection >>= 1;
            t++;
        }
        return hasAnswer ? 1 : 0;
    }
    
    // 剪枝成功时将这一批数据压入栈,主递归函数结束当前节点时需要通过栈读出数据并重置状态
    private boolean fastScaning(int e) {
        int i = e>>4;
        int j = e&15;
        List marks = new ArrayList<>();
        if(this.fastScaning(i, j, marks)) {
            scanStack.push(marks);
            return true;
        } else {
            for(int e_ : marks) {
                this.pop(e_);
            }
            return false;
        }
    }

    // 快速剪枝,当坐标可选数字为0时,这是失败的数据。否则如果是1,则将其填充进去。
    private boolean fastScaning(int i, int j, List marks) {  
        
        int[] xs = new int[21];
        int[] ys = new int[21];  
        this.findNexts(xs, ys, i, j);        
        
        for(int p=0; p<21; p++) {
            int x = xs[p];
            int y = ys[p];
            if(isExisted(x, y)) {
                continue;
            }
            int vals = this.get(x, y);
            int n = this.count(vals);
            if(n == 0) {
                return false;
            } else if(n == 1) {
                int t = this.convert(vals);
                marks.add((x<<4)|y);
                this.push(x, y, t);
                if(!fastScaning(x, y, marks)) {
                    return false;
                }
            }
        }
        return true;
    }
    
    // 失败的处理逻辑 -> 意图通过当前坐标寻找行、列、九宫作为"接下来要寻找的数据" -> 却导致了大量的重复判定,舞蹈链则不存在这个问题
    int[][] incolrow = {{3,4,5,6,7,8}, {0,1,2,6,7,8}, {0,1,2,3,4,5}};
    int[][] insqr = {{0,1,2}, {3,4,5}, {6,7,8}};
    private void findNexts(int[] xs, int[] ys, int i, int j) {

        for(int x=0; x<6; x++) {
            xs[x] = i;
            ys[x] = incolrow[i/3][x];
        }
        for(int y=0; y<6; y++) {
            xs[y+6] = incolrow[j/3][y];
            ys[y+6] = j;
        }
        for(int x=0; x<3; x++) {
            for(int y=0; y<3; y++) {
                xs[(x*3+y)+12] = insqr[i/3][x];
                ys[(x*3+y)+12] = insqr[j/3][y];
            }
        }
    }
    
    // 数据逻辑区
    int[] row = new int[9];
    int[] col = new int[9];
    int[] sqr = new int[9];

    // 返回当前i,j可选数字的数位表示
    private int get(int e) {
        return this.get(e>>4, e&15);
    }
    private int get(int i, int j) {
        return row[i] & col[j] & sqr[(i/3*3)+(j/3)];
    }
    // 将给定数值加入到当前坐标的判定集中
    private void push(int e, int num) {
        int i = e>>4;
        int j = e&15;
        this.push(i, j, num);
    }
    private void push(int i, int j, int num) {
        int b = ~(1<>4;
        int j = e&15;
        int b = (1<> scanStack = new Stack<>();
    private boolean isExisted(int e) {
        return this.isExisted(e>>4, e&15);
    }
    private boolean isExisted(int i, int j) {
        return results[i][j]!=0;
    }
    private void addResult(int i, int j, int value) {
        results[i][j] = value+1;
    }
    private int findResult(int i, int j) {
        return results[i][j] - 1;
    }
    private void delResult(int i, int j) {
        results[i][j] = 0;
    }
    private void clearResults() {
        List marks = scanStack.pop();
        for(int e : marks) {
            this.pop(e);
        }
    }
    private void loadBoard() {
        for(int i=0; i<9; i++) {
            for(int j=0; j<9; j++) {
                board[i][j] = results[i][j];
            }
        }
    }
    
    // 工具区
    // 对待处理列标进行排序,数据量足够小,采用冒泡排序即可
    private void sort(List backdate) {
        for(int i=0; ii; j--) {
                int a = backdate.get(j-1);
                int b = backdate.get(j);
                if(bigThan(a, b)) {
                    backdate.set(j-1, b);
                    backdate.set(j, a);
                }
            }
        }
    }
    private boolean bigThan(int sit1, int sit2) {
        return this.count(get(sit1)) > this.count(get(sit2));
    }
    // 标准函数,统计整数在二进制位上有几个1
    private int count(int m) {
         int temp = m - ((m>>1)&033333333333) - ((m>>2)&011111111111);
         return ((temp + (temp>>3)) & 030707070707) % 63;
    }
    // 当数位上有且只有一个1时,其位数恰好表示数独中唯一可选的值
    private int convert(int p2n) {
        int t = 0;
        while(p2n!=1) {
            t++;
            p2n>>=1;
        }
        return t;
    }
    
    // 主函数处理区
    private static void readsudo(int[][] sudo, Scanner scan) {
        for(int i=0; i<9; i++) {
            sudo[i] = new int[9];
            for(int j=0; j<9; j++) {
                sudo[i][j] = scan.nextInt();
            }
        }
    }
    
    private static void printSudo(int[][] sudo) throws Exception  {
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out), 1 << 16);
        for(int[] cs : sudo) {
            for(int c : cs) {
                writer.write(c + " ");
            }
            writer.write("\r\n");
        }
        writer.flush();
    }

    public static void main(String args[]) throws Exception {
        
        /*
        String data = 
            "0 6 0 5 9 3 0 0 0\n"+
            "9 0 1 0 0 0 5 0 0\n"+
            "0 3 0 4 0 0 0 9 0\n"+
            "1 0 8 0 2 0 0 0 4\n"+
            "4 0 0 3 0 9 0 0 1\n"+
            "2 0 0 0 1 0 6 0 9\n"+
            "0 8 0 0 0 6 0 2 0\n"+
            "0 0 4 0 0 0 8 0 7\n"+
            "0 0 0 7 8 5 0 1 0";
        */
        /*
        String data = 
            "0 0 0 3 4 0 0 0 0\n"+
            "8 0 0 0 0 0 0 2 0\n"+
            "0 7 0 0 1 0 5 0 0\n"+
            "4 0 0 0 0 5 3 0 0\n"+
            "0 1 0 0 7 0 0 0 6\n"+
            "0 0 3 2 0 0 0 8 0\n"+
            "0 6 0 5 0 0 0 0 9\n"+
            "0 0 4 0 0 0 0 3 0\n"+
            "0 0 0 0 0 9 7 0 0";
        */
        /*
        String data = 
            "0 0 0 0 0 0 0 0 0\n"+
            "0 0 0 0 0 0 0 0 0\n"+
            "0 0 0 0 0 0 0 0 0\n"+
            "0 0 0 0 0 0 0 0 0\n"+
            "0 0 0 0 0 0 0 0 0\n"+
            "0 0 0 0 0 0 0 0 0\n"+
            "0 0 0 0 0 0 0 0 0\n"+
            "0 0 0 0 0 0 0 0 0\n"+
            "0 0 0 0 0 0 0 0 0";
        */
        /*
        String data = 
            "0 0 0 4 0 0 0 0 7\n"+
            "0 1 0 0 0 0 0 0 0\n"+
            "0 0 0 0 2 0 0 0 0\n"+
            "6 0 0 0 5 0 2 7 0\n"+
            "0 0 0 0 0 0 8 3 0\n"+
            "7 0 0 0 0 0 0 0 0\n"+
            "2 0 6 0 0 0 5 0 0\n"+
            "0 0 0 1 0 0 0 0 4\n"+
            "0 0 0 3 0 0 0 0 0";
        */
        /*
        String data = 
            "0 6 0 5 9 3 0 0 0\n"+
            "9 0 1 0 0 0 5 0 0\n"+
            "0 3 0 4 0 0 0 9 0\n"+
            "1 0 8 0 2 0 0 0 4\n"+
            "4 0 0 3 0 9 0 0 1\n"+
            "2 0 0 0 1 0 6 0 9\n"+
            "0 8 0 0 0 6 0 2 0\n"+
            "0 0 4 0 0 0 8 0 7\n"+
            "0 0 0 7 8 5 0 1 0";
        */
        /*
        String data = 
            "7 6 2 5 0 3 1 4 8\n"+
            "9 4 1 0 0 8 5 3 6\n"+
            "8 3 5 4 0 0 7 9 2\n"+
            "1 9 8 6 2 0 3 5 4\n"+
            "4 7 6 3 5 0 2 8 1\n"+
            "2 5 3 8 1 0 6 7 9\n"+
            "3 8 7 1 4 0 9 2 5\n"+
            "5 1 4 9 3 0 8 6 7\n"+
            "6 2 9 7 8 0 4 1 3";
         */

        // 51nod 题号 1211 数据格式
        Scanner scan = new Scanner(new ByteArrayInputStream(data.getBytes()));
        
        int[][] sudo = new int[9][];
        
        readsudo(sudo, scan);

        
        if(new Solution().solveSudoku(sudo)) {
            printSudo(sudo);
        } else {
            System.out.println("No Solution");
        }
    }
}

你可能感兴趣的:(java)