• 高级软件工程第二次作业


    1.GitHub地址

    **https://github.com/3781/sudoku **

    2.解题思路

    观察题目输出要求,可以看出解决该问题应该将重点放在"N个”“不重复”这两个关键词上,"N个”决定了问题的规模,所以要考虑的是算法的效率问题,产生单个数独解的耗时不能太长,而“不重复”也是算法设计过程中必须考虑的一个点。
    通过查找资料,产生数独解的方法大多是“回溯法”。该方法需要经过先随机生成一个1到9不重复的序列,再进行尝试性填充,不停的验证,不停的回退,重复验证回退的步骤直到填充结束。可以看出,这种办法产生数独解的效率并不高,因为要做大量的回退操作,而且如果采用该方法,为了达到题目所指的“不重复“要求,那就需要对新产生的解和旧解集合一个个去对比,其消耗的时间也会随着N的增大而变得很长。
    考虑到以上情况,所以决定不采用回溯的方法。继续观察数独解的排列规律,发现如果确定了第一个九宫格,那么通过行变换和列变换就能生成其余的九宫格,从而合成一个正确的数独解,这样的话,就可以产生9!=362880个数独解,产生数独解的速度明显快于“回溯法”。举个例子说明算法的实现过程:
    (1)产生一个1到9不重复的随机序列(如下面:519483276),并依次填入第一个九宫格:
    5 1 9
    4 8 3
    2 7 6
    (2)接着通过列变换生成左边另外的两个九宫格,如第二个九宫格就是通过第一个九宫格按照第二列、第三列、第一列排列生成:
    5 1 9
    4 8 3
    2 7 6

    1 9 5
    8 3 4
    7 6 2

    9 5 1
    3 4 8
    6 2 7
    (3)通过行变换生成剩余的九宫格,如第四个九宫格通过第一个九宫格按照第二行、第三行、第一行排列生成:
    5 1 9  4 8 3  2 7 6
    4 8 3  2 7 6  5 1 9
    2 7 6  5 1 9  4 8 3

    1 9 5  8 3 4  7 6 2
    8 3 4  7 6 2  1 9 5
    7 6 2  1 9 5  8 3 4

    9 5 1  3 4 8  6 2 7
    3 4 8  6 2 7  9 5 1
    6 2 7  9 5 1  3 4 8

    3.设计实现

    1.题目1
    设计了三个类,分别是SudokuData(存储数独解数据)、SudokuGenerator(数独解生成器)、SudokuTest(测试数独解的正确性)。
    2.附加题1
    使用QT进行GUI的开发,因为加入GUI的因素,所以除了使用上面的三个类,同时引入MainWindow(主游戏窗口类)、SelectNumDialog(选择数字对话框类)。

    4.代码说明

    /*** getNextMatrix
      *  获取下一个数独解
      */
    bool SudokuGenerator::getNextMatrix(SudokuData &data)
    {
        if (mCount == mMatrixNum) {
            return false;
        }
        
        // 随机产生第一个九宫格
        randomFirstMatrix();
    
        // 生成数独解
        generate(data);
    
        mCount++;
        return true;
    }
    

    函数getNextMatrix是调用产生数独解的接口。

    /*** randomFirstMatrix
      *  随机产生第一个九宫格
      */
    void SudokuGenerator::randomFirstMatrix()
    {
        while (true) {
            // 生成随机排列
            std::random_shuffle(mFirstMatrix, mFirstMatrix + SudokuData::DIMEN);
    
            std::stringstream ss;
            for (int i = 0; i < SudokuData::DIMEN; i++) {
                ss << mFirstMatrix[i];
            }
            std::string str = ss.str();
    
            // 排列是否已经存在,不存在则跳出循环
            if (mGeneratedFirstMatrices.count(str) == 0) {
                mGeneratedFirstMatrices.insert(std::pair<std::string, int>(str, 1));
                break;
            }
        }
    }
    

    该函数通过map来保证产生的第一个九宫格不重复,将数字序列转换成字符串序列,并作为map的键。而random_shuffle是一个对一个数组进行随机打乱的函数,利用该函数产生随机数字序列。

    /*** generate
      *  根据第一个九宫格的数据,生成数独解
      */
    void SudokuGenerator::generate(SudokuData &data)
    {
        int i, j, k;
        int sqrtDimen = SudokuData::SQRT_DIMEN;
    
        // 放入第一个九宫格数据
        for (i = 0, k = 0; i < sqrtDimen; i++) {
            for (j = 0; j < sqrtDimen; j++) {
                data.setData(i, j, mFirstMatrix[k++]);
            }
        }
    
        int firstRow, firstCol, t1, t2;
    
        // 放入最左边另外两个九宫格数据
        for (t1 = 1; t1 < sqrtDimen; t1++) {
            firstRow = sqrtDimen * t1, firstCol = sqrtDimen * 0;
            for (j = firstCol; j < firstCol + sqrtDimen; j++) {
                int col = firstCol + (j + t1) % sqrtDimen;
                for (i = firstRow; i < firstRow + sqrtDimen; i++) {
                    data.setData(i, j, data.getData(i % sqrtDimen, col));
                }
            }
        }
    
        // 放入剩余九宫格数据
        for (t1 = 0; t1 < sqrtDimen; t1++) {
            for (t2 = 1; t2 < sqrtDimen; t2++) {
                firstRow = sqrtDimen * t1, firstCol = sqrtDimen * t2;
                for (i = firstRow; i < firstRow + sqrtDimen; i++) {
                    int row = firstRow + (i + t2) % sqrtDimen;
                    for (j = firstCol; j < firstCol + sqrtDimen; j++) {
                        data.setData(i, j, data.getData(row, j % sqrtDimen));
                    }
                }
            }
        }
    }
    

    该函数就是按照解题思路中所讲的通过对第一个九宫格进行列、行的变换生成一个正确的数独解。

    /*** randomEmpty
      *  随机挖空
      */
    void MainWindow::randomEmpty()
    {
        int i, j;
    
        // 获得一个随机的数独解
        SudokuGenerator generator;
        generator.setMatrixNum(1);
        generator.getNextMatrix(mSudokuData);
    
        // 产生一个长度为81的一维数组a,数组数据为自己的下标,后又使用random_shuffle进行随机打乱
        srand(time(0));
        const int total = SudokuData::DIMEN * SudokuData::DIMEN;
        int a[total];
        for (i = 0; i < total; i++) {
            a[i] = i;
        }
        std::random_shuffle(a, a + total);
    
        // 随机生成挖空的数量
        mEmptyNum = rand() % 30 + 30;
        
        // 从数组a中取出前mEmptyNum个的数据,即要挖空的点的下标,转换为数独解的行列值后,往挖空处填充0
        for (i = 0; i < mEmptyNum; i++) {
            int value = a[i];
            int row = value / SudokuData::DIMEN;
            int col = value % SudokuData::DIMEN;
            mSudokuData.setData(row, col, 0);
        }
        
        // 保留原始挖空数据,用于清空按钮的还原
        mOriginData = mSudokuData;
    }
    

    该函数是在附加题1中使用,主要是解决数独解随机挖空问题。

    5.测试运行

    1.题目1
    得到的数独解

    2.附加题1
    游戏主界面

    选择填充的数字

    回答错误

    回答正确

    6.性能分析

    这是当N=10000时的性能分析图:

    调用关系树
    N=1000

    N=10000

    从上图可以看出randomFirstMatrix函数占用较多的时间,主要是因为当N变大后,random_shuffle产生的随机序列重复出现的可能性加大,所以需要经过更多次的random_shuffle才能产生不重复的解。

    7.PSP表格

    PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
    Planning 计划 10 10
    · Estimate · 估计这个任务需要多少时间 10 10
    Development 开发 350 370
    · Analysis · 需求分析 (包括学习新技术) 30 40
    · Design Spec · 生成设计文档 50 60
    · Design Review · 设计复审 (和同事审核设计文档) 30 30
    · Coding Standard · 代码规范 (为目前的开发制定合适的规范) 20 20
    · Design · 具体设计 100 120
    · Coding · 具体编码 80 50
    · Code Review · 代码复审 20 20
    · Test · 测试(自我测试,修改代码,提交修改) 20 30
    Reporting 报告 115 135
    · Test Report · 测试报告 40 50
    · Size Measurement · 计算工作量 15 15
    · Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 60 70
    合计 475 515
  • 相关阅读:
    ES6的let命令
    html5的新标签
    text()和html()的区别,以及val()
    jquery链接多个jquery方法
    jquery实现动画
    jquery的滑动
    jquery实现淡入淡出
    jquery的hide()和show()
    jquery里面的名称冲突解决方法
    写给W小姐的一封信
  • 原文地址:https://www.cnblogs.com/htd6/p/7637662.html
Copyright © 2020-2023  润新知