递归的暴力数独解算器需要太长时间

时间:2013-11-23 21:13:47

标签: java recursion sudoku brute-force

我正在制作一个数独游戏程序,并且我'创建'一个递归算法来解决数独游戏。问题是,有时候它会在瞬间完美地工作,有时会卡住并且工作10秒钟,有时候我只需要退出它。 可能导致这种情况的任何想法?

我保留了代码原样,因为我不确定你是如何回答这个问题的(如果你复制并试一试或只是检查逻辑)。如果你愿意,我可以写出片段。

谢谢!

import java.util.Random;

/**
 * @Program Sudoku
 * 
 * @date November 2013
 * @hardware MacBook Pro 17", mid 2010, i7, 8GiB RAM
 * @IDE eclipse SDK 4.3.1
 * @purpose generates a valid 9x9 sudoku grid
 *
 */
public class SudokuSolver //seems to werk !!
{
    //create a validitychecker object(will be used as Sudoku.isValid();)
    validityChecker Sudoku = new validityChecker();

    //Create a 2D array where the full sudoku grid will be stored
    private int[][] grid = new int[9][9];

    //Creates a 2D array for the playable sudoku grid (with elements removed)
    private int[][] playingGrid = new int[9][9];

    private Random Ran = new Random();  



    /**
     * @purpose use this construct if you wish to generate a new sudoku
     * @param difficultyLevel removes amount of elements from sudoku using the equation elementToRemove=40+15*difficultyLevel
     */
    SudokuSolver(int difficultyLevel)
    {

            //generate an empty grid
            generateBaseGrid();
            //populate it with a valid sudoku
            solveSudoku(0,0);
            //store this in a new from which elements shall be removed
            for(int i = 0; i < grid.length; i++)
                playingGrid[i] = grid[i].clone();               
            //calculate the amount of elements to be removed
            int difficultyMultiplier = 15;
            int baseDifficulty = 40;
            int difficulty = baseDifficulty+difficultyLevel*difficultyMultiplier;

            //and remove them
            removeElements(difficulty);
    }

    /**
     * @purpose use this constructor if you just want to use methods and solve
     * @param the sudoku you wish to solve. values have to be within the range 1-9(inclusive), and -1 for unknown 
     * @note to get the solved sudoku use the fullGrid getter.
     */


    SudokuSolver(int[][] pg)
    {
        //lets clone out the arrays - we don't want to just have references ...
        for(int i = 0; i < pg.length; i++)
            grid[i] = pg[i].clone();    
        for(int i = 0; i < grid.length; i++)
            playingGrid[i] = grid[i].clone();   
        int coords[] = findOnes(grid);
        solveSudoku(coords[0],coords[1]);
        System.out.println(coords[0]+" "+coords[1]);
    }

    /**
     * Use this if you only wish to use the internal methods
     */
    SudokuSolver()
    {}


    //this method was implemented later, and I'm too lazy to change methods that use the procedure, but don't call the method. Maybe in next version
    /**
     * @purpose creates a copy of the passed array
     * @param the array you wish to be copied
     * @return returns a clone of the passed 2D array
     */
    public int[][] cloneBoard(int[][] sudokuArray)
    {
        int[][] result = new int[9][9];
        for(int i = 0; i < sudokuArray.length; i++)
            result[i] = sudokuArray[i].clone();
        return result;
    }


    /*
     *@purpose fills the grid with -1s; This is for proper functionality during validation
     */
    private void generateBaseGrid() 
    {
    //iterates through all the values and stores -1s in it
    for(int r=0;r<9;r++)
        for(int c=0;c<9;c++)
            grid[r][c] = -1;
    //System.out.println("Base Grid Created");
    }



    /**
     * @purpose checks if there are -1s in the grid, if so the grid is playable (its not a solution)
     * @return true if its playable
     */
    public boolean isGridPlayable()
    {
        for(int i=0;i<9;i++)
            for(int j=0;j<9;j++)
                if(grid[i][j]==-1)
                    return true;
        return false;
    }

