数独生成的python实现(半成品-未完待续)

数独的规则,简而言之,在9*9的表格里,填入数字,填入的数字在其所在的3*3区域里是唯一的,在其所在的行里、列里也是唯
一的,具体的参照该词条的解释。

数独的生成思路

采用倒着来拆解问题:

  1. 对于每一个小方块填入数字,需要知道该位置能填入的数字集合(final_useInBit);
  2. 从规则推出:final_useInBit = 所在3*3区域的可取数集 - 所在列数集 - 所在行数集;
  3. 要得到“所在3*3区域的可取数集(Area_canUse)”,就要得到“所在区域已用的数集(Area_used_num),再用基数集(base=[1,2,3,4,5,6,7,8,9])- Area_used_num = Area_canUse,即差集;
  4. 要得到Area_used_num,就要获悉需要填入的位置隶属于哪个区域。

备注:

本版本是草稿版,有许多未修改、未完善、删减之处,有些函数甚至直接引用了全局变量,仅做笔记。

以下是进行前面代码的一些初始定义:

import numpy as np
import random
import copy

#构造初始9*9矩阵,全局变量:
sudoku = np.zeros((9,9),dtype=np.int16)
#构造基本数源,全局变量
base = [1,2,3,4,5,6,7,8,9]

#初始化数独矩阵第一行的数列
first = copy.deepcopy(base)#使用深拷贝的方式,避免base被改动
random.shuffle(first)#调用shuffle函数随机生成
sudoku[0] = np.array(first) 
#在第一行数列已经设定的基础上进行数独生成

-----以上先输入,以下的函数调用需在完成下面的步骤后进行-------

#调用函数生成数独
create(1,0) 

在未调用函数create生成数独前,sudoku的结构如下:

(图片加载失败...)

在实现的时候,我已经预设了第一行的数据生成(调用随机函数赋值)(你可以根据你的想法做出调整,也可以不预设,但或许
我的函数就得做些修改),因此数独的生成起点是在坐标(1,0)开始的。

实现step 4的函数

get_Area(i,j),实现思路,通过传入的位置坐标,做简单的位置判断,返回所在区域的起始行列坐标,如:

(图片加载失败....)

上图坐标为(4,4)值为5的,所属的区域就应该是第5区域,起始坐标值为显示白色的0值(3,3),(区域的划分从左到右,从
上往下进行排序,左上角为1区,右上角为3区,右下角为9区)

函数代码如下:

##########################################################
#Function:get_Area
#Description:通过输入的坐标,返回其所在区域的起始坐标
#########################################################
def get_Area(i,j):
    if i <= 2:
        if j <= 2:
            Area_now = (0,0)
        elif 3 <= j and j <=5:
            Area_now = (0,3)
        else:
            Area_now = (0,6)
    elif 6 <= i:
        if j <= 2:
            Area_now = (6,0)
        elif 3 <= j and j <=5:
            Area_now = (6,3)
        else:
            Area_now = (6,6)
    else:
        if j <= 2:
            Area_now = (3,0)
        elif 3 <= j and j <=5:
            Area_now = (3,3)
        else:
            Area_now = (3,6)
    return Area_now

为什么是返回区域起始坐标点,而不直接返回区域值(比如1区、2区...)呢?因为这样更方便于接下来的函数实现。

实现step 3的函数

从上面的分析可知,这一步的目的是得到“所在3*3区域的可取数集(Area_canUse)”,而实现这一步的前提是得到区域已用数
集,这些前面已经论述。

函数代码如下:

#########################################################
#Function:get_AreaNUM_canUse
#Description:获得3*3区域内还未使用的的数的list
#thought:传入区域的起始坐标、当前位的坐标,进行遍历得到已经添加的数,在与基本数列
#    做差集,得到当前区域还能使用的集合
    
#########################################################
def get_AreaNUM_canUse(nowRow,nowCol):
    
    startRow,startCol = get_Area(nowRow,nowCol)
#    print('区域起始行:%s,区域起始列:%s' % (startRow,startCol))    
    Area_used_num = set()
    for i in range(startRow,nowRow):
        for j in range(startCol,startCol+3):
            Area_used_num.add(sudoku[i][j])#考虑替换成numpy
    for j in range(startCol,nowCol):
        Area_used_num.add(sudoku[nowRow][j])
       
#    print('当前区域已用数集:%s' % Area_used_num)
#    print('基数集:%s' % base)                 
    Area_canUse = set(base) - Area_used_num
#    print('最终区域可用数集:%s' % Area_canUse)
    return Area_canUse

这个函数以当前的位置坐标为参数传入,并在调用前面实心的get_Area函数后,返回当前所在区域的起始坐标值,通过遍历取
出~该位置之前的~在该区域的~已填入的数据,在进行做差集,得到所在区域目前还能使用的数字Area_canUse。

注:注释掉的是调试测试时使用,另外由于是未修改代码,因此直接用了一些全局变量。

实现step 2的函数

这一步是要获得所在位可用数集final_useInBit,分析前面已经讲过。

函数代码如下:

#########################################################
#Function:get_bitNUM
#Description:获取当前位可用的数
#thought:区域可用集 - 当前列已经出现的数-当前行已经出现的数 = 位可用集
#    
#########################################################
def get_bitNUM(Row,Col):    
    
    Col_usedNum = set() 
    Row_usedNum = set()
    #获得区域可用数
    Area_canUse = get_AreaNUM_canUse(Row,Col)
    #获得当前行已用数
    for i in range(0,Row):
        Col_usedNum.add(sudoku[i][Col])   
    #获得当前列已用数   
    for i in range(0,Col):
        Row_usedNum.add(sudoku[Row][i])
    #求差集 
    CR_used = Col_usedNum | Row_usedNum 
    final_useInBit =  Area_canUse - CR_used 

