数独解题程序

一个假期过去,明显编程水平大降,键盘都敲不好了,于是就想着恢复一下。怎么恢复呢?写个数独解题程序吧。

public class Sudoku {
    int[][] data = new int[9][9];//填好的数字
    List<Integer>[][] dataLeft = new ArrayList[9][9];//每个位置可能的数字

    public Sudoku(int data[][]) {
        this();
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (data[i][j] != 0) {
                    putNumber(i, j, data[i][j]);
                }
            }
        }
    }

    public Sudoku() {
        //初始化数据
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                data[i][j] = 0;
                dataLeft[i][j] = new ArrayList<Integer>();
                for (int k = 1; k <= 9; k++) {
                    dataLeft[i][j].add(k);
                }
            }
        }
    }

    public void putNumber(int x, int y, Integer n) {
        data[x][y] = n;//此点位置赋值
        dataLeft[x][y].clear();//可能数字清空;
        //清理水平和竖直
        for (int i = 0; i < 9; i++) {
            if (i != x) {
                dataLeft[i][y].remove(n);
            }
            if (i != y) {
                dataLeft[x][i].remove(n);
            }
        }
        //清理小空格
        int startX = x / 3;
        int startY = y / 3;
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                if (i + startX * 3 != x && j + startY * 3 != y) {
                    dataLeft[i + startX * 3][j + startY * 3].remove(n);
                }
            }
        }
    }

    public void compute() {
        int count = 0;
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (dataLeft[i][j].size() == 1) {
                    putNumber(i, j, dataLeft[i][j].get(0));
                    count++;
                }
            }
        }
        if (count == 0) {
            System.out.println("Compute completed.");
        } else {
            compute();
        }
    }

    public void print() {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                System.out.printf("%d ", data[i][j]);
            }
            System.out.println();
        }
    }
}

编写测试的入口方法:

public class Test {
    public static void main(String[] args) {
      int[][]data={
              {6,0,5,0,0,2,0,0,8},
              {0,4,0,3,8,0,0,5,0},
              {3,0,0,0,6,0,7,0,2},
              {0,5,0,0,0,3,0,4,0},
              {0,6,1,0,0,4,8,7,0},
              {0,2,0,0,7,8,0,6,0},
              {1,0,9,0,2,0,0,0,4},
              {0,7,0,0,4,1,0,2,0},
              {2,0,0,9,0,0,5,0,7}};
        Sudoku sudoku=new Sudoku(data);
        sudoku.compute();
        sudoku.print();
    }
}
下面是运行结果:
Compute completed.
6 9 5 7 1 2 4 3 8 
7 4 2 3 8 9 1 5 6 
3 1 8 4 6 5 7 9 2 
8 5 7 6 9 3 2 4 1 
9 6 1 2 5 4 8 7 3 
4 2 3 1 7 8 9 6 5 
1 3 9 5 2 7 6 8 4 
5 7 6 8 4 1 3 2 9 
2 8 4 9 3 6 5 1 7
事实证明,简单的数独确实是可以算出来的。

这时,再给一个复杂的算一下:

public class Test1 {
    public static void main(String[] args) {
      int[][]data={
              {0,0,0,0,0,8,1,0,0},
              {7,0,0,9,0,0,0,0,6},
              {0,0,8,0,3,0,7,0,0},
              {4,0,0,0,0,9,3,0,0},
              {8,0,0,0,7,0,0,0,5},
              {0,0,3,0,2,5,0,0,9},
              {0,0,4,0,6,0,2,0,0},
              {5,0,0,0,0,2,0,0,7},
              {0,0,2,1,0,0,0,0,0}};
        Sudoku sudoku=new Sudoku(data);
        sudoku.compute();
        sudoku.print();
    }
}

计算结果如下:

Compute completed.
0 0 0 0 0 8 1 0 0 
7 0 0 9 0 0 0 0 6 
0 0 8 0 3 0 7 0 0 
4 0 0 0 0 9 3 0 0 
8 0 0 0 7 0 0 0 5 
0 0 3 0 2 5 0 0 9 
0 0 4 0 6 0 2 0 0 
5 0 0 0 0 2 0 0 7 
0 0 2 1 0 0 0 0 0

呵呵,显而易见,它的计算能力太差了,已经无法计算了。

吃过饭之后,再对数独程序进行增强。增强之后,成为下面的样子:

public class Sudoku {
    int[][] data = new int[9][9];//填好的数字
    List<Integer>[][] dataLeft = new ArrayList[9][9];//每个位置可能的数字


