欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

软件工程基础——个人项目——数独(5)

程序员文章站 2022-06-14 15:30:04
...

软件工程基础——个人项目——数独(5)

生成终局的完善

1.生成终局时,加入2,3;4,5,6;7,8,9行交换的情况以增加不重复的终局数量

原情况下,第2~9行为第一行左移固定数量得到,移动量由数组move数组存储,行交换的实现只需交换对应的移动量。
将move数组设为二维数组,每一行对应一个移动量的情况,共72行。
将原代码中move的使用方式:

num[i][j] = num[0][(j + move[i - 1]) % 9];

转化为:

num[i][j] = num[0][(j + move[moven][i - 1]) % 9];
moven++;
moven %= 72;

其中moven为新设置类的整形私有变量。

2.生成多个终局的实现

从用户处获取生成数量m
对生成单个数独方法(newone函数)进行m次调用
调用时传递参数:生成终局所需全排列数对应的序号x
x的修改应在此排列数所能生成的72种全部输出后
故将newone函数设为int类型,当运行结束时moven若为0则返回0,此时x加1
保证x在范围0~40319中

3.时间复杂度的优化

原代码中排列数存储在硬盘文件中,每次从中取出所需排列数都需要调用一次fopen和fclose函数,此外向文件中写终局时,我所采用的方法同样是在每次写入时调用一次fopen与fclose函数,最终在测试时1分钟的时间大约只能生成5000个左右的终局,而在分析报告中显示与开关文件相关的操作占据了百分之九十多的时间。
对此的问题的优化:
排列数以全局变量的形式存储在主存中,以空间换时间
写入文件仅打开和关闭一次,将文件指针作为参数在各函数间传递
最终相关代码

//排列数的生成
int pailieshu[40320][8];

void pailie() {
 int a[8] = { 1,2,3,4,5,6,8,9 };
 int n = 0;
 do {
  for (int i = 0; i < 8; i++)
   pailieshu[n][i] = a[i];
  n++;
 } while (std::next_permutation(a, a + 8));
 return;
}
//写文件方法
 void write(FILE *fp)
 {
  if (!sum)
  {
   sum++;
  }
  else
  {
   fprintf(fp, "\n");
  }
    for (int i = 0; i < 9; i++)
  {
   for (int j = 0; j < 9; j++)
   {
    if (j == 8)
     fprintf(fp, "%d", num[i][j]);
    else fprintf(fp, "%d ", num[i][j]);
   }
   fprintf(fp, "\n");
  }
}
//生成单个终局方法
 int newone(int n, FILE *fp)
 {
  num[0][0] = 7;
  for (int i = 0; i < 8; i++)
   num[0][i + 1] = pailieshu[n][i];
  for (int i = 1; i < 9; i++)
   for (int j = 0; j < 9; j++)
    num[i][j] = num[0][(j + move[moven][i - 1]) % 9];
  write(fp);
  moven++;
  moven %= 72;
  return moven;
 }
 //主函数中的调用
 int main(int argc,char *argv[])
{
if (!strcmp("-c", argv[1]))
  {
   pailie();
   shudu n;
   int m = atoi(argv[2]);
   int x = 0 ;
   FILE *fp;
   fp = fopen("sudoku.txt", "w");
   while (m--)
   {
    if(!n.newone(x, fp))
    x++;
    x %= 40320;
   }
   fclose(fp);
  }

}

解数独的实现

概要讲述

设置类的私有数据:
num[9][9]保存数独数据信息
left[9][9]每一个数据的后9位保存该数据的可取值
trynum[9][9]保存需填项尝试取的值
judge判断解数独是否结束,用于递归调用继续或返回的标志。
设置私有方法:
writesolve因引入了trynum数组,设置新的写文件方法
bittonum通过对传入的int值后九位进行处理转化为对应1~9值
change对传入数独位置所在行、列、九宫格进行处理,使这些位置的left值与传入的值互斥
restore为change函数的逆操作,将传入值加到获取位置的left值中
standard通过对每一个有确定值或尝试值的位置调用change函数使left值保持正确
solve函数递归调用解数独
设置公有方法:
solveshudu读取数据,调用solve函数解数独,写入数据

详细实现

