zoukankan      html  css  js  c++  java
  • 第二次作业——个人项目实战

    第二次作业——个人项目实战

    Github项目地址
    作业地址

    项目相关要求

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

    输入

    数独棋盘题目个数N

    输出

    随机生成N个 不重复 的 已解答完毕的 数独棋盘,并输出到sudoku.txt中,输出格式见下输出示例。
    在生成数独矩阵时,左上角的第一个数为:(学号后两位相加)% 9 + 1。例如学生A学号后2位是80,则该数字为(8+0)% 9 + 1 = 9,那么生成的数独棋盘应如下(x表示满足数独规则的任意数字):

    输入示例

    sudoku.exe -c 1
    

    输出示例

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

    PSP表格

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

    解题思路

    解题心理历程按照时间顺序分以下几个阶段

    • 刚拿到题目,自然想到暴力解决,递归回溯,深度优先,很开心得写好了最初的核心算法代码。
    • 接下来对写txt、参数的使用不了解,于是开始百度材料,另外新建立了一个项目,在新建的项目上测验写txt和参数的传递,对写txt和参数的使用有了一定的了解后,进一步修改了代码,随便又花了些时间实现了一下随机的功能。
    • 虽然自己的代码运行能得到结果,但是当N=1000000的时候,运行起来就变得非常慢了。不得不上网找找资料,查查别人是怎么实现数独的,花了挺多时间去寻找更加有效率的算法,最后挑选了行行变换、列列变换(矩阵变换法)。
    • 先是推理了一下为什么矩阵变换法的原理,最后发现貌似如果第一个数字固定生成的个数会不够多呀。矩虽然阵变换法快,但数量有限,回溯慢,但生成的数量可以非常非常多。
    • 想来想去,最后决定把自己之前敲的回溯与矩阵变换结合一下。而且两种方法各自都能保证生成数独唯一性。两种方法的结合,控制一下,也便能保证整体生成数独的唯一性。

    设计实现

    一共2个类

    • class Program //包含主函数
    • public class Creator //生成数独

    一共7个函数:

    • void Main(string[] args) //主函数用来接收参数,并判断参数是否符合要求
    • public Creator(int temp) //构造函数,用来初始化数据,并接收主函数传递的参数
    • private void Begin() //开始寻找数独,先矩阵变换得到数独,再判断数量是否满足需求,不满足调用递归回溯函数
    • private void FindFirst(int p) //寻找第一个小九宫格,先生成第一个小九宫格(9个数字)
    • private void FindAll() //根据第一个九宫格,进行行行变换,列列变换,生成完整的数独
    • private void Dfs(int p) //递归回溯,生成完整的数独
    • private void Write() //把数独写入到txt文件

    代码说明

    快速生成,矩阵变换

    生成第一个小九宫格:

    private char[,] a = new char[9, 9];             //二维数组,用来存放整个数独的每一个数
    private List<char> mylist = new List<char>();   //字符型List在函数begin()中初始化,使list包含字符1、2、3、5、6、7、8、9 (4为每个数独的首个,无需在List中)
    
    private void FindFirst(int p)                    //寻找第一个小九宫格
    {
    	//采用递归回溯生成首个小九宫格
    	int x, y, i, j, rand;
    	if (count == over) return;
    	if (p == 10)
    	{
    		FindAll();                          //成功生成首个小九宫格,调用FindAll()生成完整的数独
    		return;
    	}
    	x = (p - 1) % 3;
    	y = (p - 1) / 3;
    	rand = re.Next(1, 100);                 //随机生成一个数字为下面循环做准备
    	for (j = 0; j < mylist.Count(); j++)
    	{
    		i = (j + rand) % mylist.Count();    //使得首次循环i不一定都是从1开始循环,可以从2到最后一个中的某个数字开始循环,如循环次序:i= 5,6,7,8,1,2,3,4  实现一定的随机性
    		a[x, y] = mylist[i];
    		mylist.RemoveAt(i);                  
    		FindFirst(p + 1);
    		mylist.Insert(i, a[x, y]);
    	}
    }
    

    矩阵变化的方法原理,参照这位同学,但是值得一提的是,每次生成下个小九宫格的时候有两种情况:

    private void FindAll()                           //找到剩下的8个九宫格
    {
    	//采用行行变换,列列变换
    	int i,q,w,e,r;
    	if (count == over) return;
    	for (q = 0; q < 2; q++)  //两种情况
    	{
    		//第2、3个九宫格变换
    		for (i = 0; i < 3; i++)
    		{
    			...//代码类似第6、9个九宫格的变换,此处不再给出代码
    		}
    		for (w = 0; w < 2; w++)  //两种情况
    		{
    			//第4、7个九宫格变换
    			for (i = 0; i < 3; i++)
    			{
    				...//代码类似第6、9个九宫格的变换,此处不再给出代码
    			}
    			for (e = 0; e < 2; e++)  //两种情况
    			{
    				//第5、8个九宫格变换
    				for (i = 0; i < 3; i++)
    				{
    					...//代码类似第6、9个九宫格的变换,此处不再给出代码
    				}
    				for (r = 0; r < 2; r++)  //两种情况
    				{
    					//第6、9个九宫格变换
    					for (i = 0; i < 3; i++)
    					{
    						a[X3 + 0, Y2 + i] = a[X3 + ((r == 0) ? 2 : 1), Y1 + i];         //6
    						a[X3 + 1, Y2 + i] = a[X3 + ((r == 0) ? 0 : 2), Y1 + i];         //6
    						a[X3 + 2, Y2 + i] = a[X3 + ((r == 0) ? 1 : 0), Y1 + i];         //6
    						a[X3 + 0, Y3 + i] = a[X3 + ((r == 0) ? 1 : 2), Y1 + i];         //9
    						a[X3 + 1, Y3 + i] = a[X3 + ((r == 0) ? 2 : 0), Y1 + i];         //9
    						a[X3 + 2, Y3 + i] = a[X3 + ((r == 0) ? 0 : 1), Y1 + i];         //9
    					}
    					if (count == over) return;
    					if (count < FAST_MAX) Write();  //理论上有645120个数独,为了使用回溯时避免重复,最后一组16个数独不用
    					if (count == over) return;
    				}
    				if (count == over) return;
    			}
    			if (count == over) return;
    		}
    		if (count == over) return;
    	}
    }
    

    继续生成,递归回溯

    我们知道,如果利用矩阵变换,一共可以生成645120(8!x2x2x2x2)个数独,每16个的第一个小九宫格是一样的,为了避免重复,我们将最后的16个不在矩阵变换算法中写txt,我们利用最后这一组数独来生成递归,在递归中写txt,这样生成的数独剧不会重复了。

    private void Dfs(int p)                         //回溯寻找数独
    {
    	int x, y, i, j, temp,rand;
    	if (p == 1 || p == 2 || p == 3 || p == 10 || p == 11 || p == 12 || p == 19 || p == 20 || p == 21)         //防止破坏第一个小九宫格
    	{
    		Dfs(p + 1);
    		return;
    	}
    	if (count == over)  return;                 //寻找到足够的数独,return
    	if (p == 82)                                //找到一个数独,写txt 
    	{
    		Write();
    		return;
    	}
    	x = (p - 1) % 9;
    	y = (p - 1) / 9;
    	rand = re.Next(1, 100);
    	for (j = 1; j < 10; j++)
    	{
    		//其中flag_hang、flag_lie、flag_jiu是用来记录某行、列、九宫格出现数字,检测是否i符合要求
    		i = (rand + j) % 9 + 1;
    		if (flag_hang[y, i] == 1) continue;     
    		if (flag_lie[x, i] == 1) continue;
    		temp = (x / 3) + (y / 3) * 3;
    		if (flag_jiu[temp, i] == 1) continue;
    		else
    		{
    			a[x, y] = (char)i;
    			a[x, y] += '0';
    			flag_lie[x, i] = 1;
    			flag_hang[y, i] = 1;
    			flag_jiu[temp, i] = 1;
    			Dfs(p + 1);                   //i放在p处,满足要求,寻找p+1
    			flag_lie[x, i] = 0;           //回溯
    			flag_hang[y, i] = 0;
    			flag_jiu[temp, i] = 0;
    		}
    	}
    }
    

    测试运行

    运行测试结果如下截图:


    性能分析

    改进前

    从性能分析的截图上可以看出,Wirte的效率太低下了,花了好长的时间。

    改进后

    从朋友口中得知,原来使用stream的时候输出字符类型会快很多,于是对代码做了大幅度的更改,将byte型的数组,改为了char型的数组,果然效果很明显。

    感想

    • 想和做差距是很大的,大家都很有想法,查完资料后更有想法,但是正真要做,总是会遇到的很多困难,泛泛而谈大部分人都会,我们不能养成那种说比做的还好听的坏习惯,有想法很好,但是同时也要有执行力。我们要去实现自己的idea,这样才能提高我们得执行力。idea只有变成了显示,它的价值才真正得被体现。
    • 在整个过程中还是遇到一些困难的:
      • 在每次修改完代码后,总是得找bug,找得很累呀。有时候经常找不到bug,这种时候,只能通过调试,写一些控制台输出变量来找bug。
      • 单元测试遇到的麻烦真心大,不懂得怎么写单元测试的代码,翻看了一些网上的资料,最后勉强写了一点测试自己的程序。
  • 相关阅读:
    2-5-归并链式存储的单链表-线性表-第2章-《数据结构》课本源码-严蔚敏吴伟民版
    2-4-单链表链式存储结构-线性表-第2章-《数据结构》课本源码-严蔚敏吴伟民版
    2-3-归并单链表(顺序表)-线性表-第2章-《数据结构》课本源码-严蔚敏吴伟民版
    2-2-求并集A=A∪B-线性表-第2章-《数据结构》课本源码-严蔚敏吴伟民版
    2-1-单链表顺序存储结构-线性表-第2章-《数据结构》课本源码-严蔚敏吴伟民版
    线性表-第2章-《数据结构题集》习题解析-严蔚敏吴伟民版
    绪论-第1章-《数据结构题集》习题解析-严蔚敏吴伟民版
    1-1-绪论-第1章-《数据结构》课本源码-严蔚敏吴伟民版
    【十大经典数据挖掘算法】PageRank
    灵活可扩展的工作流管理平台Airflow
  • 原文地址:https://www.cnblogs.com/pengpeng123/p/7502049.html
Copyright © 2011-2022 走看看