【个人项目数独】4、编码

代码编写

经过这几天的努力,代码基本完成,详请参阅GitHub

main函数

main部分的实现较为简单,简单描述一下:
首先,对输入参数不是两个,以及第一个参数不是"-c"或"-s"的,会提示“输入不合法”并结束运行,对于"-c"参数,则首先判断第二个参数是不是数字,然后会判断数字是否在1~1000000之间,若满足要求则调用CreateSudoku进行终局的生成。否则提示输入不合法。
对于"-s"参数,则会尝试打开第二个参数所指的文件,若文件不存在则提示输入不合法,否则调用ReadFile从文件中读取数独进行求解。

CreateSudoku函数

大体思路前面已经描述过,我这里为了节约时间在函数被调用时就将第一行的全排列全部计算出并储存,以便之后调用,由于按要求我的学号产生的第一位是9,所以这里只需要用next_permutation函数产生后续全排列即可。

void Initialize()//预先初始化所有全排列
{
 int init[9] = { 9,1,2,3,4,5,6,7,8 };//第一位按要求取九
 int count = 0;
 do
 {
  for (int i = 0; i < 9; i++)
  {
   src[count][i] = init[i];
  }
  count++;
 } while (next_permutation(init + 1, init + 9));

然后就由上面已保存的第一行依次生成指定数量的数独终局。

 for (int i = 0; i < 40320; i++)
 {
  for (int j = 0; j < 9; j++)//存放数独盘面的第一行
  {
   grid.map[0][j] = src[i][j];
  }
  for (int j = 1; j < 9; j++)//移动生成其余8行
  {
   for (int k = 0; k < 9; k++)
   {
    grid.map[j][k] = grid.map[0][(k + shift[j]) % 9];
   }
  }
  Grid grid_swap;
  for (int j = 0; j < 2; j++)//前三行的两种变换
  {
   for (int jj = 0; jj < 6; jj++)//中间三行的六种变换
   {
    for (int jjj = 0; jjj < 6; jjj++)//后三行的六种变换
    {
     memset(grid_swap.map, 0, sizeof(grid_swap.map));
     if (count < n)
     {
      Swap(j, jj, jjj, grid.map, grid_swap.map);//得到9行的一种变换后的结果
      PrintFile(fp, grid_swap, count);
      count++;
     }
     else if (count == n)
     {
      fclose(fp);
      return;
     }
     else
     {
      fclose(fp);
      cout << "错误!" << endl;
      return;
     }
    }
   }
  }
 }

这个过程中可以先将变换时的顺序索引先保存起来直接调用,可以减少不小的开销。

int shift[9] = { 0,3,6,1,4,7,2,5,8 };//第一行右移生成其余行的位数
int swap0to2[2][3] = { {0,1,2},{0,2,1} };//0~2行的排列索引
int swap3to5[6][3] = { {3,4,5},{3,5,4},{4,3,5},{4,5,3},{5,4,3},{5,3,4} };//3~5行的排列索引
int swap6to8[6][3] = { {6,7,8},{6,8,7},{7,6,8},{7,8,6},{8,7,6},{8,6,7} };//6~7行的排列索

SolveSudoku函数

大体思路前面已经描述过,即采用DFS(回溯法)进行搜索。
为了加快运行速度,这里采用int数的9位分别记录每一行/列/宫的9个数字的出现情况,然后在判断每行每列每宫中一个数是否出现时就可以利用位运算的方法进行判断,速度会快不少。

bool DFS(Grid& grid, int count)//从第一个格开始搜索结果
{
 if (count == 81)
 {
  return true;
 }
 int i = count / 9, j = count % 9;
 if (grid.map[i][j] == 0)
 {
  for (int num = 1; num < 10; num++)//分别尝试1~9
  {
   int valid = (markrow[i] & (1 << num)) || (markcol[j] & (1 << num)) || (markpalace[GetPalace(i, j)] & (1 << num));
   if (valid)//若当前行/列/宫中已有num,则尝试下一个数字
   {
    continue;
   }
   else//填入数字,并标记当前行/列/宫中已有该数字
   {
    grid.map[i][j] = num;
    markrow[i] |= (1 << num);
    markcol[j] |= (1 << num);
    markpalace[GetPalace(i, j)] |= (1 << num);
   }
   if (DFS(grid, count + 1))//继续搜索下一个格,若无结果则回溯
   {
    return true;
   }
   else
   {
    markrow[i] ^= (1 << num);
    markcol[j] ^= (1 << num);
    markpalace[GetPalace(i, j)] ^= (1 << num);
    grid.map[i][j] = 0;
   }
  }
 }
 else//若当前格已有数据则继续搜索下一个
 {
  return DFS(grid, count + 1);
 }
 return false;
}

count代表已搜索的网格数,当达到81时跳出,直接搜索所有的格,若已有数据则搜索下一个,可以避免在每次搜索时都要查找待解元素的位置。

在编码过程中边写边调基本问题已经不大了,考完试后会进行系统的单元测试与代码分析。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值