利用程序随机构造N个已解答的数独棋盘

高级软件工程第二次作业:利用程序随机构造N个已解答的数独棋盘,代码如下:

  1 package SudokuGame;
  2 /**
  3  * 解决这个问题使用的是回溯+剪枝的算法
  4  * 基本思想:不断地将每个格子可填入的数字减少,如果当前格子没有数字可填入,就回溯到上一格
  5  * 实现算法:首先初始化数独,将数独的第一行以随机数字填入,从第二行开始的每个格子都从1开始找可以填入的最小的数字,
  6     在没有数字可填时,就开始回溯。 此算法在回溯的同时将不可能的结果排除,不会陷入死循环     
  7  */
  8 
  9 import java.io.File;
 10 import java.io.FileWriter;
 11 import java.io.IOException;
 12 import java.util.Scanner;
 13 
 14 public class sudokuV2 {
 15     
 16     public static void main(String[] args) {
 17         sudokuV2 sudoku = new sudokuV2();
 18         int[][] square = new int[9][9];        //存放数独的二维数组
 19         
 20         String pathName = "sudotiku.txt";
 21         File file = new File(pathName);
 22         if(file.exists()){
 23             file.delete();    //此句话在每次执行程序时先删除已经存在的文件sudotiku.txt
 24         }
 25         //开始生成数独
 26         sudoku.go(square);
 27         
 28     }
 29     
 30     /**
 31      * 将square中生成的数独写到文件sudotiku.txt中
 32      * @param square
 33      */
 34     public void writerToFile(int [][] square) {
 35         String pathName = "sudotiku.txt";
 36         File file = new File(pathName);
 37 
 38         try {
 39                 if(!file.exists()) {//若文件不存在就创建一个新文件
 40                     file.createNewFile();
 41                 }
 42                 FileWriter fWriter = new FileWriter(file, true);
 43                 
 44                 //此处参数如果直接是整形,则会被当成ASCII码,最后输出时转换成ASCII码对应的字符
 45                 //所以此处将int型转换成String输出
 46                 for(int i = 0; i < 9; i++) {
 47                     for(int j = 0; j < 9; j++) {
 48                         fWriter.write(String.valueOf(square[i][j]) + "	");
 49                     }    
 50                     fWriter.write("
");
 51                 }
 52                 fWriter.write("
");
 53                 fWriter.close();
 54             } catch (IOException e) {
 55                 e.printStackTrace();
 56         }
 57     }
 58     
 59     /**
 60      * 接收用户输入的数字,若不符合要求则重新输入
 61      * @return 返回用户输入的数字
 62      */
 63     public int getNumber() {
 64         int n;    //用来接收用户想要生成的数独解的个数
 65         do{
 66             System.out.print("请输入要生成的数独的个数:(1~1000000之间)");
 67             Scanner input = new Scanner(System.in);
 68             n = input.nextInt();
 69             if(n <= 0 || n >1000000) {
 70                 System.out.println("输入错误,请重新输入!");
 71             }
 72         }while(n <= 0 || n >1000000);
 73         return n;
 74     }
 75     
 76     /**
 77      * 初始化九宫格,全部元素置0
 78      * 并将数独的第一行填入随机数字
 79      */
 80     public void init(int[][] square) {
 81         /** 将大九宫格数字全部置0 */
 82         for (int i = 0; i < 9; i++) {
 83             for (int j = 0; j < 9; j++) {
 84                 square[i][j] = 0;
 85             }
 86         }    
 87         /** 将第一行填入随机数字 */
 88         for(int i = 0,j =0; j < 9; j++) {
 89             square[i][j] = findValueInFirst(square, i, j);
 90         }
 91     }
 92     
 93     /**
 94      * 找到数独第一行可以填入的数字,并返回该数字
 95      * @param square
 96      * @param row
 97      * @param col
 98      * @return    value    返回找到的合适的数字
 99      */
100     public int findValueInFirst(int[][] square, int row, int col) {
101         int value = 0;
102         
103         do{
104             value = (int)(Math.random() * 100) % 9 + 1 ;
105             if(rowCheck(square, row, col, value) == true) {
106             //如果value的值与第一行已有的值不重复,则返回value,将其存入square数组中
107                 return value;
108             }
109         }while(true);
110     }
111     
112     /**
113      * 生成数独,从第二行开始,每个格子的可填入数字都从1开始试探,不符合则增加1
114      * @param square
115      */
116     public void go(int[][] square) {
117         int n;
118         n = getNumber();    //用来接收用户想要生成的数独的个数 
119         for(;n >= 1;n--) {    //控制生成数独的个数
120             init(square);
121             /* 开始生成数独**/
122             int number ;    
123             for (int i = 1; i < 9; i++) {
124                 for (int j = 0; j < 9;) {
125                     number = 0;
126                     number = findValue(square, i, j);
127                     if(number > 0) {
128                         square[i][j] = number;
129                         j++;
130                     }else {
131                         //没有找到可填入的值,开始回溯
132                         if(j == 0) {//如果当前格子在第一列,则回溯到上一行的最后一格,此时需要将当前格子的数字置为0
133                             square[i][j] = 0;
134                             i--;
135                             j = 8;
136                         }else {    //如果当前格子不在第一列,则回溯到同行的前一格
137                             square[i][j] = 0;
138                             j--;
139                         }
140                     }
141                 }
142             }
143             writerToFile(square);
144             show(square);
145             System.out.println();
146         }
147     }
148     
149     /**
150      * 寻找可以填入格子的数字
151      * @param square
152      * @param row
153      * @param col
154      * @return
155      */
156     public int findValue(int[][] square, int row, int col) {
157         
158         int temp = square[row][col];    
159         if(temp == 0) {
160             //如果square当前的格子值为0,则说明在一个新的格子里,从1开始寻找可以填入的值
161             temp = 1;
162         }else {
163             //如果square当前的格子的不为0,则说明进入了回溯程序,将temp+1
164             temp++;
165         }
166         
167         boolean isChange = false;    //记录temp是否有改变,用来判断循环是否继续
168         
169         do {
170             /* tempValue用来记录进入循环时temp的值,以方便查看temp的值是否有变化 **/
171             int tempValue = temp;        
172             if(rowCheck(square, row, col, temp) == false) {    
173                 //行内检查不唯一则temp在原来基础上+1
174                 temp++;
175                 isChange = true;
176             }
177             if(colCheck(square, row, col, temp) == false) {
178                 //列内检查不唯一则temp在原来基础上+1
179                 temp++;
180                 isChange = true;
181             }
182             if(smallCheck(square, row, col, temp) == false) {
183                 //小九宫格内检查不唯一则temp在上一步基础上+1
184                 temp++;
185                 isChange = true;
186             }
187             if(temp >= 10) {
188                 //此时1~9均不能填入格子,需要进入回溯
189                 return -1;
190             }
191             if(temp == tempValue) {
192                 //全部检查完成后,若没有改变,则表示找到可以填入的值,返回temp
193                 return temp;
194             }    
195         }while(isChange == true);    //isChange变成true,说明temp改变了,此时没有找到可以填入格子的值
196         return -1;
197     }
198 
199     /**
200      * 输出数独
201      * @param square
202      */
203     public void show(int[][] square) {
204         for (int i = 0; i < 9; i++) {
205             for (int j = 0; j < 9; j++) {
206                 System.out.print(square[i][j] + "	");
207             }
208             System.out.println();
209         }
210     }
211     
212     
213     /**
214      * 行检查:判断将要填入的数字是否与该行的元素有重复
215      * @param square    二维数组组成的大九宫格
216      * @param row        当前格子的行坐标
217      * @param col        当前格子的列坐标
218      * @param value        当前准备填入格子的数字
219      * @return            若value与该行已有的元素重复则返回false,不重复返回true
220      */
221     public boolean rowCheck(int[][] square, int row, int col, int value) {
222         for(int j = 0; j < col ;j++ ) {//判断新填入的元素是否与该行的元素有重复
223             if(value == square[row][j]) {
224                 return false;
225             }
226         }
227         return true;
228     }
229 
230     /**
231      * 列检查:判断将要填入的数字是否与该列的元素有重复
232      * @param square    二维数组组成的大九宫格
233      * @param row        当前格子的行坐标
234      * @param col        当前格子的列坐标
235      * @param value        当前准备填入格子的数字
236      * @return            若value与该列已有的元素重复则返回false,不重复返回true
237      */
238     public boolean colCheck(int[][] square, int row,int col,int value) {
239         for(int i = 0; i < row ;i++ ) {//判断新填入的元素是否与该列的元素有重复
240             if( value == square[i][col] ) {
241                 return false;
242             }
243         }
244         return true;
245     }
246     
247     /**
248      * 小九宫格检查:判断新填入的元素是否与其所在的小九宫格的元素有重复
249      * 想法:根据该格子所在的行和列判断所属的小九宫格的位置,然后分成九个小块分别判断
250         分别有九组  (0,0),(0,1),(0,2)
251                 (1,0),(1,1),(1,2)
252                    (2,0),(2,1),(2,2)
253      * @param square    二维数组组成的大九宫格
254      * @param row        当前格子的行坐标
255      * @param col        当前格子的列坐标
256      * @param value        当前准备填入格子的数字
257      * @return            若value与该小九宫格已有的元素重复则返回false,不重复返回true
258      */
259     public boolean smallCheck(int[][] square, int row,int col,int value) {
260         
261         int x = row / 3;        //(x,y)的组合表示小九宫格的位置
262         int y = col / 3;        
263         
264         for(int i = 3 * x; i < 3 * x + 3 ;i++ ) {
265             for (int j = 3 * y; j < 3 * y + 3; j++) {
266                 if(i == row && j == col) {
267                     continue;
268                 }
269                 if(value == square[i][j]) {
270                     return false;
271                 }
272             }    
273         }
274         return true;
275     }
276 }

以上为程序运行的全部代码。生成数独后将其写入文件“sudotiku.txt”中,也会在屏幕上显示。以下是运行结果:

请输入要生成的数独的个数:(1~1000000之间)3
9    3    5    7    1    8    2    4    6    
1    2    4    3    5    6    7    8    9    
6    7    8    2    4    9    1    3    5    
2    1    3    4    6    5    8    9    7    
4    5    6    8    9    7    3    1    2    
7    8    9    1    2    3    5    6    4    
3    4    1    6    7    2    9    5    8    
5    6    2    9    8    1    4    7    3    
8    9    7    5    3    4    6    2    1    

4    5    1    9    7    8    2    3    6    
2    3    6    1    4    5    7    8    9    
7    8    9    2    3    6    1    4    5    
1    2    3    4    5    7    6    9    8    
5    4    7    6    8    9    3    1    2    
6    9    8    3    1    2    4    5    7    
3    6    2    5    9    1    8    7    4    
8    1    5    7    2    4    9    6    3    
9    7    4    8    6    3    5    2    1    

4    8    1    5    2    7    6    9    3    
2    3    5    1    6    9    4    7    8    
6    7    9    3    4    8    1    2    5    
1    2    3    4    5    6    7    8    9    
5    4    7    8    9    1    2    3    6    
8    9    6    2    7    3    5    1    4    
3    1    2    6    8    4    9    5    7    
7    5    4    9    3    2    8    6    1    
9    6    8    7    1    5    3    4    2    

程序运行的正确性以及性能分析:程序可以正确地运行出结果,计算1000个数独完全解大概用时926ms,理论上来说此程序可以得出9!个不同结果。另外,代码已经上传到coding,请老师指点。

学习过程及遇到的问题:

1、在做这次作业时,最开始我想到的方案是:用一个二维数组,每次填入格子中的值都是1~9中的随机数value,如果该数不符合数独要求,则value循环+1再填入,1~9都不能填入则回溯到上一格。此方法遇到了一个死循环,因为每次回溯到上一格后,该格子总是可以填入一个值,再开始下一格,因此导致死循环。

  解决方法:在网上看到了目前这个算法,此算法每次都会排除不合适的数字(包括当前格不能填入的数字和填入此数字后导致后面的格子不能正确填入的数字),这样可以控制不陷入死循环。(PS:学习到别人的算法后,有试着再在每个格子填入随机数的基础上改进,但无可避免的落入死循环,这点不知道老师能否给出指点。)

2、本次作业从自己开始编程、遇到死循环再到寻找解决方案完成作业大概用了三到四天时间,通过本次作业锻炼了自己的编程能力,同时也看到了自己在算法设计上的不足,学习了别人的算法后才发现原来可以用这样的方式解决问题,还是受益匪浅。

 关于课外任务:

问题:在你一生中身体最健康,精力最旺盛的时候,能在大学学习和研究,是一生中少有的机会。请说明一下,你已经具备的专业知识、技能、能力有哪些?离成为一个合格的 IT专业毕业生,在专业知识、技能、能力上还差距哪些?请看这个技能调查表, 从表中抽取 5 - 7 项你认为对你特别重要的技能, 记下你目前的水平, 和你想在课程结束后达到的水平 (必须至少列出 5 项)。链接: http://www.cnblogs.com/xinz/p/3852177.html

  目前来说,我觉得我的专业知识与技能水平是比较差的,有些专业基础课之前没有学过,这些要利用课外的时间尽快补上。以下几项技能我觉得是比较重要的,希望课程结束之后可以在以下几个方面有较大的提升。

原文地址:https://www.cnblogs.com/dyls/p/9738114.html