个人项目作业-数独生成与求解

 

个人项目作业-数独生成与求解

 

项目地址

https://github.com/FelixCaae/SudokuGen

解题思路

  一开始打算自己设计一个算法,因为之前也做过数独题目,对解题思路有一定的了解,打算用排除法加搜索来做,初步的思路是这样的,首先初始化一个空数独表格,给每个格子内填入可能的解,在没有任何约束条件的情况下就是1-9。然后将题目上的数字依次填入,每填入一个数字就依据约束条件对可能性做一些修改,比如(0,0)的位置填入了1,那么就从第一个宫内的其他数字单元和该行该列的其他数字单元中移除可能解1.如此反复,将题目填入完毕后,依次遍历可能解最少的单元来进行解的搜索,每次确定一个解就进一步压缩限制条件直到所有数字都被填完。

这个算法没有来得及实施,因为感觉很可能会性能不足,所以就上百度搜索了一下数独的算法求解,了解到一种以宫为单位进行填写的算法而不是以格为单位进行填写的回溯法和一种以数独的同质性为基础的交换算法(用于生成而不是求解)。Wiki上列举的各种方法更为详细,但是没有细看,决定先试试回溯法,并且用python写了一个版本用于验证。

数独入门解题技巧 http://ask.kedo.gov.cn/resource/stars/823575.shtml

回溯法介绍  http://blog.sina.com.cn/s/blog_a28e3dd90101e1i2.html

设计过程

  这部分参考了之前OO的经验,把功能分给了4个类,参数处理类(ArgumentHandler)用于处理输入的参数,文件处理类(FileHandler)用于打开关闭文件和读写数独,表类(Table)用于记录数独数据并且提供生成和求解的方法,数独缓冲类(SdkBuffer)用于临时存储生成的数独。

类确定后,首先是设计公有方法,确定类的协同关系以及确保覆盖全部功能,其中比较重要的Table类试着使用了形式化的方法

  1 class Table
  2 
  3 {
  4 
  5 private:
  6 
  7 int cells[9][9];
  8 
  9 int total;
 10 
 11 int top;
 12 
 13 SdkBuffer* pCurrentBuffer;
 14 
 15 public:
 16 
 17 Table();
 18 
 19 /*@Modifies:this
 20 
 21 Set all elements to zero ,which stands for not being filled.
 22 
 23 */
 24 
 25 void SetZero();
 26 
 27  
 28 
 29 /*@Requires:0<=row,col<9 and 1<=num<=9
 30 
 31  @Modifies:this
 32 
 33 Set one element to the value given
 34 
 35 */
 36 
 37 void Set(int row, int col, int num);
 38 
 39  
 40 
 41 /*@Requires:length(num)>=9 and any element i in num[][] ==> 1<=i<=9
 42 
 43    @Modifies:this
 44 
 45    Set the table with the two-dimension array given.
 46 
 47 */
 48 
 49 void Set(int num[][9]);
 50 
 51  
 52 
 53 /*@Requires:total>0 and total <=100000 and sdb!=null and total<sdb->GetCapacity()
 54 
 55    @Modifies:sdb
 56 
 57    Generate sudoku solution and write to a file.
 58 
 59 */
 60 
 61 void Generate(unsigned int total, FileHandler* fh);
 62 
 63  
 64 
 65 /*@Requires:sdb!=nulls
 66 
 67 @Effects:/result==true <==> We can find at least one solution
 68 
 69 @Effects:/result==false <==> We can`t find any solution
 70 
 71 Solve the problem in the table
 72 
 73 */
 74 
 75 bool Solve(SdkBuffer* sdb);
 76 
 77  
 78 
 79 /*Solve the problems in src buffer and write the answers to the dst buffer
 80 
 81   Notice:it will clear the dst buffer firtst and clear src buffer as an effect
 82 
 83 */
 84 
 85 void Solve(SdkBuffer * sdbSrc, SdkBuffer * sdbDst);
 86 
 87  
 88 
 89 /*@Requires:total>0 and total <=100000 and sdb!=null
 90 
 91 @Modifies:sdb
 92 
 93 Generate sudoku solution to the sdb
 94 
 95 */
 96 
 97 void Generate(unsigned int total, SdkBuffer* sdb);
 98 
 99  
100 
101 /*@Requires:src!=null,dst!=null
102 
103 @Modifies:src,dst
104 
105 Solve the problem set given by the src and output the result to dst.
106 
107 */
108 
109 void Solve(FileHandler* src, FileHandler*dst);
110 
111 ~Table();
112 
113 private:
114 
115 /*@Requires : 0<rst, cst<3 1 <= num <= 9
116 
117    @Effects : / result == null <= = > there is one number in Palace(rst, cst) equals to num
118 
119    @Effects : / result == {-1 } <= = > there is no suitable place for num in Palace(rst, cst)
120 
121    @Effects : len(/ result) >= 1 && / result != {-1} <= = > there is one or more suitable place for num in Palace(rst, cst)*/
122 
123 int* lookUp(int rst, int cst, int num);
124 
125  
126 
127 void solve(int subt, int num);
128 
129  }

 

