软件工程2017第二次做业

GitHub:sudoku

解题思路描述

刚看到题目的时候,我去,好难。吃了根冰棍冷静下来,开始细细思考。题目的要求是随机生成N个不重复的数独棋盘,有两种方案:1.用数字1~9填满第一个九宫格,而后再去填下一个九宫格,直到九个九宫格都填满,而且不会每一行每一列不会有相同的数字。2.用数字1填第一个九宫格,而后再填第二个.......直到九个九宫格都填入了1,再把数字变成2,再一个个地去填九宫格,以此类推,直到9个数字都填入。我认为第2种方案实现起来会更容易一些,所以决定采用第二种方案。
接下来即是肯定数据结构和方法了。若是单纯采用二维数组来实现递归的话,我不知道该如何表示已经遍历过的格子。所以我决定采用链表加上二维数组的方式来实现数独棋盘的生成。
具体的方法以下:每一个九宫格作一个含有九个结点的链表,Grid[g]存储第g个九宫格链表的首结点,blocks[g]用来表示第g个九宫格中空闲位置的数量(不包括已经尝试过的结点),递归函数PutNum和GetRandomValue互相配合往合适位置填入数字,当一个九宫格中没法填入数字,则向上一个宫返回false,若是能够则继续往下一个九宫格填数字。若是每次都是用这种递归方式随机生成数独棋盘,这样子效率过低,因而我想到一个方法,每随机生成一个数独棋盘以后,能够调换数字,这样就又成了一个新的数独棋盘,考虑到左上角的数字是固定的,因此这种换数字大法能够在一个随机数独棋盘的基础上生成40320种不一样的棋盘。要实现这种换数字大法,就必须获取每种全排列的顺序,我使用permutation函数来生成全排列并将获取到的数据填入arr2中。git

设计实现

代码中的全局变量弄得有点多,虽然我知道这样很差,可是不这样弄得话又感受很不方便。代码中共有6个函数:BuildLinkedList()是用来创建存储坐标的链表;GetRandomValue(short g)是用来在第g个九宫格中放置数字num,当没有合法位置放置时,返回false;PutNum(short g)是用来递归调用的函数,该函数调用GetRandomValue来放置数字num;ShowSudoku()是用来将数独棋盘输出到文本文件中的函数;Permutation(short length)是用来产生全排列数组的函数;Clean()函数是当没法生成数独棋盘的时候,对一些动态变量进行清理,防止内存泄漏。github

代码说明

这个是用来在九宫格中随机选取空闲可用位置的函数算法

bool GetRandomValue(short g)//在第g个九宫格中随机选取可用的位置来放入数字
{
    if (blocks[g] == 0)return false;
    int value;
    value = rand() % blocks[g];//生成随机数
    int i;
    Node *p1, *p2;
    for (i = 0, p2 = Grid[g], p1 = p2; i < 2 * blocks[g] - 1; i++)//p2即为可放置数字的位置坐标
    {
        if (i >= value&&row_flag[p2->row] == false && column_flag[p2->column] == false)break;
        if (i == blocks[g] - 1)p2 = Grid[g], p1 = p2;
        else
        {
            p1 = p2;
            p2 = p2->next;
        }
    }
    if (i == 2 * blocks[g] - 1)return false;
    sudoku[p2->row][p2->column] = num;//接下来的代码是对被选中的目标位置结点进行删除前的准备操做
    numlocation[g] = 3 * (p2->row % 3) + (p2->column % 3);
    row_flag[p2->row] = true;
    column_flag[p2->column] = true;
    if (p2 == Grid[g])Grid[g] = p2->next;
    if (p2 == LinkedListTail[g])LinkedListTail[g] = p1, p1->next = NULL;
    if (p2 != Grid[g] && p2 != LinkedListTail[g])p1->next = p2->next;
    delete p2;
    blocks[g] = blocks[g] - 1;
    return true;
}

调用GetRandomValue函数在每一个宫内放置数字的函数PutNum数组

