数独算法

package cn.tiger.funny;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 计算数独的所有解
 * @author jyuan
 *
 */
public class Sudoku {
    private final Integer[] numbers = {1,2,3,4,5,6,7,8,9};
    private List<Integer[][]> answer = new ArrayList<Integer[][]>();

    /**
     * 获取数独的所有解
     * @param squared 数独
     * @return 数独的所有解
     */
    public List<Integer[][]> calculate(Integer[][] squared) {
        long start = System.currentTimeMillis();
        FillInTheDeterminedValue(squared);
        startCalculate(squared);
        long end = System.currentTimeMillis();
        System.out.println("take times:" + (end-start) + "ms.");
        
        print();
        return answer;
    }

    /**
     * 数独中根据现有的数据,将能确定的值先填上
     * @param squared 数独
     */
    private void FillInTheDeterminedValue(Integer[][] squared) {
        boolean falg = true;
        do {
            if(!determinedViaValue(squared) 
                    && !determinedViaExclusionMethod(squared)) {
                falg = false;
            }
        } while (falg);
    }
    
    /**
     * 如果当前小格相关联的行,列,格已经包含其他八个数字,则当前小格数字确认
     * @param squared 数独
     * @return 如果数独发生改变,则return true
     */
    private boolean determinedViaValue(Integer[][] squared) {
        boolean changed = false;
        for (int i = 0; i < squared.length; i++) {
            for (int j = 0; j < squared[0].length; j++) {
                if(squared[i][j] != 0) {
                    continue;
                }
                Set<Integer> exclusion = getUsedInIndex_x(squared, i);
                exclusion.addAll(getUsedInIndex_y(squared, j));
                exclusion.addAll(getUsedInSquared(squared, i, j));
                if(exclusion.size()==8) {
                    for (Integer number : numbers) {
                        if(!exclusion.contains(number)) {
                            changed = true;
                            squared[i][j] = number;
                        }
                    }
                }
            }
        }
        return changed;
    }
    
    /**
     * 通过排除法确认唯一值
     * eg.
     *     如果同一行别的格出现过数字7,则这一列都不能为数字7
     * @param squared 数独
     * @return 如果数独发生改变,则return true
     */
    private boolean determinedViaExclusionMethod(Integer[][] squared) {
        boolean changed = false;
        for(int num : numbers) {
            boolean[][] table = new boolean[9][9];
            setTable(squared, table, num);
            
            changed = excludeRow(squared, table, num) 
                    || excludeColumn(squared, table, num)
                    || excludeCell(squared, table, num);
        }
        return changed;
    }

    /**
     * 如果对应数独位置数字为0,则table对应位置不变。
     * 如果对应数独位置数字不为零且不等于num,则将table该位置设为true。
     * 如果对应数独位置数字不为零且等于num,则将该位置对应的行、列、以及所在九格全部设为true。
     * 
     * target:
     * 如果table每行每列每小九格仅有一个false,则这一格false必为num。
     * 
     * @param squared 数独
     * @param table 排除法工具表,对应数独
     * @param num 当前检测的数字
     */
    private void setTable(Integer[][] squared, boolean[][] table, int num) {
        for (int i = 0; i < squared.length; i++) {
            for (int j = 0; j < squared[0].length; j++) {
                if(squared[i][j] == 0) {
                    continue;
                } else if (squared[i][j] != 0 && squared[i][j] != num) {
                    table[i][j] = true;
                } else if (squared[i][j] != 0 && squared[i][j] == num) {
                    for(int index = 0 ; index < 9 ; index++) {
                        table[index][j] = true;
                        table[i][index] = true;
                    }
                    int smallSquared = getSmallSquared(i, j);
                    switch (smallSquared) {
                    case 1: {
                        setSmallSquaredInTable(table, 1, 1);
                        break;
                    }
                    case 2: {
                        setSmallSquaredInTable(table, 1, 4);
                        break;
                    }
                    case 3: {
                        setSmallSquaredInTable(table, 1, 7);
                        break;
                    }
                    case 4: {
                        setSmallSquaredInTable(table, 4, 1);
                        break;
                    }
                    case 5: {
                        setSmallSquaredInTable(table, 4, 4);
                        break;
                    }
                    case 6: {
                        setSmallSquaredInTable(table, 4, 7);
                        break;
                    }
                    case 7: {
                        setSmallSquaredInTable(table, 7, 1);
                        break;
                    }
                    case 8: {
                        setSmallSquaredInTable(table, 7, 4);
                        break;
                    }
                    case 9: {
                        setSmallSquaredInTable(table, 7, 7);
                        break;
                    }
                    default:
                        throw new IllegalArgumentException("Unexpected value: " + smallSquared);
                    }
                }
            }
        }
    }
    
