专栏名称: 生信媛
生信媛,从1人分享,到8人同行。坚持分享生信入门方法与课程,持续记录生信相关的分析pipeline, python和R在生物信息学中的利用。内容涵盖服务器使用、基因组转录组分析以及群体遗传。
目录
相关文章推荐
51好读  ›  专栏  ›  生信媛

写个程序帮我玩游戏(数独)

生信媛  · 公众号  · 生物  · 2020-01-29 17:29

正文

请到「今天看啥」查看全文


由于不可抗力的因素,导致今年(2020)的假期格外的长,毕竟还只能蜗居度过。在家里消磨时光有很多方法,比如说看剧,看书,玩游戏等。

说到玩游戏,我就想到了我初中时候看杂志时,里面会有一类题目,叫做数独。

数独游戏的目标是用数字填充9x9的宫格,让每一列,每一行和每个3x3小九宫部分都包含1到9之间的数字。在游戏开始时,9x9的宫格中会有一些方格已填上数字。你要做的是运用逻辑来填上缺失的数字并完成宫格。不要忘记,如果出现以下情况,表示填法不正确

  • 任意一行中,有多个相同的1到9中的数字

  • 任意一列中,有多个相同的1到9中的数字

  • 任意一个3x3小宫格中,有多个相同的1到9中的数

那个时候,拿着铅笔和橡皮擦玩,一个谜题能把一个晚自修给干掉。

最近在家学习算法,学到了回溯算法这一部分,里面就提到了回溯算法的使用范围为,在一组可能的解中,搜索满足期望的解。课程介绍了回溯算法处理八皇后问题,把数独问题留作了练习题。我就用C语言写了相应的代码。

代码的核心是写一个递归函数,用于分步求解当前值是否满足要求,如果满足需求就进入下一阶段,如果下一个阶段在前一阶段基础上无解,就会回到上一阶段,换另一个值进行尝试。

按照这个思想我写了第一版代码

  1. void

  2. calcSudoku( int (*arr)[9], int (*arr2)[9], int pos, int value ){


  3. if ( pos == 81){

  4. printSudoku(arr);

  5. return ;

  6. }


  7. int row = pos / 9;

  8. int col = pos % 9;


  9. int val;

  10. for ( val = 1; val < 10; val++){

  11. // 如果为0, 说明此处可以进行修改

  12. if ( arr2[row][col] == 0 ){

  13. if ( isValidStep(arr, row, col, val ) ){

  14. arr[row][col] = val;

  15. calcSudoku( arr, arr2, pos + 1, val );

  16. }

  17. } else{

  18. // 当前记录如果不为0, 说明此处是已填写区域

  19. // 跳过这个地方

  20. calcSudoku ( arr, arr2, pos + 1, val );

  21. }

  22. }

  23. } for ( val = 1; val < 10; val++){

  24. // 如果为0, 说明此处可以进行修改

  25. if ( arr2[row][col] == 0 ){

  26. if ( isValidStep(arr, row, col, val ) ){

  27. arr[row][col] = val;

  28. calcSudoku( arr, arr2, pos + 1, val );

  29. printSudoku (arr);

  30. }

  31. } else{

  32. // 当前记录如果不为0, 说明此处是已填写区域

  33. // 跳过这个地方

  34. calcSudoku( arr, arr2, pos + 1, val );

  35. }

  36. }

结果代码会陷入一个死循环。我在代码中各种加printf来想办法找到可能的问题,然后还和之前的八皇后问题进行了比较,最终和别人的java代码做了比较之后,才发现代码的问题

  1. 原本是提供arr2作为原来数组的拷贝,用于记录哪里为0,想避免修改原来为不为0的地方, 但对于正确的回溯代码,递归记录了路径,因此在回溯的过程中,可以重设原来的状态。

  2. 判断当前值是否为0,应该是在for循环之外,可以避免不必要的计算

  3. 缺少重设原来状态的代码,导致回退之后,还是之前的状态。

经过我的修改,下面的代码才是真正能用的

  1. void

  2. calcSudoku( int (*metrics)[9], int pos, int value ){


  3. if ( pos == 81 ){

  4. printSudoku(metrics);

  5. return ;

  6. }



  7. int row = pos / 9;

  8. int col = pos % 9;


  9. int val;

  10. // 如果为0, 说明此处可以进行修改

  11. if ( metrics[row][col] == 0 ){

  12. for ( val = 1; val < 10; val++){


  13. if ( isValidStep(metrics, row, col, val ) ){ // 如果能够设置当前值

  14. metrics[row][col] = val;

  15. calcSudoku( metrics, pos + 1, val ); // 前进到下一步

  16. }

  17. //从下一状态回退到当前值时,则重设当前值


  18. metrics[row][col] = 0;

  19. }

  20. } else{

  21. // 当前记录如果不为0, 说明此处是已填写区域

  22. // 跳过这个地方

  23. calcSudoku( metrics, pos + 1, val );

  24. }

  25. }

最终版代码在GitHub上, https://github.com/xuzhougeng/learn-algo/blob/master/sudoku.c

使用方法,把之前的问题输入到一个文本中,以空格作为分隔符

  1. 0 0 9 4 2 0 0 6 0

  2. 0 7 0 9 0 5 3 0 2

  3. 5 0 0 0 0 3 0 9 0

  4. 0 0 0 8 0 1 0 2 0

  5. 2 6 0 0 0 0 0 5 1

  6. 0 1 8 2 0 0 4 0 0

  7. 3 8 0 0 0 4 0 1 9

  8. 0 9 4 0 3 0 6 8 5

  9. 0 2 1 0 0 8 0 3 0

然后运行代码,就会得到答案。

  1. $ make sudoku

  2. $./sudoku q.txt

  3. 0 0 9 4 2 0 0 6 0

  4. 0 7 0 9 0 5 3 0 2

  5. 5 0 0 0 0 3 0 9 0

  6. 0 0 0 8 0 1 0 2 0

  7. 2 6 0 0 0 0 0 5 1

  8. 0 1 8 2 0 0 4 0 0

  9. 3 8 0 0 0 4 0 1 9

  10. 0 9 4 0 3 0 6 8 5

  11. 0 2 1 0 0 8 0 3 0


  12. 1 3 9 4 2 7 5 6 8

  13. 8 7 6 9 1 5 3 4 2

  14. 5 4 2 6 8 3 1 9 7

  15. 4 5 3 8 7 1 9 2 6

  16. 2 6 7 3 4 9 8 5 1

  17. 9 1 8 2 5 6 4 7 3

  18. 3 8 5 7 6 4 2 1 9

  19. 7 9 4 1 3 2 6 8 5

  20. 6 2 1 5 9 8 7 3 4


最后发现,用C语言写代码,很容易随随便便就写出100多行,不过写得多了,才发现在算法上的思考,以及对错误的排查才是最要时间的。


推荐阅读

这一次我要真正学会C语言

C语言实战课-写一个fastq转fasta的程序

C语言实战-让程序能够处理压缩文件







请到「今天看啥」查看全文