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

Posted ecutwzl1996

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用程序随机构造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"格式的命令后将矩阵输出到当前路径下的‘sudoku.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即可解决。

   



以上是关于利用程序随机构造N个已解答的数独棋盘的主要内容,如果未能解决你的问题,请参考以下文章

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

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

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

150+行Python代码实现带界面的数独游戏

C语言折半查找法详细代码(假如有10个已排好序的数)

在 Java 中使用 JPanel 的数独板