    /**
     * 排除法检查每行是否有确定值,如果有则改动并返回true
     * @param squared 数独
     * @param table 排除法工具表,对应数独
     * @param num 当前检测的数字
     * @return 是否改动数独
     */
    private boolean excludeRow(Integer[][] squared, boolean[][] table, int num) {
        boolean changed = false;
        for(int index_i = 1 ; index_i <=7; index_i+=3) {
            for(int index_j = 1 ; index_j <=7; index_j+=3) {
                int count = 0;
                for (int i = index_i-1; i <= index_i+1; i++) {
                    for (int j = index_j-1; j <= index_j+1; j++) {
                        if(table[i][j] == true) {
                            count++;
                        }
                    }
                }
                if(count==8) {
                    for (int i = index_i-1; i <= index_i+1; i++) {
                        for (int j = index_j-1; j <= index_j+1; j++) {
                            if(table[i][j] == false) {
                                changed = true;
                                squared[i][j] = num;
                            }
                        }
                    }
                }
            }
        }
        return changed;
    }
    
    /**
     * 排除法检查每列是否有确定值,如果有则改动并返回true
     * @param squared 数独
     * @param table 排除法工具表,对应数独
     * @param num 当前检测的数字
     * @return 是否改动数独
     */
    private boolean excludeColumn(Integer[][] squared, boolean[][] table, int num) {
        boolean changed = false;
        for (int i = 0; i < squared.length; i++) {
            int count = 0;
            for (int j = 0; j < squared[0].length; j++) {
                if(table[i][j] == true) {
                    count++;
                }
            }
            if(count==8) {
                for (int j = 0; j < squared[0].length; j++) {
                    if(table[i][j] == false) {
                        changed = true;
                        squared[i][j] = num;
                    }
                }
            }
        }
        return changed;
    }
    
    /**
     * 排除法检查对应小九格是否有确定值,如果有则改动并返回true
     * @param squared 数独
     * @param table 排除法工具表,对应数独
     * @param num 当前检测的数字
     * @return 是否改动数独
     */
    private boolean excludeCell(Integer[][] squared, boolean[][] table, int num) {
        boolean changed = false;
        for (int i = 0; i < squared.length; i++) {
            int count = 0;
            for (int j = 0; j < squared[0].length; j++) {
                if(table[j][i] == true) {
                    count++;
                }
            }

            for (int j = 0; j < squared[0].length; j++) {
                if(count==8) {
                    if(table[j][i] == false) {
                        changed = true;
                        squared[j][i] = num;
                    }
                }
            }
        }
        return changed;
    }
    
    /**
     * 将table对应小九格中的值设为true
     * @param table 排除法工具表,对应数独
     * @param index_i 对应小九格中心位置的x坐标
     * @param index_j 对应小九格中心位置的y坐标
     */
    private void setSmallSquaredInTable(boolean[][] table, int index_i, int index_j) {
        for (int i = index_i-1; i <= index_i+1; i++) {
            for (int j = index_j-1; j <= index_j+1; j++) {
                if(table[i][j] == false) {
                    table[i][j] = true;
                }
            }
        }
    }