    public Sudoku(int data[][]) {
        this();
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (data[i][j] != 0) {
                    putNumber(i, j, data[i][j]);
                }
            }
        }
    }


    public Sudoku() {
        //初始化数据
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                data[i][j] = 0;
                dataLeft[i][j] = new ArrayList<Integer>();
                for (int k = 1; k <= 9; k++) {
                    dataLeft[i][j].add(k);
                }
            }
        }
    }


    public void putNumber(int x, int y, Integer n) {
        data[x][y] = n;//此点位置赋值
        dataLeft[x][y].clear();//可能数字清空;
        //清理水平和竖直
        for (int i = 0; i < 9; i++) {
            if (i != x) {
                dataLeft[i][y].remove(n);
            }
            if (i != y) {
                dataLeft[x][i].remove(n);
            }
        }
        //清理小空格
        int startX = x / 3;
        int startY = y / 3;
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                if (i + startX * 3 != x && j + startY * 3 != y) {
                    dataLeft[i + startX * 3][j + startY * 3].remove(n);
                }
            }
        }
    }


    public void compute() {
        int count = 0;
        //计算某个位置只有一个数字的情况
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (dataLeft[i][j].size() == 1) {//如果只有一个可能的数字,那就是它了
                    putNumber(i, j, dataLeft[i][j].get(0));
                    count++;
                }
            }
        }
        if (count == 0) {//如果没有唯一一个数的,则查找只出现过一次的
            count += computeRow();
            count += computeColumn();
            count += computeBox();
        }


        if (count > 0) {
            compute();
        }
    }


    public int computeRow() {
        int c = 0;
        for (int i = 0; i < 9; i++) {//行
            int[] count = new int[9];
            for (int j = 0; j < 9; j++) {//列
                for (int num : dataLeft[i][j]) {
                    count[num - 1]++;//统计个数
                }
            }
            for (int k = 0; k < 9; k++) {
                if (count[k] == 1) {//如果区域内的某个数字可能次数为1
                    for (int j = 0; j < 9; j++) {
                        if (dataLeft[i][j].contains(k + 1)) {
                            putNumber(i, j, k + 1);
                            c++;
                        }
                    }
                }
            }
        }
        return c;
    }




    public int computeBox() {
        int c = 0;
        for (int dx = 0; dx < 3; dx++) {
            for (int dy = 0; dy < 3; dy++) {
                //9个小格子
                int[] count = new int[9];
                List<Point>[] points = new ArrayList[9];
                for (int i = 0; i < 9; i++) {
                    points[i] = new ArrayList<Point>();
                }
                for (int i = 0; i < 3; i++) {//行
                    for (int j = 0; j < 3; j++) {//列
                        for (int num : dataLeft[dx * 3 + i][dy * 3 + j]) {
                            count[num - 1]++;//统计个数
                            points[num - 1].add(new Point(dx * 3 + i, dy * 3 + j));
                        }
                    }
                }
                for (int k = 0; k < 9; k++) {
                    if (count[k] == 0) {//如果没有


                    } else if (count[k] == 1) {//如果区域内的某个数字可能次数为1
                        putNumber(points[k].get(0).x, points[k].get(0).y, k + 1);
                    } else {//如果出现次数大于1
                        Point point = points[k].get(0);
                        boolean sameRow = true;
                        boolean sameColumn = true;
                        for (int i = 1; i < points[k].size(); i++) {
                            Point p = points[k].get(i);
                            if (p.x != point.x) {
                                sameRow = false;
                            }
                            if (points[k].get(i).y != point.y) {
                                sameColumn = false;
                            }
                        }
                        if (sameRow) {// 且在一行上,则可以进行区块排除
                            cleanRow(point.x, dy, k + 1);
                        }
                        if (sameColumn) {// 且在一列上,则可以进行区块排除
                            cleanColumn(dx, point.y, k + 1);
                        }
                    }
                }
            }
        }


        return c;
    }


    private void cleanColumn(int dx, int y, Integer n) {
        for (int i = 0; i < 3; i++) {
            if (i != dx) {
                for (int j = 0; j < 3; j++) {
                    dataLeft[i * 3 + j][y].remove(n);
                }
            }
        }
    }


    private void cleanRow(int x, int dy, Integer n) {
        for (int i = 0; i < 3; i++) {
            if (i != dy) {
                for (int j = 0; j < 3; j++) {
                    dataLeft[x][i * 3 + j].remove(n);
                }
            }
        }
    }
    public int computeColumn() {
        int c = 0;
        for (int i = 0; i < 9; i++) {//行
            int[] count = new int[9];
            for (int j = 0; j < 9; j++) {//列
                for (int num : dataLeft[j][i]) {
                    count[num - 1]++;//统计个数
                }
            }
            for (int k = 0; k < 9; k++) {
                if (count[k] == 1) {//如果区域内的某个数字可能次数为1
                    for (int j = 0; j < 9; j++) {
                        if (dataLeft[j][i].contains(k + 1)) {
                            putNumber(j, i, k + 1);
                            c++;
                        }
                    }
                }
            }
        }
        return c;
    }


    public void print() {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                System.out.printf("%d ", data[i][j]);
            }
            System.out.println();
        }
    }

    class Point {
        private final int x;
        private final int y;


        Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
}

再次计算上面算不出来的数独:

Compute completed.
2 4 6 7 5 8 1 9 3 
7 3 5 9 4 1 8 2 6 
1 9 8 2 3 6 7 5 4 
4 5 7 6 1 9 3 8 2 
8 2 9 4 7 3 6 1 5 
6 1 3 8 2 5 4 7 9 
9 8 4 5 6 7 2 3 1 
5 6 1 3 8 2 9 4 7 
3 7 2 1 9 4 5 6 8

Yeah,居然算出了,这个大大超出我的意料。

至此,一般难度的数独及相当难度的数独都可以算出了,后续又增加了区块排除法,智力稍微又强大了一些,但是非常难的需要应用复杂逻辑推理的还是解不开。

小结:

代码有一定的优化,先用效率高的算法进行快速计算,效率高的算法没有进展,再用效率低的算法计算。

相对来说,其算法不一定相当优化,但是实现还是非常简单的,主要是采用逻辑计算方法。已实现的逻辑有:

a.基础摒除法

b.唯余解法

c.区块摒除法

我的区分逻辑与非逻辑的方法很简单:落子为安为逻辑,落子不安为暴力

由于没有进行过深入测试,欢迎验证其正确性。

转载于:https://my.oschina.net/tinyframework/blog/197710

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值