数独生成算法

问题描述:

数独是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个九宫格内的数字均含1-9,不重复

要求:设计算法随机生成不同难度的数独游戏,阐述如何评价所生成数独的难度。

我的分析思路:

对于数独程序,主要需要解决两个问题:数独生成、数独求解。目前影响比较大,功能比较完善的数独程序有:Hodoku、Sudoku Explainer。

要实现数独的生成,就要先理清每一个位置的约束条件,每个位置的数需要满足四个约束条件:

首先,这个格子里不能有数字,即为空

其次,同一行不能有相同数字

再次,同一列不能有相同数字

最后,同一个九宫格不能有相同数字

用算法的形式把这四个约束条件表现出来,就是数独的生成。

计算机程序求解数独,一般采用回溯法,对于任意数独初盘,好的算法都可以在一秒内得到解。目前,非常有效的算法,是舞蹈链(Dancing Links)算法。它实际上也是一种回溯算法,巧妙地运用了双向十字链表的数据结构,用空间换取时间,将数独求解转化为一个精确覆盖问题,用C语言实现的算法,在普通的微机上,能够在0.1ms左右对任意标准数独进行求解。

大致步骤:

数独求解:

首先,要先确定该数独中空格的数量以及位置,用count记录空格的数量,用blank数组记录所有需要填的空格的坐标。

然后,定义一个初始坐标top,使初始坐标top=0,利用一个双循环求解。

进行第二步后,会有一部分空格需要填的数不唯一,因此需要进行试填,从而使数独能更好的解决。定义a,b记录当前top指向的元素作为当前试填空格的坐标,利用循环从1-9依次试填并检验当前空格内填入的数字是否可行,我们定义一个变量ok表示当前空格内填入的数字是否可行,当ok=1时表示可行,ok=0时表示不可行。如果填入的数字可行,top+1进行下一个空格的试填,最终求解。

#include <bits/stdc++.h>
    
bool fill(int sd[][9])
{
    int count = 0;   //count用于记录需要填的空格数量,初始为0
    int blank[81][2];    //blank数组用于记录所有需要填的空格的坐标           
 
    for(int i = 0;i < 9;i++)                     
        for(int j = 0;j < 9;j++)                //  
            if(sd[i][j] == 0)                       // 
            {                                             //     将整个数独扫描一遍,
                blank[count][0]=i;             //     统计其中的空格数量
                blank[count][1]=j;             //  
                count++;                           //
        }
    int top = 0;          //top指向blank数组元素,表示当前待填坐标。初始为0
    while(top != -1)   //最外层循环,每填一个空格,top加1,每回退一步,top减1,如果top==-1,循环结束                 
    {
        if(top == count)   //因为每填完一个空格后top加1,填最后一个空格时top==count-1,填完后top加1即等于count
        {
   			printf("最终结果为:\n");
            for(int s = 0;s < 9;s++)                         //
            {                                                            //
                for(int t=0;t<9;t++)                          //
                {                                                        //     输出一个解
                    printf("%d  ",sd[s][t]);                   //
                }                                                        //
                printf("\n");                                       //
            }                                                            
                top--;                          //如果找到一个解,回退一步,找其他解
                continue;                    //只要发生回退,就回到循环开头测试循环条件top != -1,不会往下执行
        }
        int a = blank[top][0];        //如果执行到这里,说明还有空格要填, 
        int b = blank[top][1];        //用a,b记录下top指向的元素作为当前试填空格的坐标
        int n = sd[a][b];             //用n记录当前空格中的数字,这个数字作为试填的“进度”,从这个数字加1往下试填
        for(n++;n <= 9;n++)     //将n加1后,一直循环到9进行试填
        {
            int ok = 1;                  //ok=1标志当前数字可行
            for(int k = 0;k < 9;k++)   //检测同行,同列,同块的数字是否有相同的
            {
                if(sd[a][k] == n || sd[k][b]== n || sd[(a / 3) * 3 + k / 3][(b/ 3) * 3 + k % 3] == n )  //同行,同列,同块的数字一起检测
                {
                    ok = 0;  //如果同行,同列,同块某一处有相同数字,则ok=0,表示当前数字不可行
                    break;    //不再往下检测,跳出此层循环,继续试下一个数字
                }
            }
            if(ok) break;    //如果ok保持为1,说明当前数字可行(否则肯定会被修改为0),跳出循环,不再往下试
        }
        if(n <= 9)   // 如果有一个数字可行,n<=9。否则,n=10 
        {
            sd[a][b] = n;  //将这个数字填入空格中
            top++;   //top加1,处理下一个空格
        }
        if(n > 9)   //如果试到9都不行的话,循环结束后n的值为10,此时要回退  
        {
            sd[a][b] = 0;  //回退时先将当前空格中数字改为0,相当于清零
            top--;   //回退1步
        }
    }

}
int main(void)
{
	 
    int sd[9][9] = {  //  “世界最难数独”信息   
        8,0,0,0,0,0,0,0,0,
        0,0,3,6,0,0,0,0,0,
        0,7,0,0,9,0,2,0,0,
        0,5,0,0,0,7,0,0,0,
        0,0,0,0,4,5,7,0,0,
        0,0,0,1,0,0,0,3,0,
        0,0,1,0,0,0,0,6,8,
        0,0,8,5,0,0,0,1,0,
        0,9,0,0,0,0,4,0,0,
        };
    
	 
    
    fill(sd);  //调用函数求解
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值