第二次作业——个人项目实战

本文介绍了一种基于随机化的数独生成方法,通过洗牌算法生成数独的第一行,并利用递归回溯填充剩余格子,确保每个数独的唯一性和随机性。

本次作业的项目地址:Github仓库地址

解题思路

嗯,这次的作业要求是

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

刚开始看到的时候可以说是非常茫然的,数独以前虽然玩过,但要用程序来构造那就是完全的另一回事了。一般来说,数独的构造是你根据已有的条件,来判断剩下的空格该填什么数。我自己对这个没什么研究,平时玩的时候挺随心所欲的,这个直接导致我在面对如何用程序来解答的时候毫无头绪。何况这次的要求根本就连条件都没有,要自己凭空造。

凭空,还要随机,只有第一位数是指定的,完全没有方向,所以我就先去网上搜了“如何构建数独”,发现不少前人对此都有研究,我在其中找到了两篇对我来说很有用的,如下:
[http://blog.youkuaiyun.com/qq_31558353/article/details/50615760]
[http://blog.youkuaiyun.com/xiahn1a/article/details/50852849]

第一篇记录了该如何去解一个数独。这次作业对数独棋盘的要求只有一个,就是左上角第一个数字为(学号后两位相加)%9+1,我的学号末两位为16,也就是说我的矩阵左上角那位应为8
该篇的具体做法大致是说,从第一位开始逐个填数,如果已经有数字了就跳过,如果是空的,就从1开始看能不能填入,判断基准是该位所在的行列和小九宫里是否有跟这个数重复的数,如果没有,就填入这个数,如果有,就跳到下一个数判断能不能填,以此类推。
这种做法很容易遇上某一格一个数都填不进,这个时候就返回到它的前一格,重新走下一个数判断,直到全部填满。
这篇代码的作者非常厉害,仅用了一个n就可以表示数独矩阵每一位的坐标。我因为一开始是用num[i][j]的方法在做,不想改,本身也不能完全照他的走,所以就化用了他的方法。

但即便这样也仅仅是能构建一个以8为头的数独阵,并不能做到随机。这个时候我看到了第二篇博文。这位作者的代码有点繁杂,用到的陌生函数太多我一时看不来,但最开头部分的他的思路给了我非常大的提示。

首先第一行肯定是1~9的一种排列,直接使用shuffle进行随机。

当时看到这句的时候简直是茅塞顿开。数独阵这么多,用第一行随机可不就是能制造大批的不一样的数独嘛!然而很惭愧我不会使用shuffle,而且经过查找发现这个似乎是要在1~9里随机排列。
说到这个就要提老师的那个固定了第一位的要求了,我的首位是8,也就是说剩下8个数得在1~7和9之中随机排列,这就很不一样了,因为我在查“该如何生成一组随机数”的时候,基本上都是要在一个固定范围里随机的,并不能像我希望的那样可以自己指定数字。所以必须要想另外的办法,使我可以随心所欲的用指定的那些数随机排序。

于是我就查找到了第三篇对我来说非常重要的博文!如下:
[http://blog.youkuaiyun.com/cxllyg/article/details/7986352]
洗牌算法,我只要自己指定好数组,就可以将[0]位后面的八个数随机排列了。因此我定义了一个数组,内容是{8,1,2,3,4,5,6,7,9},随机的时候将首位除外,剩下的随机排。这也是我第一次认识rand和srand函数。

这些基本就是我的代码的主体构成了。先是在第一个数为8的基础下把1~9随机排序,然后根据已有的第一行推出剩下72个空位应该填什么。

老实说这份作业做得很跌宕起伏,经常是我前一个问题还没理清楚后脚又冒出了另一个问题。命令行输入和查重都是最后一天才发现自己遗漏的,可以说是非常不小心非常不应该的了。何况我最后一天还要返校……命令行还好解决,查重真的就是太困难。一开始做的时候没考虑到,后面想加也很有难度。把全部随机出来的第一排存在同一个数组里,新随机一个就从头开始一个个查有没有重复,数量少还能应付,但多了似乎就会运转不来。之后应该会试着尝试有没有什么更好的方法吧。

设计实现

我一共在头文件里定义了五个函数和一个全局变量,分别是:

bool sign=false;        //用以判断数独阵是否完成 
extern int num2[1000000][9];    //定义一个用于查重的数组

void firstrank(int num[][9],int n,int sum[][9]);        //随机取数独矩阵第一排的值
bool check(int n,int i,int j,int num[][9]);     //判定数字n能否放入num[i][j]中
int build(int num[][9],int x,int y);        //构建数独矩阵
void change();          //重置sign的值为false
bool diffrent(int a[][9],int b[][9],int n);     //比较a,b两个二维数组是否不相同

Main函数在生成一个数独棋盘之前,首先要先运行firstrank,定好第一行数的排列顺序。
然后要运行diffrent来判断是否有重复。每个新生成的一排随机数都要同num2里存放前几轮随机过的数进行比较。一行一行比,每行一遇到不同就跳转下一行继续比较,如果相同,则判断是否是该行最后一个数,如果是,则说明有完全相同的一行,返回false,如果全部比较完都没有重复,那就返回true。
于是就可以开始运行build,从第二行首位开始构建数独。Build中会用到check来判定这个数能否被放进这个位置里,如果check返回的值为true,就下一位;返回的值若为false,就换一个数继续判定。当所有空位都被填上数字时,build里下一个位置就是第十行第一列,这个时候sign会被置为true,build直接return 0,表明数独已经构建完成,可以等待输出。
函数change是为了重复构建函数而备的,因为sign并非是在main函数里所定义,所以为了能够重复使用,在一个数独开始构建前,要先运行change来确保sign的值确实为false。
输出部分因为涉及写入文本文件,所以我直接放在main函数里了,没有另外定义。
输入部分要求命令行输入,还要判断输入的值是否正确,我设定的是输入错误就会提示“输入错误,请重试”。

代码说明

首先是firstrank的第一行随机排序,代码如下

    int a[9]={8,1,2,3,4,5,6,7,9};       //定义一个数组,第一个数为我的学号末两位16经计算后得出的固定值8
    int ran,temp;
    
    for(int i=2;i<9;i++)    //数组除了第一个数固定为8,其余重组 
    {
        ran=rand()%(9-i)+i;
        
        temp=a[i-1];
        a[i-1]=a[ran];
        a[ran]=temp;        
    }

check从行,列,小九宫的角度判断是否能够赋值,如下:

    for(a=0;a<9;a++)        //判断第i行是否有与n重复的数字 
    {
        if(num[i][a]==n) return false;
    }
    
    for(b=0;b<9;b++)        //判断第j列是否有与n重复的数字 
    {
        if(num[b][j]==n) return false;
    }
    
    if(i<3) x=0;        //num[i][j]所在的小九宫左上角的行 
    else if(i>5) x=6;
    else x=3;
    
    if(j<3) y=0;        //num[i][j]所在的小九宫左上角的列 
    else if(j>5) y=6;
    else y=3;
    
    for(a=x;a<x+3;a++)      //判断该九宫中是否有与n重复的数字 
    {
        for(b=y;b<y+3;b++)
        {
            if(num[a][b]==n) return false;
        }
    }

build从第二行第一列开始为一个一个空位赋值,全部完成后将sign的值标为true,表示数独构造成功。

    if(x==9)        //当全部填满时,数独矩阵完成 
    {
        sign=true;
        return 0;
    }

    for(k=1;k<=9;k++)       //从1开始逐个测试是否能放入num[x][y]的位置中 
    {
        if(check(k,x,y,num)==true)
        {
            num[x][y]=k;
            if(y<8) build(num,x,y+1);       //该位不是这一行的最末位,则继续对其下一位进行置数 
            else build(num,x+1,0);      //该位已经是这一行的最末位,跳至下一行首位进行置数 
            
            if(sign==true)  return 0;       //当数独阵完成时,直接返回 
         
            num[x][y]=0;        //如果置数失败,则将这位置0,继续寻找下一个适合的数字 
        } 
    }

Main函数先要确定随机排序的随机种子和数独最后要输出的文件“sudoku.txt”,然后根据在命令行输入的n来构造数独,如果输入的不属于规定范围内,还要给出错误提示。

n=atoi(argv[argc - 1]);     //命令行输入
    srand((unsigned)time(NULL));        //随机种子 
    ofstream outFile;           //输出文件“sudoku.txt” 
    outFile.open("sudoku.txt");
    if(n<1000000 && n>1)            //判断输入的变量是否为int型 
    {
        for(i=0;i<n;i++)
        {
            change(); 
            int num1[9][9]={0};
            firstrank(num1,i,num2);         //确定第一行的排序
            if(diffrent(num1,num2,i)==1)        //判断是否重复
            {
                build(num1,1,0);        //开始构建数独
                for(j=0;j<9;j++)        //输出数独阵到文件“sudoku.txt”中 
                {
                    for(k=0;k<9;k++)
                    {
                        outFile << num1[j][k] << " ";
                    }
                    outFile << endl;
                }
                outFile << endl;        //每个数独阵间隔一个空行 
            }
            else i=i-1;
        }
    }
    else
    {
        cout << "输入错误,请重试。" << endl;        //若变量不为int型,则输出错误提示 
    }
    outFile.close(); 

测试运行

截图如下:
886072-20170910213230085-1764740931.png

性能分析

886072-20170910221505944-755292428.png

执行力,泛泛而谈的理解

执行力,按百科上的说法就是“有效利用资源、保质保量达成目标的能力”,也就是说利用现有的条件和知识,通过自己的力量又快又好的完成需要完成的任务。而泛泛而谈,指浮于表面,没有深入研究,一个人讲出来的东西很肤浅毫无思考力,大概就是泛泛而谈了。
按我个人的理解,拿这次作业作比,如果一个人在看到作业后立刻开始思考入手,然后在deadline来临前从容而高水平的完成了它,那就说明他有很强的执行力了。
泛泛而谈嘛,比如说,我上面提及我要换一种更稳妥的方法来查重复,不用事先设一个那么庞大的数组,我说了,却没有去做,并且在想起时不断强调以后会去做,那就是一种很浅薄的表现。
……老实说,我觉得我现在在这里谈论这些,谈论代码的言辞就挺肤浅挺没有见识的……

遇到的困难及解决方法

关于代码思路之中的困难上面已经提过了,现在说的都是一些细节上但是又很恼人的问题。

一个是命令行输入,今天最后了才发现的,int main(int argc ,char *argv[]),乍一看很莫名其妙,好在我舍友为我倾情指点了一下,可以简单进行使用了。
下一个是输出到txt。这个我查了我的一本讲C++的书,按照书上的说法这个创建的文档一般会跟可执行文件在一起。于是我就照着书上的做法直接用了。我比较喜欢在编代码的时候一个一个功能加,所以在建txt之前我都是用cout直接进行输出的。先前的时候是另设了一个output函数来输出,后来为了用outFile又改成直接放在main函数里。
最困难的还是没有办法查重复。我如果要改的话前面firstrank()就要跟着变,时间不足,所以就直接建数组。数组的话必须视线声明大小,[1000000][9]太大了,我一开始在main函数里建的,后来发现没办法执行,又改成全局变量。这下不会报错了,但我自己试了下,我的电脑最多只能随机出5000个左右的数独阵,如果输入6000,就会一直停留在运行,不能结束。我猜测是电脑容量的问题。

PSP表格

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划1030
· Estimate· 估计这个任务需要多少时间1030
Development开发620890
· Analysis· 需求分析 (包括学习新技术)6050
· Design Spec· 生成设计文档00
· Design Review· 设计复审 (和同事审核设计文档)00
· Coding Standard· 代码规范 (为目前的开发制定合适的规范)200
· Design· 具体设计3060
· Coding· 具体编码240300
· Code Review· 代码复审3060
· Test· 测试(自我测试,修改代码,提交修改)240420
Reporting报告9090
· Test Report· 测试报告6070
· Size Measurement· 计算工作量105
· Postmortem & Process Improvement Plan· 事后总结, 并提出过程改进计划2015
合计8701010

转载于:https://www.cnblogs.com/shadeinblue/p/7502404.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值