2019软件工程第三次作业

2019软件工程第三次作业

数独游戏

戳这里进入Github项目
第一眼看到要做数独的时候,脑海里的第一反应就是用深搜。现在好了,确定了算法,接下来就是要去实现它,可是对于将近半年没写过深搜的我要写一个如此经典的深搜还是有些难度(花了一个下午写bug改bug,弱鸡实锤)。

psp表格
PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划10min10min
Estimate估计这个任务需要多少时间25h27h
Development开发5h5h
Analysis需求分析 (包括学习新技术)30min30min
Design Spec生成设计文档1h45min
Design Review设计复审1h1h
Coding Standard代码规范 (为目前的开发制定合适的规范)2h3h
Design具体设计30min15min
Coding具体编码4h4h
Code Review代码复审5h5h
Test测试(自我测试,修改代码,提交修改)1h1h
Reporting报告3h3h
Test Report测试报告30min30min
Size Measurement计算工作量30min30min
Postmortem & Process Improvement Plan事后总结, 并提出过程改进计划1h2h
合计25h27h

由于之前没有做计划评估的习惯,所以对这些工作流程的时间观念还是有些差。第一次做出了计划,实施完之后发现还是有些高估了自己,实际用时超出了预计。

计算模块接口的设计与实现过程

对于这个题目首先想到的就是深搜+回溯的算法,算法复杂度约O($n^{2n}$),我打算将大体结构分为一个求解数独的函数、一个查找当前位置可能存在的解的search()函数用于在数独求解过程中,当查找不到解时进行回溯操作然后再继续搜索…

先是头文件和全局变量:

#include "stdafx.h"
#include<iostream>
#include<fstream>
#include<string.h>
#include<math.h>
using namespace std;
int mark[10][10][10] = { 0 };
int n_size = 0;
int sgle[10][10][10] = { 0 };
int sudo[10][10] = { 0 };
bool success = false;
int times;

以下为查找可能存在的解的函数:

bool search(int x, int y)//查找当前位置可能存在的解{
for (int i = 0; i < n_size; i++) {
    mark[x][y][sudo[i][y]] = 1;//判断列
}
for (int i = 0; i < n_size; i++) {
    mark[x][y][sudo[x][i]] = 1;//判断行
}
if (n_size == 4 || n_size == 6 || n_size == 8 || n_size == 9) {
    if (n_size == 4 || n_size == 9) {
        int n = (int)sqrt(n_size);
        int k1 = x / n;
        int k2 = y / n;
        k1 *= n;
        k2 *= n;
        for (int i = k1; i < k1 + n; i++)
            for (int j = k2; j < k2 + n; j++) {
                mark[x][y][sudo[i][j]] = 1;
            }
    }
    else if (n_size == 6) {
        int k1 = x / 2;
        int k2 = y / 3;
        k1 *= 2;
        k2 *= 3;
        for (int i = k1; i < k1 + 2; i++)
            for (int j = k2; j < k2 + 3; j++) {
                mark[x][y][sudo[i][j]] = 1;
            }
    }
    else if (n_size == 8) {
        int k1 = x / 4;
        int k2 = y / 2;
        k1 *= 4;
        k2 *= 2;
        for (int i = k1; i < k1 + 4; i++)
            for (int j = k2; j < k2 + 2; j++) {
                mark[x][y][sudo[i][j]] = 1;
            }
    }
}
for (int i = 1; i <= n_size; i++) {
    if (mark[x][y][i] == 0) {
        return true;
    }
}
return false;
}

其中我将这个search函数设置为bool类型,目的是查找到当前格不存在解的时候就返回false告诉数独求解函数,使求解函数进行回溯从而尝试其他可能存在的解。

以下为数独求解函数:

void sudoku(int x, int y, char*o) //深度优先搜索,数独求解函数{
for (int i = 1; i <= n_size; i++)
{
    mark[x][y][i] = 0;
}
if (x == n_size) {
    success = true;
    ofstream InFire(o, ios::app);
    for (int i = 0; i < n_size; i++) {
        for (int j = 0; j < n_size; j++)
            InFire << sudo[i][j] << ' ';
        InFire << endl;
    }
    InFire << endl;
}
else if (x < n_size&&y < n_size && !sudo[x][y]) {
    if (search(x, y)) {
        for (int k = 1; k <= n_size; k++) {
            if (!mark[x][y][k]) {
                sudo[x][y] = k;
                if (y < n_size - 1) {
                    sudoku(x, y + 1, o);
                }
                else if (x < n_size) {
                    sudoku(x + 1, 0, o);
                }
                sudo[x][y] = 0;
            }
        }
    }
    else
        return;
}
else if (x < n_size&&y < n_size&&sudo[x][y]) {
    if (y < n_size - 1) {
        sudoku(x, y + 1, o);
    }
    else if (x < n_size) {
        sudoku(x + 1, 0, o);
    }
}
}

比较经典的深度优先搜索题目(如开头所说,改了好久的BUG)这里就不在赘述了。我用了一个bool变量success来判断数独是否有解,由于之前作业上写明数独确保仅有一个解所以我在程序找到一个解以后就通过success的值判断退出,后来想要尝试求解多解数独,于是将if(x==n_size)里的return;删除,这样就求出了数独的多解。

