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


          一、PSP(个人软件过程)

    PSP1.1Personal Software Process Stages预估耗时(minutes)实际耗时(minutes)

                   

         

                             Planning

    计划 30 45

     

         
                           · Analysis · 需求分析 (包括学习新技术) 120 140
    · Code Review · 代码复审 20 25
    · Coding · 具体编码 210 230
    · Coding Standard · 代码规范 20 10
    · Design · 具体设计 30 30
    · Design Review · 设计复审  5 5
    · Design Spec · 生成设计文档 10 15
    · Estimate · 估计任务所需时间 10 5
    · Postmortem & Process Improvement Plan · 总结 30 20
    · Size Measurement · 计算工作量 10 10
    · Test · 测试(自我测试,Debug,提交修改) 110 125
    · Test Report · 测试报告 20 30
    合计   625 690

      二、项目要求

          1.目标:随机生成N个已解答完毕的的数独棋盘矩阵,在控制台中键入"xxxx.exe -c N"格式的命令后将矩阵输出到当前路径下的‘sudotiku.txt’文件中。

          2.限制条件:N值为0~1000000,矩阵不重复。

      三、算法思路

       1.利用回溯法来决定矩阵中每个数字的填法(编写sudomatrixgenerator函数):首先确定数独矩阵中第一行的数字(利用random头文件包含下的shuffle函数进行1~9的随机排列),从第二行第一个数字开始,尝试依次填入数字,填入后依据数独规则进行可行性判断。如果可以填入该数字,则对下一格进行相同的判断。如果某一格对于任何数字的填入都违反了数独规则,则进行回溯,重新填上一格的数字。
    当获得一个可行结果时,算法终止。

       2.在主函数中根据键入的参数值多次调用sudomatrixgenerator函数,将所生成的数独矩阵写入文本文件中,并设置异常捕获。

      四、具体源码

      

    #include <iostream>
    #include <chrono>//std下的一个子命名空间,为持续时间类服务chrono::system_clock
    #include <random>//shuffle随机排列函数  default_random_engine
    #include <algorithm>//使用for_each循环 
    #include <functional>//定义了多个类模板
    #include<fstream>
    using namespace std;
    
    void sudomatrixgenerator( int num) //数独矩阵生成函数
    {
        
        int field[9][9] = { 0 }; //随机生成一行1~9
        auto init = [](int* list) //使用auto进行变量类型的自动匹配
        {
            for_each(list, list + 9, [=](int &i) //用来遍历list进行操作  =for(int i=0;i<9;i++)
               {
                i = &i - list + 1;
                }
            );
            unsigned seed = chrono::system_clock::now().time_since_epoch().count();//调用当前系统时间作为随机种子seed的初始值
            shuffle(list, list + 9, default_random_engine(seed));//生成随机序列,将list至list+9区间内的数值随机排列
        };
        init(field[0]); //初始化第一行元素
        int trylist[9];
        init(trylist); //用于确定数字的尝试顺序
        auto judge = [&field](int i, int j, int num) -> bool //判断填入的数字是否合法
        { 
            for (int k(0); k < j; k++) //判断同一行中是否有重复元素
                if (field[i][k] == num)
                    return false;
            for (int k(0); k < i; k++) //判断同一列中是否有重复元素相同
                if (field[k][j] == num)
                    return false;
            int count = j % 3 + i % 3 * 3; //判断整个3*3区域中是否有重复元素
            while (count--)
                if (!(field[i - i % 3 + count / 3][j - j % 3 + count % 3] - num))
                    return false;
            return true; 
        };
         function<bool(int, int, int*)>//类模板 
            fill = [&trylist, &fill, &field, judge](int y, int x, int* numloc) -> bool //用简单回溯方法进行数字的填入
         {
            if (y > 8)
                return true;
            if (judge(y, x, *numloc)) 
            {
                field[y][x] = *numloc;
                if (fill(y + (x + 1) / 9, (x + 1) % 9, trylist))
                    return true;
            }
            field[y][x] = 0;
            if (numloc - trylist >= 8)
                return false;
            if (fill(y, x, numloc + 1))
                return true;
         };
         fill(1, 0, trylist);//确定某位置要填入的数字
        
        //根据参数输出相应的数独矩阵
        for (int k = 0; k <= num;k++) {
            for (int i(0); i < 9; i++) { 
                for (int j : field[i])
                    cout << j << " ";
                cout << endl;
            }
            cout << endl;//每个矩阵相隔一行
        }
        return;
    }
    
    
    //总程序入口处
    int main(int argc, char *argv[]) {
        int N;//要输出的矩阵个数
        bool check(char *c)//用来判断在命令行中输入的第三个参数是否为数字
         {
            
            int len = strlen(c);//获取字符串长度
            for (int i = 0; i < len; i++) {
                if (!isdigit(c[i]))
                    return false;
            }
            return true;
        }
    
        if (!(argc == 3 && !strcmp(argv[1], "-c") && check(argv[2]))) {//判断输入的命令格式是否符合规范
            cout << "参数输入错误!" << endl;
            return 1;
        }
        
        N = atoi(argv[2]);//将命令行中获取到的第三个字符转换为数字
        ofstream out;//定义文件流对象
        try
        {
            out.open("sudotiku.txt", ios::trunc); //文件不存在则创建,文件存在则清空其中的数据再输入数据
        }
        catch (const std::exception&)//异常捕获
        {
            cout << "打开文件:sudoku.txt 失败!!";
        }
        
        sudomatrixgenerator(N);//生成N个数独棋盘矩阵
        out.close();//关闭sudotiku.txt文件
        return 0;
    }
    
    
    
        

          五、测试运行

       cmd窗口下键入命令:

       

             输出至sudotiku.txt中:

              

             测试结果基本无误,未产生重复矩阵。

            六、性能分析

        n=20的cpu时间:12.541秒

        

               cpu占用:

       

             各函数占用:

       

            七、心得体会

       1.本次学习时长大致为11hours,在此次数独棋盘程序编写的过程当中,回溯法的运用无疑是一大关键,其实可以把回溯法看成是递归调用的一种特殊形式。但对于CS的学生来说,从来没使用过回溯法来解决问题(比如迷宫问题和八皇后问题)的情况是很少见的,不过往往是“对症下药”,针对特定的问题进行解答。这些天看了看《算法设计与分析》回溯法相关内容,觉得对回溯法抽象的很好。如果说算法是解决问题步骤的抽象,那么这个回溯法的框架就是对大量回溯法算法的抽象,再结合以前数据结构这门课程里面的深度优先搜索策略来看,运用回溯法解数独问题会在逻辑上更容易接受,同时自己也对C++11的特性有了更加深入的了解,复习了对象与类的基本方法,学会了如何利用git提交源码至Coding服务器上。

       2.所遇到的问题:如何快速的确定数独矩阵中第一行的数字  相应解决方法是查阅C++相关书籍(如C++ primer)以及阅读某CSDN博主的博客后尝试运用shuffle方法(附该博主博客地址https://blog.csdn.net/elloop/article/details/50397618)

        另一问题是C++函数库的使用问题,在程序调试阶段报错为“无法打开某源文件xxxx”,后证实(以VS2017举例)可在编译器中项目属性一栏的平台工具集中设置其版本为VS2010或以下,再次build即可解决。

       

  • 相关阅读:
    MATLAB GUI制作快速入门
    JavaFX Chart设置数值显示
    Unity查找物体的四大主流方法及区别
    Matlab 图像转极坐标系
    使用python获得N个区分度较高的RGB颜色值
    Arduino学习笔记30
    Arduino学习笔记27
    Arduino学习笔记26
    Arduino学习笔记25
    Arduino学习笔记24
  • 原文地址:https://www.cnblogs.com/ecutwzl1996/p/9744995.html
Copyright © 2020-2023  润新知