蹒跚的第一步

GitHub

https://github.com/JoyJia2/HOMEWORK

PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(小时) 实际耗时(小时)
Planning 计划 1 1
Estimate 估计这个任务需要多少时间 26 35
Development 开发 2 2
Analysis 需求分析 (包括学习新技术) 2 2
Design Spec 生成设计文档 1 3
Design Review 设计复审 2 1
Coding Standard 代码规范 (为目前的开发制定合适的规范) 1 1
Design 具体设计 2 1
Coding 具体编码 5 12
Code Review 代码复审 1 1
Test 测试(自我测试,修改代码,提交修改) 2 3
Reporting 报告 2 2
Test Repor 测试报告 2 3
Size Measurement 计算工作量 2 2
Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 1 1
合计
起初拿到这个题的时候,我笑不出来,大热天的我手脚冰凉,这一个个字分开来我都认得,组成一个题目我却怎么都看不透—-“命令行传参数”“八宫格”“性能分析”……. 想了一下退学已经来不及了,那只好硬着头皮先做一做。

Sudoku1:完全遍历,暴力解答

就程序而言,我简单理解为三个部分:
文件(input.txt)读入=>处理=>写入文件(output.txt)
查阅了一下文献,写入和写出使用了简单的fostream和instream,这个还是比较好解决的。重头戏是处理,这里我犯了一个很严重的错误,这个错误也花费了我很多很多时间——我使用的最基本的思路。
阶段一:错误的出发点
我最初的思路将整个棋盘的每个子理解为一个结构体node,

struct node{
 int pb[9]={1,2,3,4,5,6,7,8,9};
 int part;
 int num;
};

其中pb数组表示该位置可能的取值,可以直接默认为1-9,(在各阶宫中阶数会限制访问,这样n宫格的每个位置的可能性都是1-n),part用于判断四,六,八,九宫格的各个宫内进行判定,num则用来保存已经确定下的值。这样一来,就可以用最基本的排除法思路来做了。
需要一个 int solo(int *a)函数用于判断数组中是否只存在唯一的一个值,如果是则返回它,否则返回0。

int solo(int *a)
{
 int temp=0,save=0;
 for(int i=0;i<n;i++)if(a[i]!=0)
 {
 temp++;
 save=a[i]; 
 }
if(temp==1)return save; 
 else return 0;
}

需要一个 void kill(int a,int *b)函数用于已知数值缩小同行或者同列其他位置的取值可能性。(将排除掉的值设为0)

void kill(int a,int *b)
{
 for(int i=0;i<n;i++)if(b[i]==a)b[i]=0;}

紧接着就是主要的handle函数,其用途是遍历结构体数组(即所有位置),逐个得到排除后的结果,并且将第一次产生的结果用于第二次遍历,如此循环直到某一次没有发生如何改动,跳出循环。

截至目前,函数已经可以处理三,五阶的宫格,但是再提高阶数就不能完成,即便接下来的部分有使用逐个假设的方法(代码不再列出)。

Sudoku2:递归回溯,清晰优雅

穷则思变,在下一步前查阅了资料,发现这种问题最好是用递归回溯方法,对比之下,顿时感觉到了算法的无穷魅力,于是基本上完全改变了我之前的代码。我两天的思考无疾而终,而这无疾而终的思考又让我产生了新的思考----闭门造车要不得,计划规划要完备。

#include<fstream>
using namespace std;
int Sudoku[9][9];


bool compare(int num,int ch_nowline,int ch_nowcolumn,
    int ch_blockline,int ch_blockcolumn)
{
    for(int i=0;i<=m-1;++i)
    {
        if(Sudoku[ch_nowline][i]==num||Sudoku[i][ch_nowcolumn]==num) 
            return false;
    }
    if(m==9)for(int i=0;i<=2;++i)//宫内检测
    {
        for(int j=0;j<=2;++j)
        {
            if(Sudoku[ch_blockline+i][ch_blockcolumn+j]==num)
                return false;
        }
    }
    if(m==8)for(int i=0;i<=3;++i)
    {
    	for(int j=0;j<=1;j++)
    	{
    		if(Sudoku[ch_blockline+i][ch_blockcolumn+j]==num)
    			return false;
		}
	}
	if(m==6)for(int i=0;i<=1;++i)
    {
    	for(int j=0;j<=2;j++)
    	{
    		if(Sudoku[ch_blockline+i][ch_blockcolumn+j])
    			return false;
		}
	}
	if(m==4)for(int i=0;i<=1;++i)
    {
    	for(int j=0;j<=1;j++)
    	{
    		if(Sudoku[ch_blockline+i][ch_blockcolumn+j])
    			return false;
		}
	}
    return true;
}