其他类的方法设计比较简单,缓冲区采用了栈的设计,参数处理器使用一个方法接受输入参数,并返回一个枚举型变量,文件读写类是对文件操作做了一个简单的封装。

单元测试及性能优化

这部分没有来得及做,确实时间没有安排好,直到周六才开始编码,而且周末组队的事情耽误了一些时间,这个作业其实还是很勉强完成的,之后得补上测试,优化的话可能得结对编程的时候再说了。

代码展示

关键算法

int*  Table::lookUp(int rst, int  cst, int num)
{
    int *result=new int[10];
    int index=0;
    int ron=0,con=0;
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            ron = i + rst * 3;
            con = j + cst * 3;
            if (cells[ron][con] == num)
            {
                return NULL;
            }
            if (cells[ron][con] != 0)
                continue;
            bool pass=true;
            for (int t = 0; t < 9; t++)
            {
                if (cells[ron][t] == num || cells[t][con] == num)
                {
                    pass = false;
                    break;
                }
            }
            if (pass)
            {
                result[index++] = ron*9+con;
            }
        }
    }
//    printf("%d", index);
    result[index] = -1;
    return result;
}
void Table::solve(int subt, int num)
{
    //this function works recursively to fill number 1-9 one by one to all the 9 sub tables

    //subt index subtable ,which is a 3x3 palace. It`s index starts from 0 to 8
    //num (1-9)  is the current  number we try to fiil in  
    
    
 //    printf("%d %d\n", subt, num);
    //if we get enough solutions ,just exit
    if (total == top)
        return;
    //It signals that all numbers are filled.   
    else if (num == 10)
    {
        total += 1;
        pCurrentBuffer->Fill(cells);
        return;
    }
    //we should try the next number while that all palaces are reached
    else  if (subt == 9)
    {
        solve(0, num + 1);
        return;
    }
    //Row or Column of SubTable
    int rst = subt / 3;
    int cst = subt % 3;
    //suitable cells 
    int * suitcells = lookUp(rst, cst, num);
    if (suitcells == NULL)
    {
        solve(subt + 1, num);
        return;
    }
    int index = 0;
    //Row or Column of Number
    int ron = 0;
    int con = 0;
    while (suitcells[index] != -1 && total!=top)
    {
        ron = suitcells[index] / 9;
        con = suitcells[index] % 9;
        cells[ron][con] = num;
        solve(subt + 1, num);
        cells[ron][con] = 0;
        index++;
    }
    delete[] suitcells;
}

 

 

lookUp()函数用于在某一个宫内为一指定的数寻找合适的位置,返回NULL代表该数已存在,空数组代表没有合适的位置等等。

solve()函数递归调用自己,依次填入1-9到九个宫里,直到发现无解或解的个数达到设定值为止。

PSP2.1

Personal Software Process Stages

预估耗时(分钟)

实际耗时(分钟)

Planning

计划

20

15

· Estimate

· 估计这个任务需要多少时间

20

15

Development

开发

665

570

· Analysis

· 需求分析 (包括学习新技术)

200

120

· Design Spec

· 生成设计文档

60

90

· Design Review

· 设计复审 (和同事审核设计文档)

30

0

· Coding Standard

· 代码规范 (为目前的开发制定合适的规范)

5

30

· Design

· 具体设计

100

0

· Coding

· 具体编码

120

300

· Code Review

· 代码复审

30

30

· Test

· 测试(自我测试,修改代码,提交修改)

120

45

Reporting

报告

80

80

· Test Report

· 测试报告

20

10

· Size Measurement

· 计算工作量

20

10

· Postmortem & Process Improvement Plan

· 事后总结, 并提出过程改进计划

40

60

 

合计

765

665

总结:

  这次作业完成的比较失败吧,自己感觉,没有一开始就认真着手这些工作导致时间不充裕,容错率低是一个很大的原因。

  其次是查询资料的环节,由于偷懒没有仔细比较各种算法的优劣,而是快速地凭印象选择,如果不是看到其他人的交流很可能不知道那些比较有效率的算法。

  设计阶段没有很到位,只是花了几分钟画了一个草图,可能之后会表现出来一些问题吧。

  代码复审,嫌麻烦,时间短,没有做,结果在调试上浪费了很多时间在一些小细节上.. 主要的算法明明先用python实现了一遍,几乎一模一样的写到VS里,本以为这块会是出错率最低的地方,实际上花了最多的时间来调。如果仔细审查一遍会好很多吧。

  单元测试..没有做,所以可能还有很多潜在的漏洞。设计的时候就没有设计单元测试,真是一团糟..

  

posted @ 2017-09-26 22:00 RedPotion 阅读(...) 评论(...) 编辑 收藏