    /**
     * 获得两个数组交集
     * @param arr1
     * @param arr2
     * @return 两个数组交集
     */
    public Integer[] getB(Integer[] arr1, Integer[] arr2) {
        Set<Integer> set1 = new HashSet<Integer>(Arrays.asList(arr1));
        Set<Integer> set2 = new HashSet<Integer>(Arrays.asList(arr2));
        set1.addAll(set2);
        Integer[] result = {};
        return set1.toArray(result);
    }
    
    /**
     * 在FillInTheDeterminedValue无法确定值之后,通过尝试所有可能性获得解
     * @param squared 数独
     */
    private void startCalculate(Integer[][] squared) {
        for (int i = 0; i < squared.length; i++) {
            for (int j = 0; j < squared[0].length; j++) {
                if(i == squared.length-1 && j == squared.length-1 && squared[i][j] != 0) {
                    answer.add(squared);
                }

                if (squared[i][j] != 0) {
                    continue;
                } else {
                    for (int number : numbers) {
                        if(getUsedInIndex_x(squared, i).contains(number) 
                                || getUsedInIndex_y(squared, j).contains(number) 
                                || getUsedInSquared(squared, i, j).contains(number)) {
                            if(number==9) {
                                return;
                            }
                            continue;
                        } else {
                            Integer[][] copy = new Integer[9][9];
                            for (int iLength = 0 ; iLength < 9; iLength++) {
                                for (int jLength = 0 ; jLength < 9; jLength++) {
                                    copy[iLength][jLength] = squared[iLength][jLength];
                                }
                            }
                            squared[i][j] = number;
                            FillInTheDeterminedValue(squared);
                            startCalculate(squared);
                            squared = copy;
                            if(number==9) {
                                return;
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 获得当前小九格中所有存在的数字
     * @param squared 数独
     * @param index_x 当前位置的x坐标
     * @param index_y 当前位置的y坐标
     * @return 当前小九格中所有存在的数字Set集合
     */
    private Set<Integer> getUsedInSquared(Integer[][] squared, int index_x, int index_y) {
        int smallSquared = getSmallSquared(index_x, index_y);
        switch (smallSquared) {
        case 1: {
            return getUsedInSquaredVisCore(squared, 1, 1);
        }
        case 2: {
            return getUsedInSquaredVisCore(squared, 1, 4);
        }
        case 3: {
            return getUsedInSquaredVisCore(squared, 1, 7);
        }
        case 4: {
            return getUsedInSquaredVisCore(squared, 4, 1);
        }
        case 5: {
            return getUsedInSquaredVisCore(squared, 4, 4);
        }
        case 6: {
            return getUsedInSquaredVisCore(squared, 4, 7);
        }
        case 7: {
            return getUsedInSquaredVisCore(squared, 7, 1);
        }
        case 8: {
            return getUsedInSquaredVisCore(squared, 7, 4);
        }
        case 9: {
            return getUsedInSquaredVisCore(squared, 7, 7);
        }
        default:
            throw new IllegalArgumentException("Unexpected value: " + smallSquared);
        }
    }

    /**
     * 获得当前行的所有数字
     * @param squared 数独
     * @param index_x 当前位置的x坐标
     * @return 当前行的所有数字Set集合
     */
    private Set<Integer> getUsedInIndex_x(Integer[][] squared, int index_x) {
        Set<Integer> usedInIndex_x = new HashSet<Integer>();
        for (int i = 0; i < squared[index_x].length; i++) {
            if (squared[index_x][i] != 0) {
                usedInIndex_x.add(squared[index_x][i]);
            }
        }
        return usedInIndex_x;
    }

    /** 当前列的所有数字
     * 获得当前列的所有数字
     * @param squared 数独
     * @param index_y 当前位置的y坐标
     * @return 当前列的所有数字Set集合
     */
    private Set<Integer> getUsedInIndex_y(Integer[][] squared, int index_y) {
        Set<Integer> usedInIndex_y = new HashSet<Integer>();
        for (int i = 0; i < squared.length; i++) {
            if (squared[i][index_y] != 0) {
                usedInIndex_y.add(squared[i][index_y]);
            }
        }
        return usedInIndex_y;
    }

    /**
     * 获得当前位置的小九格,按1-9排序
     * @param index_x 当前位置的x坐标
     * @param index_y 当前位置的y坐标
     * @return 当前小九格对应的整数
     */
    private int getSmallSquared(int index_x, int index_y) {
        Integer[] xSquared = getSmallSquaredViaIndex_X(index_x);
        Integer[] ySquared = getSmallSquaredViaIndex_Y(index_y);
        for (int i : xSquared) {
            for (int j : ySquared) {
                if(i == j) {
                    return i;
                }
            }
        }
        throw new IllegalArgumentException("Unexpected value: " + index_x + " and " + index_y);
    }

    /**
     * 获得当前位置对应的一列小九格,小九格按1-9排序
     * @param index 当前位置的y坐标
     * @return 返回三个整数数组
     */
    private Integer[] getSmallSquaredViaIndex_Y(int index) {
        switch (index) {
        case 0:
        case 1:
        case 2: {
            return new Integer[] {1, 4, 7};
        }
        case 3:
        case 4:
        case 5: {
            return new Integer[] {2, 5, 8};
        }
        case 6:
        case 7:
        case 8: {
            return new Integer[] {3, 6, 9};
        }

        default:
            throw new IllegalArgumentException("Unexpected value: " + index);
        }
    }

    /**
     * 获得当前位置对应的一行小九格,小九格按1-9排序
     * @param index 当前位置的x坐标
     * @return 返回三个整数数组
     */
    private Integer[] getSmallSquaredViaIndex_X(int index) {
        switch (index) {
        case 0:
        case 1:
        case 2: {
            return new Integer[] {1, 2, 3};
        }
        case 3:
        case 4:
        case 5: {
            return new Integer[] {4, 5, 6};
        }
        case 6:
        case 7:
        case 8: {
            return new Integer[] {7, 8, 9};
        }

        default:
            throw new IllegalArgumentException("Unexpected value: " + index);
        }
    }

    /**
     * 获得当前小九格中所有存在的数字
     * @param squared 数独
     * @param index_x 小九格中心位置x坐标
     * @param index_y 小九格中心位置y坐标
     * @return
     */
    private Set<Integer> getUsedInSquaredVisCore(Integer[][] squared, int index_x, int index_y) {
        Set<Integer> usedInSquared = new HashSet<Integer>();    
        for (int i = index_x-1; i <= index_x+1; i++) {
            for (int j = index_y-1; j <= index_y+1; j++) {
                if(squared[i][j] != 0) {
                    usedInSquared.add(squared[i][j]);
                }
            }
        }
        return usedInSquared;
    }
    
    /**
     * 答应当前数独
     * @param squared 数独
     */
    private void print() {
        System.out.println("*********************************************");
        for (Integer[][] squared : answer) {
            System.out.println("------answer------");
            for (int x = 0 ; x < squared.length; x ++) {
                for (int y = 0 ; y < squared[0].length; y++) {
                    System.out.print(squared[x][y] + " ");;
                }
                System.out.println();
            }
        }
    }
    
    
    public static void main(String[] args) {
//        Integer[][] in = new Integer[][] {
//            {5, 4, 0, 1, 6, 0, 7, 0, 0},
//            {6, 0, 0, 0, 0, 0, 3, 4, 5},
//            {0, 9, 0, 0, 4, 3, 1, 0, 0},
//            {7, 0, 0, 0, 0, 0, 0, 9, 0},
//            {0, 0, 1, 0, 7, 0, 5, 0, 0},
//            {0, 2, 0, 0, 0, 0, 0, 0, 6},
//            {0, 0, 2, 3, 9, 0, 0, 5, 0},
//            {4, 7, 9, 0, 0, 0, 0, 0, 2},
//            {0, 0, 6, 0, 8, 2, 0, 1, 7}
//        };
        
//        Integer[][] in = new Integer[][] {
//            {6, 0, 0, 5, 0, 0, 0, 0, 0},
//            {0, 3, 4, 0, 0, 1, 0, 8, 0},
//            {0, 0, 0, 0, 0, 4, 0, 6, 3},
//            {0, 1, 8, 0, 9, 0, 0, 0, 0},
//            {0, 7, 0, 0, 0, 0, 0, 0, 4},
//            {0, 0, 0, 7, 4, 0, 0, 5, 0},
//            {0, 0, 1, 0, 0, 3, 0, 0, 6},
//            {0, 0, 0, 1, 5, 0, 3, 0, 0},
//            {3, 0, 0, 8, 0, 0, 5, 9, 0}
//        };
        
//        Integer[][] in = new Integer[][] {
//            {0, 2, 0, 4, 0, 0, 0, 0, 6},
//            {0, 0, 0, 0, 1, 6, 0, 0, 9},
//            {1, 0, 6, 2, 5, 0, 0, 0, 3},
//            {0, 0, 0, 0, 0, 1, 9, 6, 0},
//            {9, 0, 0, 0, 2, 0, 0, 0, 0},
//            {0, 7, 0, 3, 0, 0, 0, 0, 8},
//            {0, 3, 7, 5, 0, 0, 0, 0, 0},
//            {0, 0, 0, 0, 6, 0, 8, 0, 0},
//            {0, 0, 0, 0, 0, 4, 0, 5, 7}
//        };
        
//        Integer[][] in = new Integer[][] {
//            {0, 2, 0, 4, 0, 1, 0, 0, 0},
//            {0, 7, 0, 0, 0, 9, 1, 4, 0},
//            {0, 1, 8, 0, 0, 0, 0, 0, 0},
//            {0, 0, 0, 6, 0, 0, 0, 0, 7},
//            {0, 0, 9, 0, 5, 0, 0, 0, 0},
//            {0, 0, 5, 0, 0, 0, 0, 2, 6},
//            {1, 0, 0, 0, 0, 0, 5, 7, 0},
//            {0, 0, 0, 0, 4, 0, 0, 6, 0},
//            {0, 0, 0, 5, 0, 3, 8, 0, 0}
//        };
        
//        Integer[][] in = new Integer[][] {
//            {0, 1, 0, 0, 0, 0, 3, 0, 0},
//            {0, 0, 9, 0, 0, 6, 0, 0, 0},
//            {5, 0, 0, 0, 4, 0, 8, 0, 0},
//            {9, 3, 5, 0, 0, 0, 0, 0, 2},
//            {0, 0, 0, 0, 0, 5, 0, 4, 0},
//            {0, 0, 0, 2, 6, 0, 7, 3, 0},
//            {7, 0, 0, 0, 0, 0, 9, 0, 6},
//            {0, 0, 0, 0, 0, 2, 0, 0, 0},
//            {0, 0, 3, 0, 1, 0, 2, 0, 0}
//        };
        
        Integer[][] in = new Integer[][] {
            {8, 0, 0, 0, 0, 0, 0, 0, 0},
            {0, 0, 3, 6, 0, 0, 0, 0, 0},
            {0, 7, 0, 0, 9, 0, 2, 0, 0},
            {0, 5, 0, 0, 0, 7, 0, 0, 0},
            {0, 0, 0, 0, 4, 5, 7, 0, 0},
            {0, 0, 0, 1, 0, 0, 0, 3, 0},
            {0, 0, 1, 0, 0, 0, 0, 6, 8},
            {0, 0, 8, 5, 0, 0, 0, 1, 0},
            {0, 9, 0, 0, 0, 0, 4, 0, 0}
        };
        

        Sudoku sudoku = new Sudoku();
        sudoku.calculate(in);
    }
}

 

posted @ 2020-11-18 14:21  预言2018  阅读(136)  评论(0)    收藏  举报