数独~编程作业

github:ThinMoon\031702541

PSP

PSP2.1Personal Software Process Stages预估耗时(h)实际耗时(h)
Planning计划33
Estimate估计这个任务需要多少时间2926
Development开发2621
Analysis需求分析 (包括学习新技术)46
Design Spec生成设计文档11
Design Review设计复审10.5
Coding Standard代码规范 (为目前的开发制定合适的规范)10.5
Design具体设计11
Coding具体编码1510
Code Review代码复审11
Test测试(自我测试,修改代码,提交修改)21
Reporting报告35
Test Repor测试报告11
Size Measurement计算工作量11
Postmortem & Process Improvement Plan事后总结, 并提出过程改进计划13
合计3229

设计、实现

第一眼看到题目发现竟然是数独题,当时想这和大一大二有什么区别怎么做来做去还是算法题,在认真读完博客后发现这可不仅仅只是像以往一般只需写出算法然后控制台能够输出真确答案,往oj上一提交ac后就能两腿一伸不再管的算法题啊!
代码分两个模块分别为数独算法模块(Sudoku)、文件读取模块(FileIO)。

Sudoku模块

求解数独第一眼我看到是蒙蔽的,毕竟咱就是个算法渣渣,所以通过在网络上搜索一番后恍然大悟,原来利用回溯法便能轻松解决!

backTrace()
 /**
     * 回溯算法
     *
     * @param i 行号
     * @param j 列号
     * @param outSudokus 输出链表
     */
    public void backTrace(int i, int j, LinkedList<int[][]> outSudokus) {
        if (i == rank-1 && j == rank) {
            int[][] array = new int[9][9];
            for(int ai = 0; ai < rank; ai++) {
                for(int aj = 0; aj < rank; aj++) {
                    array[ai][aj] = matrix[ai][aj];
                }
            }
            outSudokus.add(array);
            
            return;
        }
 
        //已经到了列末尾了,还没到行尾,就换行
        if (j == rank) {
            i++;
            j = 0;
        }
 
        //如果i行j列是空格,那么才进入给空格填值的逻辑
        if (matrix[i][j] == 0) {
            for (int k = 1; k <= rank; k++) {
                //判断给i行j列放1-9中的任意一个数是否能满足规则
                if (check(i, j, k)) {
                    //将该值赋给该空格,然后进入下一个空格
                    matrix[i][j] = k;
                    backTrace(i, j + 1, outSudokus);
                    //初始化该空格
                    matrix[i][j] = 0;
                    
                }
            }
        } else {
            //如果该位置已经有值了,就进入下一个空格进行计算
            backTrace(i, j + 1, outSudokus);
        }
    }

这段代码就是利用了回溯法求解数独的核心,其原理就是通过dfs检测到每一个为零的格子后对每一个格子进行填数,填充过程为:对当前所在格从1~9依次填充,若检测函数检测到此数能够填充便进行填充,随后进入下一格知道某一格出现1~9都无法填充的情况便开始回溯将之前走过最近的路径都归为0后重新进行判断。所以,一条路走到黑然后能走遍所有路径的backTrace()支持多解的情况

chcek()
  /**
     * 判断给某行某列赋值是否符合规则
     *
     * @param row    被赋值的行号
     * @param line   被赋值的列号
     * @param number 赋的值
     * @return
     */
    public boolean check(int row, int line, int number) {
        //判断该行该列是否有重复数字
        for (int i = 0; i < rank; i++) {
            if (matrix[row][i] == number || matrix[i][line] == number) {
                return false;
            }
        }

        
        //当盘面为4,6,8,9时需要对小宫格进行判断
        int tempRow = 0;
        int tempLine = 0;
        switch(rank) {
        case 4:
            tempRow = row / 2;
            tempLine = line / 2;
            for(int i = 0; i < 2; i++) {
                for(int j = 0; j < 2; j++) {
                    if(matrix[tempRow * 2 + i][tempLine * 2 + j] == number) {
                        return false;
                    }
                }
            }
            break;
        case 6:
            tempRow = row / 2;
            tempLine = line / 3;
            for(int i = 0; i < 2; i++) {
                for(int j = 0; j < 3; j++) {
                    if(matrix[tempRow * 2 + i][tempLine * 3 + j] == number) {
                        return false;
                    }
                }
            }
            break;
        case 8:
            tempRow = row / 4;
            tempLine = line / 2;
            for(int i = 0; i < 4; i++) {
                for(int j = 0; j < 2; j++) {
                    if(matrix[tempRow * 4 + i][tempLine * 2 + j] == number) {
                        return false;
                    }
                }
            }
            break;
        case 9:
            tempRow = row / 3;
            tempLine = line / 3;
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 3; j++) {
                    if (matrix[tempRow * 3 + i][tempLine * 3 + j] == number) {
                        return false;
                    }
                }
            }
            break;
        default:
            break;
        } 
        return true;
    }