bool PutNum(short g)//在每一个九宫格中放入相应的数字num
{
    for (;;)
    {
        if (GetRandomValue(g) == true)
        {
            if (g == 8)return true;
            else if (PutNum(g + 1) == false)//若是PutNum(g+1)返回false,则说明第g+1个宫没法放置数字,则在第g个宫尝试能够放置的其余位置,已经尝试过的位置结点则放置到链表后面
            {
                Node *p = new Node;
                p->row = 3 * (g / 3) + numlocation[g] / 3;
                p->column = 3 * (g % 3) + numlocation[g] % 3;
                p->next = NULL;
                LinkedListTail[g]->next = p;
                LinkedListTail[g] = p;
                sudoku[p->row][p->column] = 0;
                row_flag[p->row] = false;
                column_flag[p->column] = false;
                continue;
            }
            else
            {
                blocks[g] = 9 - num;
                return true;
            }
        }
        else
        {
            blocks[g] = 10 - num;
            return false;
        }

    }
}

生成全排列并存放在arr2数组中的函数Permutation数据结构

void Permutation(short length)//用递归的方法在arr2数组中生成全排列
{
    int i;
    if (length == 10 - sudoku[0][0])
    {
        if (length != 1)Permutation(length - 1);
        else
            ShowSudoku();
        return;
    }
    for (i = 0; i<9 && stop_flag; i++)
    {
        if (arr1[i] == 0)
        {
            arr1[i] = 1;
            arr2[9 - length] = i + 1;
            if (length != 1)Permutation(length - 1);
            else
                ShowSudoku();
            arr1[i] = 0;
        }
    }
}

将数独棋盘输出到文本文件中dom

void ShowSudoku()//将数独棋盘输出到文本文件中
{

    int row, column;
    for (row = 0; row < 9; row++)
    {
        for (column = 0; column < 8; column++)
        {
            fcout << arr2[sudoku[row][column] - 1] << " ";//将随机生成的数独棋盘映射到arr2数组中
        }
        fcout << arr2[sudoku[row][column] - 1] << endl;
    }
    if (--sudoku_count == 0)stop_flag = false;
    else
        fcout << endl;
}

测试运行

测试运行的截图

函数

效能分析与改进

分析时生成的数独棋盘个数设为50000个


学习

使用链表来进行生成数独棋盘真的很费时,效率不高,并且又占用空间。若是时间容许的话,我想不用链表来作,可是不用链表如何表示已经遍历过的位置对我来讲是个问题,写完这篇博客准备去研究一下。测试

PSP表格

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

我的总结

第二次做业对我我的来说难度仍是很大的,此次代码用的数据结构也不是很好,处理链表带来的时间和空间开销都比较大。生成一个数独棋盘后替换数字又生成了另一个棋盘是一个比较取巧的办法,时间开销比递归生成数独棋盘所用的时间要小,所以我采用了递归生成数独和换数字相结合的方式。还有就是这个完成这个做业的耗时远远在个人意料以外,花的时间实在是太长了,主要是前期规划不怎么好,致使编码的时候bug一大堆,很容易就中止运行了,也算是吸收一个教训了。最后一个就是本身的算法功底太薄弱了,代码也写的比较臃肿,新的学期要好好学习算法和代码的优化方法。优化

更新

得老师指点,将编译模式改成RELEASE模式,时间损耗是原来的四分之一,速度获得极大提高,所以对“效能分析与改进”板块进行修改,用RELEASE版的截图覆盖了原来的DEBUG版截图,并将RELEASE版的程序更新到GitHub上。 突然发现若是将srand(time(0))放到递归函数中产生的随机数在短期内会相等,而采用clock()函数作种子又会形成运行两次生成的2个文本文件有必定几率相等,而后尝试把srand(time(0))放到主函数中只调用一次,rand()函数放到递归函数中调用则产生的随机数即便在短期内也不会相等,所以修改了随机数产生的代码,并将新的cpp和exe文件上传到GitHub上。

相关文章
相关标签/搜索