接下来就是主函数:

int main(int argc, char*argv[]) {
FILE *fp;
char *file_path = { "./" }, *output = { "./" };
for (int i = 0; i < argc; i++)//读取命令行参数(这里借鉴了李承泽同学的方法)
{
    if (strlen(argv[i]) == 1)
    {
        if (i == 2)
            n_size = atoi(argv[i]);
        if (i == 4)
            times = atoi(argv[i]);
    }
    if (i == 6)
        file_path = argv[i];
    if (i == 8)
        output = argv[i];
}
fopen_s(&fp,file_path, "r");
if (fp == NULL)
    return -1;
for (int t = 1; t <= times; t++)//读入盘面
{
    for (int i = 0; i < n_size; i++) {
        for (int j = 0; j < n_size; j++)
            fscanf_s(fp, "%d", &sudo[i][j]);
    }
    cout << endl;
    single();//判断初始盘面中存在的唯一解
    ofstream InFire(output, ios::app);
    InFire << "盘面" << t << ":" << endl;
    sudoku(0, 0, output);
    if (!success)
        InFire << "No answer!!" << endl;
    success = false;//完成一次盘面计算后数据重新初始化
    memset(sudo, 0, sizeof(sudo));
    memset(mark, 0, sizeof(mark));
    memset(sgle, 0, sizeof(sgle));
}
return 0;
}

优化

能不能在现有的基础上进一步优化算法呢?如果在现有的盘面上有的格子有且仅有一个解,则直接将唯一解填入,数独是可以产生连锁反应的,一个地方填入唯一解就可能使得其他地方所存在的可能解的数量减少,从而大大减少求解时间。于是我写了一个判断地方格唯一解的函数。

void single() {
int count = 0;
for (int i = 0; i < n_size; i++) {
    for (int j = 0; j < n_size; j++) {
        for (int k = 0; k<n_size; k++)//判断行
            if (sudo[k][j] && !sgle[i][j][sudo[k][j]]) {
                count++;
                sgle[i][j][sudo[k][j]] = 1;
            }
        for (int k = 0; k < n_size; k++) //判断列
            if (sudo[i][k] && !sgle[i][j][sudo[i][k]]) {
                count++;
                sgle[i][j][sudo[i][k]] = 1;
            }
        if (n_size == 4 || n_size == 6 || n_size == 8 || n_size == 9)//判断宫 
        {
            if (n_size == 4 || n_size == 9) {
                int n = (int)sqrt(n_size);
                int k1 = i / n;
                int k2 = j / n;
                k1 *= n;
                k2 *= n;
                for (int k = k1; k < k1 + n; k++)
                    for (int l = k2; l < k2 + n; l++)
                        if (sudo[k][l] && !sgle[i][j][sudo[k][l]]) {
                            count++;
                            sgle[i][j][sudo[k][l]] = 1;
                        }
            }
            else if (n_size == 6) {
                int k1 = i / 2;
                int k2 = j / 3;
                k1 *= 2;
                k2 *= 3;
                for (int k = k1; k < k1 + 2; k++)
                    for (int l = k2; l < k2 + 3; l++)
                        if (sudo[k][l] && !sgle[i][j][sudo[k][l]]) {
                            sgle[i][j][sudo[k][l]] = 1;
                            count++;
                        }
            }
            else if (n_size == 8) {
                int k1 = i / 4;
                int k2 = j / 2;
                k1 *= 4;
                k2 *= 2;
                for (int k = k1; k < k1 + 4; k++)
                    for (int l = k2; l < k2 + 2; l++)
                        if (sudo[k][l] && !sgle[i][j][sudo[k][l]]) {
                            sgle[i][j][sudo[k][l]] = 1;
                            count++;
                        }
            }
        }
        if (count == n_size - 1)\\即只剩余一个解 {
            for (int k = 1; k <= n_size; k++)
            {
                if (!sgle[i][j][k] && !sudo[i][j]) {
                    sudo[i][j] = k;
                    break;
                }
            }
        }
        count = 0;
    }
}
现在来将改良版与初始版本进行对比:

以下是没有加入single()函数的性能测试结果:

性能测试结果

以下是加入single()函数之后的性能测试结果:

性能测试结果
可以看出加入single()函数后程序CPU占用增加但是运行的时间成功减少,程序在一定程度上得到了优化。

样例测试结果

首先是九宫格的求解:

九宫格

接下来就是3、4、5、6、7、8宫格的求解结果辽。先附上cmd运行图例:

cmd

下面是数据集和程序得出的数据集解:

数据集解

(在这里真的要感谢助教大大辛苦做的数据集,阿里嘎多)

最后来总结一下

之前大一大二没有那么注重代码的规范问题,遇到了编译成功后编译器提出的警告也是选择性无视2333,都是一切以编译成功,能正确运行就“大功告成”了,这是与现在最大的区别之处,通过这次的作业我也学到了不仅要确保代码正确运行,提升代码的鲁棒性也是同样重要的,这在以后的学习或是工作过程中都是不可或缺的环节。

转载于:https://www.cnblogs.com/yeqiyi/p/11585067.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值