接下来是核心函数

bool work(int now_line,int now_column)//主要的处理函数
{
    if(now_line==m)
    {
        return true;//如果将数独解完,返回true 
    }
    else 
    {
        int next_line,next_column,block_line,block_column;
        next_column=now_column+1;
        next_line=(next_column>=m?now_line+1:now_line);
        next_column=(next_column>=m?0:next_column);
        if(Sudoku[now_line][now_column]!=0)//如果当前坐标有数字,则对下一个坐标进行工作 
        {
            if(work(next_line,next_column)) return true;//如果数独最终有解,则不断向前返回true 
        }
        else
        {
            if(m==9)
			{
			block_line=(now_line/3)*3;//计算所在的3*3方格左上角坐标
            block_column=(now_column/3)*3;
			}
			if(m==8)
			{
			block_line=(now_line/4)*4;//计算所在的4*2方格左上角坐标
            block_column=(now_column/2)*2;
			}
			if(m==6)
			{
			block_line=(now_line/2)*2;//计算所在的2*3方格左上角坐标
            block_column=(now_column/3)*3;
			}
			if(m==4)
			{
			block_line=(now_line/2)*2;//计算所在的2*2方格左上角坐标
            block_column=(now_column/2)*2;
			}
            for(int i=1;i<=m;++i)
            {
                if(compare(i,now_line,now_column,block_line,block_column))
                {
                    Sudoku[now_line][now_column]=i;
                    if(work(next_line,next_column)) return true;
                }
            }
            Sudoku[now_line][now_column]=0;//回溯操作 
            return false;
        }
    }
}
void Input(int t,int *save)//将待测数据拷贝进特定数组
{
    for(int i=0;i<=m-1;++i)
    {
        for(int j=0;j<=m-1;++j)
        {
            Sudoku[i][j]=save[t*m*m+i*m+j];
        }
    }
}
void Output(int t,int *save)//将结果拷贝到输出特定数组
{
    for(int i=0;i<=m-1;++i)
    {
        for(int j=0;j<=m-1;++j)
        {
            save[t*m*m+i*m+j]=Sudoku[i][j];
        }
    }
}

现在开始实现主函数,主函数包括:

1.实现命令行参数读入。
2.实现文件导出数据(多说一句这里直接设置了一个长度500的字符数组来接收,本来可以完美运用内存,奈何vs2019不支持数组内为变量,这个故事告诉我们平时尽量使用正规编译器,dev c++还是有一定不足的)
3.每个棋盘依此处理,如何存进数组。
4.实现数据写入文件。

int main(int argc,char *argv[])
{
	ifstream infile;
	ofstream outfile;
	m=argv[2][0]-48;
	n=argv[4][0]-48;//获取宫格数和待测棋盘数
	int save[500],cer=0;
	infile.open(argv[6]);
	char ch;
   while(!infile.eof())
    {
       infile.get(ch);
       if('0'<=ch&&ch<='9')save[cer++]=ch-48;
    }
    infile.close();
    for(int t=0;t<n;t++)
    {
    	Input(t,save);
    	if(work(0,0));
    //	else cout<<"wrong!"<<endl;
		Output(t,save);
	}
    outfile.open(argv[8]);//开始写入 
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			for(int k=0;k<m;k++)
			{
				outfile<<save[i*m*m+j*m+k]<<"  ";
			}
			outfile<<endl;
		}
		outfile<<endl;
	}
	outfile.close();
	return 0;
 } 

测试结果

###结果分析 ###心得体会 #总结下来就是要条理清晰,“要简单不要复杂,要优雅不要丑陋”,这不仅仅是建议,是血泪教训后得的经验。对于我这个粗心的毛躁年轻人来说,平时小的项目可能自己抖抖机灵就行,但是小聪明难担大项目,无论是时间安排或者是项目规划我都抱着短时间完成的想法来浪费了更多的时间,今后我要尽力减少至避免这种情况的出现。
原文地址:https://www.cnblogs.com/highwaytohell/p/11542455.html