    /**
     * 
     * @return the generated grid with all elements (for one without some  elements use theGrid()) for generator
     * @return the solved grid for solver
     */
    public int[][] fullGrid()
    {

        return grid;

    }

    /**
     * @purpose returns the playing grid
     * @return the playable grid
     */
    public int[][] theGrid()
    {
        return playingGrid;
    }

    /*
     * @purpose removes "amnt" of elements from the playingGrid
     * @return whether the current method was successful
     * @param the amount of elements to be removed
     */
    private boolean removeElements(int amnt)
    {
        if(amnt==0) //yay base case
            return true;
        for(int i=0; i<20;i++)
        {
            int r=Ran.nextInt(9);
            int c=Ran.nextInt(9);

            int element=playingGrid[r][c];

            if(element!=-1)
            {
                playingGrid[r][c]=-1;
                    if(removeElements(amnt-1))
                        {return true;}


            }else{playingGrid[r][c]=element;//removed as per suggestioni--;}
            }
        }
    return false;
    }






    //--------------Debugging--------------------------------
    public void printUserGrid(int[][] printie)
    {
        for(int i=0;i<9;i++)
        {
        for(int j=0;j<9;j++)
            {
            int x = printie[i][j];
            String bexp = Integer.toString(x);
            if(x==-1)
                bexp="[]";
            else bexp+=" ";
            System.out.print(bexp+" ");
            if(j==2||j==5)
                System.out.print("  ");
            }
        System.out.println();
        if(i==2||i==5)
            System.out.println();
        }
    }





//  //----------Main only for debugging-----------------------
    public static void main(String[] args) 
    {
        SudokuSolver Generator = new SudokuSolver(2);
        int[][] generatedGrid = Generator.theGrid();
        int[][] fullGrid = Generator.fullGrid();
        Generator.printUserGrid(fullGrid);

//      Generator.printUserGrid(generatedGrid);
        System.out.println("\n\n");


        SudokuSolver Solver = new SudokuSolver(generatedGrid);
        Solver.printUserGrid(fullGrid);


    }

}

编辑: 我忘记提到的一个关键问题是solveSudoku方法,它重新排列了一些值。这意味着如果我从** 3开始它没有问题返回312(这只是一个例子)。所以我假设那里某处有一些严重的逻辑错误。

2 个答案:

答案 0 :(得分:0)

您尝试解决的问题是Artificial Intelligence问题。按brute-force或更好地称为普通backtracking实际上意味着您可能具有指数时间复杂度。

指数解决方案预计需要很长时间。在order of guesswork与实际解决方案匹配的某些情况下,您的求解器将返回更快的结果。

所以你能做什么:

  1. 阅读名为Constraint Satisfaction的AI技术,并尝试实现它。
  2. 阅读更具体的AI数独解决技巧,也许是一些研究论文,如果有的话,试着去实现。

答案 1 :(得分:-1)

