zoukankan      html  css  js  c++  java
  • 高级软件工程第二次作业

    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
  • 相关阅读:
    【算法学习笔记】76.DFS 回溯检测 SJTU OJ 1229 mine
    【算法学习笔记】75. 动态规划 棋盘型 期望计算 1390 畅畅的牙签盒(改)
    【算法学习笔记】74. 枚举 状态压缩 填充方案 SJTU OJ 1391 畅畅的牙签袋(改)
    【算法学习笔记】73.数学规律题 SJTU OJ 1058 小M的机器人
    【算法学习笔记】72.LCS 最大公公子序列 动态规划 SJTU OJ 1065 小M的生物实验1
    【算法学习笔记】71.动态规划 双重条件 SJTU OJ 1124 我把助教团的平均智商拉低了
    【算法学习笔记】70.回文序列 动态规划 SJTU OJ 1066 小M家的牛们
    【算法学习笔记】69. 枚举法 字典序处理 SJTU OJ 1047 The Clocks
    【算法学习笔记】68.枚举 SJTU OJ 1272 写数游戏
    【算法学习笔记】67.状态压缩 DP SJTU OJ 1383 畅畅的牙签袋
  • 原文地址:https://www.cnblogs.com/htd6/p/7637662.html
Copyright © 2011-2022 走看看