#    print('当前位可用数集:%s' % final_useInBit)
    return list(final_useInBit)
    

到了这一步,基本必要的工具函数已经完成,接下来是考虑怎么生成的问题。

数独的生成的考虑

在前面的函数中,我们已经实现了给出某个位可以填的数集,但是并不是某个位填入数集中的数就能得到正确的答案,尽管它填
下去时是符合游戏规则的,可是这一位填入的数字却会限制或者说影响下面每一位能填入的数集,如果导致最后没有合适的数字
可以填,数独自然就没法顺利生成了。所以有必要思考几个问题:

  1. 怎么判断这一步无法进行下去?
  2. 怎么判断数独顺利生成?
  3. 如果这一步无法进行下去怎么解决?

可以这样考虑:假设现在在起点(1,0),从该位的可填数集中取数,由于可填数集非空(作为起点它是一定非空的),从
[a,b,c,d....]中取出a后,进入下一位(1,1)的取数,此时如果该位的可填数集非空,那么仍然从中取数并进入下一位(1,2)。但
如果(1,1)位的可填数集为空,那么意味着该位没有符合游戏规则的数字可以填入,造成这一结果的,必定是由前面的选择导致
的。因此回退到上一位(1,0)中,重新进行选择,且不能选前面已经试过的会导致后面失败的数字。然后继续进行....直到坐标
值为(8,8)且此时可填位为非空集,那么意味着顺利的走到了最后一步。

本函数的实现,并非只生成一个解,而是生成在确定了第一行数据后的所有解,因此有些bug(不断的生成,并解的数量太多),搞
得有些懒,所以这一部分还没修改掉。

函数代码实现:

def create(Row=0,Col=0,flag=0):
   
    if Col == 9:
        Row +=1
        Col = 0
                      
    final_useInBit = get_bitNUM(Row,Col)               
    if len(final_useInBit) == 0:
        return                 
    else:       
        for choice in final_useInBit:
            sudoku[Row][Col] = choice        
            if Row == 8 and Col == 8:
                flag += 1
                print('-'*25,'数独式:',flag)
                print(sudoku)                
                return flag   
            
            create(Row,Col+1,flag)

前面使用到if Col == 9的判断后赋值操作,由于每次调用create(Row,Col+1,flag)时,操作位在当前行向右端移动,当Col == 9的
时候实际上已经越界了,此时应该是进入下一行Row,而Col应该重置为0值

上面有个flag参数,这个不用管(目前不起效用,也没影响),打算要修改来对数独生成的解进行计数,使得能控制解的生成数
量,还是有很多问题,就权当记录吧

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的 Python 数独生成器,它使用递归算法生成一个有解的数独谜题: ```python import random def generate_board(): # 创建一个 9x9 的数独棋盘 board = [[0 for i in range(9)] for j in range(9)] # 递归填充数字 fill_board(board) # 随机移除部分数字 remove_numbers(board) return board def fill_board(board): # 获取第一个空格的位置 row, col = get_next_empty_cell(board) # 如果没有空格,则数独已经填好并且有解 if row == -1: return True # 从 1 到 9 随机选择一个数字 for num in random.sample(range(1, 10), 9): # 检查数字是否可以放入当前空格 if is_valid(board, row, col, num): # 如果可以,将数字填入当前空格 board[row][col] = num # 递归填充下一个空格 if fill_board(board): return True # 如果下一个空格无法填入数字,则回溯并尝试下一个数字 board[row][col] = 0 # 如果所有数字都无法填入当前空格,则数独无解 return False def get_next_empty_cell(board): # 找到下一个空格的位置 for i in range(9): for j in range(9): if board[i][j] == 0: return i, j # 如果没有空格,则返回 (-1, -1) return -1, -1 def is_valid(board, row, col, num): # 检查行和列是否有重复数字 for i in range(9): if board[row][i] == num or board[i][col] == num: return False # 检查 3x3 的子区域是否有重复数字 sub_row = (row // 3) * 3 sub_col = (col // 3) * 3 for i in range(3): for j in range(3): if board[sub_row+i][sub_col+j] == num: return False # 如果没有重复数字,则该数字可以放入当前位置 return True def remove_numbers(board): # 随机移除一些数字,直到达到所需的空格数 num_empty = random.randint(40, 60) for i in range(num_empty): row, col = random.randint(0, 8), random.randint(0, 8) while board[row][col] == 0: row, col = random.randint(0, 8), random.randint(0, 8) board[row][col] = 0 ``` 你可以使用以下代码生成一个数独谜题: ```python board = generate_board() for row in board: print(row) ``` 输出结果将类似于: ``` [6, 4, 0, 0, 0, 0, 5, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 9] [0, 0, 0, 0, 0, 0, 0, 2, 0] [0, 0, 0, 0, 0, 0, 0, 0, 2] [0, 0, 0, 0, 0, 0, 0, 0, 8] [0, 0, 0, 0, 0, 0, 0, 0, 6] [4, 0, 0, 0, 0, 5, 9, 0, 0] [0, 0, 0, 0, 0, 7, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0] ``` 每个空格为 0 表示该位置为空,需要填入一个数字。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值