高级软件工程第二次作业

数独生成算法优化
本文介绍了一个数独生成项目的开发过程,从需求分析到设计实现再到性能优化,详细记录了如何通过改进递归函数大幅提高数独生成效率。

Github项目地址

PSP(预估) 见下表

PSPPersonal Software Process Stages预估耗时(分钟)
Planning计划30
· Estimate· 估计这个任务需要多少时间30
Development开发220
· Analysis· 需求分析 (包括学习新技术)60
· Design Spec· 生成设计文档30
· Design Review· 设计复审 (和同事审核设计文档)30
· Coding Standard· 代码规范 (为目前的开发制定合适的规范)10
· Design· 具体设计10
· Coding· 具体编码30
· Code Review· 代码复审20
· Test· 测试(自我测试,修改代码,提交修改)30
Reporting报告35
· Test Report· 测试报告10
· Size Measurement· 计算工作量5
· Postmortem & Process Improvement Plan· 事后总结, 并提出过程改进计划20
合计285

解题思路

大概浏览题目之后,着重看了下项目需求

项目需求

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

需求简介明了,即构造N个数独棋盘。之前对数独游戏只有大概的了解,所以还是先对“数独”这个概念重新了解下:

数独是源自18世纪瑞士的一种数学游戏。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复。

数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。

对问题进行抽象,即构造一个9*9的矩阵满足如下要求:

  • 矩阵元素只能为1-9的数字
  • 每一行的数字不重复
  • 每一列的数字不重复
  • 9个3*3小宫格里的数字不重复

数字只有1-9,想到可以用暴力搜索来求解数独棋盘。

设计实现

设计一个关键函数递归求解数独棋盘,只要每次检验是否满足上面的四个条件,大致流程如下。

1249576-20171008125614793-1684803615.png

代码说明

以下为关键函数代码:

bool solve_sudoku(int i, int j)            //求解第i行,第j列元素可放置的数
{
    if (i > 9 || j > 9) return true;       //行或列已填满,返回true
    for (int k = 1; k < 10; ++k){          

        bool Isok = true;

        if (Isok){                         //检查所在行是否可以填k
            for (int t = 1; t < j; ++t)
                if (sudoku[i][t] == k){
                    Isok = false;
                    break;
                }
        }

        if (Isok){                         //检查所在列是否可以填k
            for (int n = 1; n < i; ++n)
                if (sudoku[n][j] == k){
                    Isok = false;
                    break;
                }

        }

        if (Isok){                                            //检查所在小九宫格是否可以填k
            int low_i = i / 3 * 3 + 1;
            int low_j = j / 3 * 3 + 1;
            if (i % 3 == 0) low_i = i / 3;
            if (j % 3 == 0) low_j = j / 3;
            for (int x = 0; x < 3; ++x){
                if (!Isok) break;
                for (int y = 0; y < 3; ++y)
                    if (sudoku[low_i+x][low_j+y] == k){
                        Isok = false;
                        break;
                    }
            }
        }

        if (Isok){                     //递归求解下一格
            sudoku[i][j] = k;
            if (j < 9){
                if (solve_sudoku(i, j + 1)){
                    return true;
                }
                    
            }
            else {
                if (i < 9){
                    if (solve_sudoku(i + 1, 1)){
                        return true;
                    }
                        
                }
                else{
                    return true;
                }
                    
            }
            sudoku[i][j] = 0;         //无解,置0以免影响下次试解
        }
    }
    return false;                          //1-9均试过,无解
}

测试运行

程序测试运行结果如下图所示。

1249576-20171008183452715-916355023.png

改进与优化

下图为生成一个数独棋盘所用的时间。显然,递归调用的花销太大。但是,只生成一个数独棋盘的所花的时间也远远超出了一般递归函数所需要的代价。
1249576-20171008185548403-39809160.png

生成一个棋盘就花了三分钟,递归时间占用89.55%,将优化目标锁定在递归函数。发现二维数组下标出现表达式可能是导致递归耗时的原因,将源代码的

if (Isok){                                    //检查所在小九宫格是否可以填k
            int low_i = i / 3 * 3 + 1;
            int low_j = j / 3 * 3 + 1;
            if (i % 3 == 0) low_i = i / 3;
            if (j % 3 == 0) low_j = j / 3;
            for (int x = 0; x < 3; ++x){
                if (!Isok) break;
                for (int y = 0; y < 3; ++y)
                    if (sudoku[low_i + x][low_j + y] == k){
                        Isok = false;
                        break;
                    }
            }

        }

改为

if (Isok){                                    //检查所在小九宫格是否可以填k

            int up1 = ( i/3 ) * 3 + 3 ; // (i,j)方格所在的3×3小方格i坐标的上限  
            int up2 = ( j/3 ) *3 + 3;  // (i,j)方格所在的3×3小方格在j坐标的上限  
   
            if( i % 3 == 0 )    //这是针对特殊情况的处理  
                up1 = i ;   
            if( j % 3 == 0 )   
                up2 = j ;  
   
            for( int p = up1-2 ; p <= up1 ; ++p  )  /* 检查在3×3的小方格中是否出现了同一个数字 */  
            {  
                if( Isok == false )  /* 跳出外层循环 */  
                    break ;  
                for( int q = up2-2 ; q <= up2 ; ++q )   
                    if( sudoku[p][q] == k )   
                    {  
                        Isok = false ;  
                        break ;  
                    }  
            }  

        }

再次进行性能分析,得到如下结果。

1249576-20171008190632621-368168379.png

总执行时间由原来的三分钟降低到1.2秒,性能得到了很大的提高!

PSP(实际)

PSPPersonal Software Process Stages实际耗时(分钟)
Planning计划30
· Estimate· 估计这个任务需要多少时间30
Development开发460
· Analysis· 需求分析 (包括学习新技术)40
· Design Spec· 生成设计文档60
· Design Review· 设计复审 (和同事审核设计文档)60
· Coding Standard· 代码规范 (为目前的开发制定合适的规范)60
· Design· 具体设计30
· Coding· 具体编码120
· Code Review· 代码复审60
· Test· 测试(自我测试,修改代码,提交修改)30
Reporting报告100
· Test Report· 测试报告40
· Size Measurement· 计算工作量30
· Postmortem & Process Improvement Plan· 事后总结, 并提出过程改进计划30
合计590

收获与感想

这次的项目需求看似简单,但是在完成的过程中发现了很多自己没在意的事情,比如源代码管理,还有代码的性能分析等等。还有一点感触就是要把一门开发语言应用到实际项目中来,仅仅掌握基础语法是远远不够的,一些基础的函数库必须要熟悉,不然用都不知从何用起,每次搜索就浪费了很多的时间。所以,日常还是要多写写小项目和多看看算法的书。

转载于:https://www.cnblogs.com/liminghuang/p/7636862.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值