这是我的SudokuSolver版本:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class Sudoku
{
    //This member has been intentionally left public, as the solved sudoku is
    //obtained from here and user will find it much easier to handle this.
    //The Sudoku input has to be given through this instance variable only.
    //Moreover, many a times, Sudoku inputs as an edit-able 2D array is more
    //comfortable than passing a whole 9x9 2D array as a constructor parameter.
    public String SUDOKU[][]=new String[9][9];
    //This variable records the nature of the Sudoku whether solvable or not
    public boolean VALID=true;
    public Sudoku()//this constructor can be used to create SUDOKU objects
    {
        for(int i=0;i<9;i++)
            for(int j=0;j<9;j++)
                SUDOKU[i][j]="";
    }
    private Sudoku(String SDK[][])//This is constructor kept private for program use
    {
        SUDOKU=SDK;
    }
    //This function checks if a certain digit is possible in a particular cell
    private boolean isPossible(int n,int i,int j)
    {
        for(int a=i/3*3;a<i/3*3+3;a++)
            for(int b=j/3*3;b<j/3*3+3;b++)
                if(SUDOKU[a][b].length()==1 && n==Integer.parseInt(SUDOKU[a][b]))
                    return false;
        for(int k=0;k<9;k++)
            if(SUDOKU[i][k].length()==1 && n==Integer.parseInt(SUDOKU[i][k]) ||  SUDOKU[k][j].length()==1 && n==Integer.parseInt(SUDOKU[k][j]))
                return false;
        return true;
    }
    //The following function is compulsory as it is the only function to place appropriate
    //possible digits in the cells of the Sudoku. The easiest Sudokus will get solved here.
    private void fillPossibles()
    {
        boolean newdigit=false;
        for(int i=0;i<9;i++)
            for(int j=0;j<9;j++)
                if(SUDOKU[i][j].length()!=1)
                {
                    SUDOKU[i][j]="";
                    int count=0;
                    for(int k=1;k<10;k++)
                        if(isPossible(k,i,j))
                        {
                            SUDOKU[i][j]+=k;
                            count++;
                        }
                    if(count==1)
                        newdigit=true;
                }
        if(newdigit)
            fillPossibles();
    }
    //This following function is optional, if the Sudoku is known to be genuine. As,
    //in that case it only increases the solving speed!! But if the nature is not known,
    //this function becomes necessary because the nature of the Sudoku is checked here.
    //It returns true if any cell is filled with a digit and false for all other cases
    private boolean deepFillPossibles(int ai,int aj,int bi,int bj,boolean first)
    {
        if(SUDOKU!=null)
            for(char c='1';c<='9';c++)
            {
                int count=0,digit=0,possibility=0,i=0,j=0;
                boolean valid=true;
                for(int a=ai;a<bi;a++)
                    for(int b=aj;b<bj;b++)
                    {
                        if(SUDOKU[a][b].length()==0)
                            valid=false;
                        for(int k=0;k<SUDOKU[a][b].length();k++)
                            if(SUDOKU[a][b].charAt(k)==c)
                            {
                                if(SUDOKU[a][b].length()>1)
                                {
                                    i=a; j=b; count++;
                                }
                                if(SUDOKU[a][b].length()==1)
                                    digit++;
                                possibility++;
                            }
                    }
                //the following code is executed only if solution() is called first time
                if(first && (digit>1 || valid && possibility==0))
                {
                    SUDOKU=null; return false;
                }
                if(count==1)
                {
                    SUDOKU[i][j]=String.valueOf(c);
                    fillPossibles(); return true;
                }
            }
        return false;
    }
    //This function is also optional if Sudoku is genuine. It only combines the solving
    //power of fillPossibles() and deepFillPossibles() to reduce memory consumption
    //in the next stages. In many cases the Sudoku gets solved at this stage itself.
    private void solution(boolean first)
    {
        fillPossibles();
        for(int i=0;i<9;i++)
            if(deepFillPossibles(i,0,i+1,9,first) || deepFillPossibles(0,i,9,i+1,first) ||
           deepFillPossibles(i/3*3,i%3*3,i/3*3+3,i%3*3+3,first))
                i=-1;
    }
    //This function is the most challenging. No Sudoku can ever escape solution after
    //passing this stage. It uses ECHO TREE logic implementing brute force to check all
    //kinds of combinations until solution is obtained. It returns a null for no solution.
    private void treeSolution(Tracker track)
    {
        if(SUDOKU==null)
        {
            track.TRACK_SUDOKU=null;
            return;
        }
        solution(false);
        //For a genuine sudoku the statement could have been replaced by "fillPossibles();"
        //(Only it would make solving slower and increase memory consumption!)
        //But it is risky if there is a doubt regarding the genuineness of the Sudoku
        int a=-1,b=-1;
        for(int i=0;i<9;i++)
            for(int j=0;j<9;j++)
                if(SUDOKU[i][j].length()>1 && a==-1)
                {
                    a=i; b=j;
                }
                else if(SUDOKU[i][j].length()==0)
                    return;
        if(a==-1)//checks if the Sudoku is solved or not setting necessary flags
        {
            track.TRACK_SOLUTION++;
            for(int i=0;i<9;i++)
                for(int j=0;j<9;track.TRACK_SUDOKU[i][j]=SUDOKU[i][j],j++);
            return;
        }
        String temp[][]=new String[9][9];
        for(int k=0;k<SUDOKU[a][b].length();k++)
        {
            for(int i=0;i<9;i++)
                for(int j=0;j<9;temp[i][j]=SUDOKU[i][j],j++);
            temp[a][b]=String.valueOf(SUDOKU[a][b].charAt(k));
            new Sudoku(temp).treeSolution(track);
            //The following condition stops the braching TREE if the Sudoku is solved by
            //echoing back to the root, depending on the flags set on being solved
            if(track.TRACK_SOLUTION==2 || track.TRACK_SUDOKU==null)
                return;
        }
        return;
    }
    //This is the last function which has public access and can be called by the user.
    //It sets the Sudoku as null if non-genuine and VALIDity as false if no unique solution
    public void solve()
    {
        try
        {
            for(int i=0;i<9;SUDOKU[i][8]=SUDOKU[i][8],SUDOKU[8][i]=SUDOKU[8][i],i++);
        }
        catch(Exception e)
        {
            SUDOKU=null; VALID=false; return;
        }
        Tracker track=new Tracker();
        solution(true);
        treeSolution(track);
        SUDOKU=track.TRACK_SOLUTION==0?null:track.TRACK_SUDOKU;
        if(track.TRACK_SOLUTION!=1)
            VALID=false;
    }
}