如果说backTrace()是Sudoku的灵魂,那么check()便是backTrace()的伴侣,没有check()的backTrace()就是徒有其表,只会到处瞎转。check()的作用其实也很简单,就是检测当前数字是否能够填进当前格子检测原理为判断将填入数字是否在当前行、列及小宫格中存在。
或许你会对backTrace()中的参数感到迷惑,不急我们下文详解

FileIO模块

好吧我承认算法渣渣的我输入输出流也不怎么会,不会怎么办?去学啊!于是又开始了我的学习+魔改+查api之路,终于是实现了读取txt文件及将内容输出到txt中代码,接着又传来一个噩耗“文件里有多个盘”,???黑人问号,原来我之前没看清楚一个txt文件中是有多个盘的!好吧,继续改,改改。。。最终终于有了如下代码

readFile()
    /**
     * 读入TXT文件
     * 返回一个整型数组包含里面所有数字
     */
    public int[] readFile( String pathName) {
        int[] array = new int[5000];
        int count = 0;
        
        try {
            FileReader reader = new FileReader(pathName);
            BufferedReader br = new BufferedReader(reader);
            int number = 0;
   
            while ((number = br.read()) != -1) {
                if((number-48) >= 0) {
                    array[count++] = number - 48;
                }
            }
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return array;
    }

这个方法就是读入文件中的所有数字然后返回一个整型数组。

writeFile()
 /**
     * 写入TXT文件
     * 
     */
    public void writeFile(String pathName, LinkedList<int[][]> sudokus, int rank) {
        
        try {
            File writeName = new File(pathName); 
            writeName.createNewFile(); 
            FileWriter writer = new FileWriter(writeName);
            BufferedWriter out = new BufferedWriter(writer);
            
            for(int k = 0; k < sudokus.size(); k++) {
                int[][] array = new int[9][9];
                array = sudokus.get(k);
                for(int i = 0; i < rank; i++) {
                    for(int j = 0; j < rank; j++) {
                        out.write(array[i][j] + 48);
                        out.write(" ");
                    }
                    out.write("\n");
                }
                out.write("\n");
            }
             
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

写到这个方法或许你就懂了为什么上面backTrace()会有一个链表参数了吧,这个链表就是用来储存所有解的容器!然后在writeFile()中将所有的解打印出来!
或许你的问题又来了,readFile()返回的是一个一维数组我们该怎么操作呢? 这时候我们的splitArray()方法便应运而出啦!

splitArray()
 /**
     * 将整型readerFile读出的数组分解为不同盘面
     * 用LinkedList进行存储二位数组
     * 返回一个LinkedList
     * @param suCnt面数
     * @param rank阶数
     */
    public LinkedList<int[][]> splitArray(int[] array, int rank, int suCnt) {
        LinkedList<int[][]> sudokus = new LinkedList<int[][]>();
        int count = 0;
        for(int k = 0; k < suCnt; k++) {
            int[][] temp = new int[9][9];
            for(int i = 0; i < rank; i++) {
                for(int j = 0; j < rank; j++) {
                    temp[i][j] = array[count++];
                }
            }
            sudokus.add(temp);
        }
        
        return sudokus;
    }

测试

三阶
1644770-20190924222942212-566691875.png
四阶
1644770-20190924222952243-664505141.png
五阶
1644770-20190924222957118-367012416.png
六阶
1644770-20190924223001309-604999349.png
七阶
1644770-20190924223005833-723294311.png
八阶
1644770-20190924223010122-387974233.png
九阶
1644770-20190924223014524-1772594441.png

性能分析

性能分析主要使用jprofiler但是分析完的结果我是一脸茫然的
1644770-20190924223018746-948555834.png

1644770-20190924223023378-1336859560.png

TO THE END

这次作业其实算法不算太难但是各种新东西确实让我学到了很多,比如熟悉了git的使用,熟悉了java的基本语法,学会了去查api等等,总的来说搜获还是蛮大的!

转载于:https://www.cnblogs.com/ThinMoon/p/11581667.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值