C语言的DLX求解数独程序效率虽然很高,但可读性不好,在知乎上找到精确覆盖问题这篇文章介绍得比较清楚,让DeepSeek把其中的精确覆盖问题代码和求解数独代码截图转化为文本。再把其他程序读取文件中的数独题代码加入。
from typing import Optional
class ExactCoverOfSt:
def __init__(self, m: int) -> None:
self.m = m # 元素个数
self.ests = [] # 子集列表
self.size = [[] for _ in range(m)] # 从元素到子集的查找索引
def add_set(self, set: list[int]) -> None:
i = len(self.ests) # 当前子集的编号
self.ests.append(set) # set是一个整数列表,表示这个子集覆盖的元素编号。例如[0, 4, 6, 8]。
for j in set:
self.size[j].append(i)
def make_exact_cover(self) -> Optional[list[int]]:
self.covered = [False] * self.m # 元素是否已经被覆盖
self.selected = [] # 被选取的子集编号
self.conflict = [False] * len(self.ests) # 子集是否和之前已经选取的子集有冲突
return list(self.selected) if self.dfs() else None
def dfs(self) -> bool:
candidate = {}
for j in range(self.m):
if not self.covered[j]: # 忽略已经被覆盖的元素
candidate[j] = [i for i in self.size[j] if not self.conflict[i]] # 统计可选项
if len(candidate[j]) == 0: # 如果存在一个元素已经没有可选项了,那么一定无解,直接返回。
return False
if len(candidate) == 0: # 如果已经没有未被覆盖的元素了,说明已经找到了一个精确覆盖,直接返回。
return True
j = min(candidate.keys(), key=lambda x: len(candidate[x])) # 找到可选项最少的元素
for i in candidate[j]:
self.selected.append(i)
mark = [] # 记录因为选取子集i之后发生冲突的那些子集
for k in self.ests[i]: # 依次处理子集i覆盖的所有元素
self.covered[k] = True
for l in self.size[k]: # 标记冲突
if not self.conflict[l]: # 只标记由于选取子集i产生的冲突。
mark.append(l)
self.conflict[l] = True
if self.dfs(): # 递归dfs,如果能够找到精确覆盖,则返回。
return True
# 如果没有找到精确覆盖,则恢复状态,尝试下一个子集。
self.selected.pop()
for k in self.ests[i]:
self.covered[k] = False
for t in mark:
self.conflict[t] = False
# 遍历完所有子集,仍然没有找到精确覆盖,说明无解。
return False
def decode(subset: list[int]) -> tuple[int, int, int]:
i = subset[0] // 9
j = subset[0] % 9
v = (subset[1] - 81) % 9 + 1
return i, j, v
def solve_sudoku(grid: list[str]) -> None:
ec = ExactCoverOfSt(81 * 4)
for i in range(9):
for j in range(9):
for v in range(1, 10):
if grid[i][j] == '.' or v == int(grid[i][j]):
subset = []
subset.append(81 * 0 + (i * 9 + j))
subset.append(81 * 1 + (i * 9 + v - 1))
subset.append(81 * 2 + (j * 9 + v - 1))
subset.append(81 * 3 + ((i - i % 3 + j // 3) * 9 + v - 1))
ec.add_set(subset)
selected = ec.make_exact_cover()
if selected:
ans = [list(row) for row in grid]
for i in selected:
x, y, v = decode(ec.ests[i])
ans[x][y] = str(v)
for row in ans:
print(''.join(row))
else:
print("No solution.")
'''
solve_sudoku([
'53..7....',
'6..195...',
'.98....6.',
'8...6...3',
'4..8.3..1',
'7...2...6',
'.6....28.',
'...419..5',
'....8..79',
])
b='000000019500600000000000000600080500040000300000010000380000040000200700010900000'.replace('0','.')
solve_sudoku([b[i*9:i*9+9] for i in range(9)])
'''
import sys
import time
def main():
if len(sys.argv) != 2:
print("用法: python script.py <文件名>")
return
filename = sys.argv[1]
try:
with open(filename, 'r', encoding='utf-8') as file:
while True:
line = file.readline().strip()
if line == '':
break
print(f"题目: {line}")
t = time.time()
solve_sudoku([line.replace('0','.')[i*9:i*9+9] for i in range(9)])
#solve(board)
print(f"耗时: {round(time.time()-t, 4)} s")
#print("-" * 50)
except FileNotFoundError:
print(f"错误: 文件 '{filename}' 不存在")
except Exception as e:
print(f"错误: {e}")
if __name__ == '__main__':
main()
针对1000道17个已知数的问题,用时6秒,基本与DLX相当。
time python ecso.txt sudoku171k.txt >ec171k.txt
有趣的是,原图中的前两个from import语句不知何故没有识别,手工加上from typing import Optional,而代码其余部分识别又相当精确,包括List Tuple中的大写,不import它们,而把代码中改为小写,在Python 3.13中是可以的。在python 3.8中报错。
line 10, in ExactCoverOfSt
def add_set(self, set: list[int]) -> None:
TypeError: 'type' object is not subscriptable (key <class 'int'>)

1595

被折叠的 条评论
为什么被折叠?