//the following class is purposely designed to easily track the changes during solution,
//including the nature of the Sudoku and the solution of the Sudoku(if possible)

class Tracker
{
    protected int TRACK_SOLUTION=0;
    protected String TRACK_SUDOKU[][]=new String[9][9];
}

public class SudokuSolver extends JApplet implements KeyListener, MouseListener
{
    private String SUDOKU[][]=new String[9][9];
    private Sudoku SDK=new Sudoku();
    private Image BOX[][]=new Image[9][9],SELECT_IMG;//BOX is an array of square images
    private int SELECT_ROW=4,SELECT_COLUMN=4;//stores the position of the selection box
    //this function is used to initialize the coloured square images and fonts
    public void init()
    {
        resize(190,190);
        setFont(new Font("Dialog",Font.BOLD,12));
        addMouseListener(this);
        addKeyListener(this);
        Graphics g;
        for(int i=0;i<9;i++)
            for(int j=0;j<9;SUDOKU[i][j]="",j++)
            {
                BOX[i][j]=createImage(22,22);
                g=BOX[i][j].getGraphics();
                if((i/3+j/3)%2==0)
                    g.setColor(Color.yellow);
                else
                    g.setColor(Color.green);
                g.fillRect(0,0,21,21);
                g.setColor(Color.black);
                g.drawRect(0,0,21,21);
            }
        //the following statements colour the selection box red
        SELECT_IMG=createImage(22,22);
        g=SELECT_IMG.getGraphics();
        g.setColor(Color.red);
        g.fillRect(0,0,21,21);
        g.setColor(Color.black);
        g.drawRect(0,0,21,21);
    }
    public void mouseExited(MouseEvent e){}
    public void mouseEntered(MouseEvent e){}
    public void mouseReleased(MouseEvent e){}
    public void mouseClicked(MouseEvent e){}
    //the following function handles the mouse click operations
    public void mousePressed(MouseEvent e)
    {
        if(SDK.SUDOKU==null)
        {
            SDK.SUDOKU=new String[9][9];
            for(int i=0;i<9;i++)
                for(int j=0;j<9;SUDOKU[i][j]="",SDK.SUDOKU[i][j]="",j++);
            SDK.VALID=true;
        }
        if(e.getY()<190 && e.getX()<190 && e.getY()%21!=0 && e.getX()%21!=0)
        {
            SELECT_ROW=e.getY()/21;
            SELECT_COLUMN=e.getX()/21;
        }
        repaint();
    }
    public void keyReleased(KeyEvent e){}
    //this function manages the operations associated with the various keys
    public void keyPressed(KeyEvent e)
    {
        int code=e.getKeyCode();
        if(code==KeyEvent.VK_DELETE || code==KeyEvent.VK_BACK_SPACE || code==KeyEvent.VK_SPACE)
        {
            SUDOKU[SELECT_ROW][SELECT_COLUMN]=""; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="";
        }
        switch(code)
        {
            case KeyEvent.VK_Y : if(SDK.SUDOKU!=null) SDK.VALID=true; break;
            case KeyEvent.VK_UP : SELECT_ROW=(SELECT_ROW+8)%9; break;
            case KeyEvent.VK_DOWN : SELECT_ROW=(SELECT_ROW+1)%9; break;
            case KeyEvent.VK_LEFT : SELECT_COLUMN=(SELECT_COLUMN+8)%9; break;
            case KeyEvent.VK_RIGHT : SELECT_COLUMN=(SELECT_COLUMN+1)%9; break;
            case KeyEvent.VK_ENTER : SDK.solve(); break;
            case KeyEvent.VK_N : if(SDK.SUDOKU!=null){ SDK.VALID=true; code=KeyEvent.VK_ESCAPE;}
            case KeyEvent.VK_ESCAPE : if(SDK.SUDOKU==null)
                                      {
                                          SDK.SUDOKU=new String[9][9];
                                          for(int i=0;i<9;i++)
                                              for(int j=0;j<9;SUDOKU[i][j]="",SDK.SUDOKU[i][j]="",j++);
                                          SDK.VALID=true;
                                      }
                                      else
                                          for(int i=0;i<9;i++)
                                              for(int j=0;j<9;SDK.SUDOKU[i][j]=SUDOKU[i][j],j++);
        }
        repaint();
    }
    //this function is for entering the numbers in the sudoku grid
    public void keyTyped(KeyEvent e)
    {
        char code=e.getKeyChar();
        if(Character.isDigit(code))
            switch(code)
            {
                case '1': SUDOKU[SELECT_ROW][SELECT_COLUMN]="1"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="1"; break;
                case '2': SUDOKU[SELECT_ROW][SELECT_COLUMN]="2"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="2"; break;
                case '3': SUDOKU[SELECT_ROW][SELECT_COLUMN]="3"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="3"; break;
                case '4': SUDOKU[SELECT_ROW][SELECT_COLUMN]="4"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="4"; break;
                case '5': SUDOKU[SELECT_ROW][SELECT_COLUMN]="5"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="5"; break;
                case '6': SUDOKU[SELECT_ROW][SELECT_COLUMN]="6"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="6"; break;
                case '7': SUDOKU[SELECT_ROW][SELECT_COLUMN]="7"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="7"; break;
                case '8': SUDOKU[SELECT_ROW][SELECT_COLUMN]="8"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="8"; break;
                case '9': SUDOKU[SELECT_ROW][SELECT_COLUMN]="9"; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="9"; break;
                default : SUDOKU[SELECT_ROW][SELECT_COLUMN]=""; SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN]="";
            }
    }
    //the paint() function is designed to print the the sudoku grid and other messages
    public void paint(Graphics g)
    {
        if(!SDK.VALID)
        {
            g.setColor(Color.white);
            g.fillRect(1,1,188,188);
            g.setColor(Color.black);
            g.drawRect(0,0,189,189);
            if(SDK.SUDOKU==null)
            {
                g.drawString("INVALID SUDOKU!!",45,80);
                g.drawString("[PRESS ESCAPE TO RE-ENTER]",10,105);
            }
            else
            {
                g.drawString("INCOMPLETE SUDOKU!!",30,60);
                g.drawString("Would you like to see a",30,90);
                g.drawString("possible solution?",45,105);
                g.drawString("Y / N",80,120);
            }
        }
        else
        {
            for(int i=0;i<9;i++)
                for(int j=0;j<9;j++)
                {
                    g.drawImage(BOX[i][j],j*21,i*21,null);
                    g.drawString(SDK.SUDOKU[i][j],8+j*21,15+i*21);
                }
            g.drawImage(SELECT_IMG,SELECT_COLUMN*21,SELECT_ROW*21,null);
            g.drawString(SDK.SUDOKU[SELECT_ROW][SELECT_COLUMN],8+SELECT_COLUMN*21,15+SELECT_ROW*21);
        }
    }
}

我试图尽可能使用最小蛮力