writesolve函数:对传入的文件进行格式化的写操作,若num不为零则写入num值,否则写入trynum值

 void writesolve(FILE *fp)
 {
  if (!sum)
  {
   sum++;
  }
  else
  {
   fprintf(fp, "\n");
  }
  for (int i = 0; i < 9; i++)
  {
   for (int j = 0; j < 9; j++)
   {
    if (j == 8)
    {
     if (num[i][j] > 0)
      fprintf(fp, "%d", num[i][j]);
     else fprintf(fp, "%d", trynum[i][j]);
    }
    else
    {
     if (num[i][j] > 0)
      fprintf(fp, "%d ", num[i][j]);
     else fprintf(fp, "%d ", trynum[i][j]);
    }
   }
   fprintf(fp, "\n");
  }
 }

bittonum函数:对传入值进行switch判断,返回相应值

int bittonum(int bit)
 {
  switch (bit)
  {
  case 1:
   return 1;
  case 2:
   return 2;
  case 4:
   return 3;
  case 8:
   return 4;
  case 16:
   return 5;
  case 32:
   return 6;
  case 64:
   return 7;
  case 128:
   return 8;
  case 256:
   return 9;
  }
 }

change函数:循环遍历相应位置,所得left值与传入值的非相与

void change(int m, int n, int bit)
 {
  for (int i = 0; i < 9; i++)
  {
   left[i][n] &= ~bit;
   left[m][i] &= ~bit;
  }
  for (int i = m / 3 * 3; i < (m / 3 + 1) * 3; i++)
   for (int j = n / 3 * 3; j < (n / 3 + 1) * 3; j++)
    left[i][j] &= ~bit;
 }

restore函数:循环遍历相应位置,所得left值与传入值相或

void restore(int m, int n, int bit)
 {
  for (int i = 0; i < 9; i++)
  {
   left[i][n] |= bit;
   left[m][i] |= bit;
  }
  for (int i = m / 3 * 3; i < (m / 3 + 1) * 3; i++)
   for (int j = n / 3 * 3; j < (n / 3 + 1) * 3; j++)
    left[i][j] |= bit;
 }

standard函数:循环遍历所有位置,若有确定值或尝试值,则调用change函数,传入值为位置与该位置的确定值或尝试值

在这里插入代码片void standard()
 {
  for (int i = 0; i < 9; i++)
   for (int j = 0; j < 9; j++)
   {
    if (num[i][j] > 0) change(i, j, 1 << (num[i][j] - 1));
    else if (trynum[i][j] > 0) change(i, j, 1 << (trynum[i][j] - 1));
   }
 }

solve函数:
传入参数为当前处理的位置,大小为0~81。
若为81则解数独结束,设置judge为1,返回。
若该位置已有确定值,则参数加一调用下一层递归。
否则,对于该位置的left值进行操作。
取该位置的left值为leftnow。
对leftnow所表示的可取值进行遍历。
设置trynum值为可取值,调用change函数。
递归调用下一位置。
递归结束时若judge仍为0,则将trynum置为0,调用调用restore函数,标准化left数组。否则解数独结束,直接返回。

 void solve(int position)
 {
  if (position == 81)
  {
   judge = 1;
   return;
  }
  int m = position / 9;
  int n = position % 9;
  int bit, leftnow;
  if (num[m][n] > 0)
   solve(position + 1);
  else
   for (leftnow = left[m][n]; leftnow; leftnow &= (leftnow - 1))
   {
    bit = ((~leftnow&(leftnow - 1)) + 1);
    change(m, n, bit);
    trynum[m][n] = bittonum(bit);
    solve(position + 1);
    if (judge) break;
    restore(m, n, bit);
    trynum[m][n] = 0;
    standard();
   }
 }

solveshudu函数:对于要读的文件以81为周期进行读取,直到读到设置的结束标识符-1为止,通过读取值对num,trynum,left数组进行初始化,调用函数实现对数组的标准化和解数独的功能。最后向目标文件中写入内容。

 void solveshudu(FILE *fpr, FILE *fpw)
 {
  while (1)
  {
   judge = 0;
   for (int i = 0; i < 9; i++)
    for (int j = 0; j < 9; j++)
    {
     fscanf_s(fpr, "%d", &num[i][j]);
     if (num[i][j] == -1) return;
     trynum[i][j] = 0;
     left[i][j] = 0x0001ff;
    }
   standard();
   solve(0);
   writesolve(fpw);
  }
 }

main函数中内容:以读的方式打开数独文件,向文件末尾写入标识符-1。关文件。以只读打开数独文件,以只写打开目标文件,调用解数独函数,最后关文件。

github地址:https://github.com/zhuchaojie6/legendary-guide/blob/master/源.cpp

相关标签: 软件开发