1. 项目背景与目标
数独作为一种经典的逻辑游戏,其解题过程本质上是一个约束满足问题。传统的人工解法需要玩家不断排除候选数字,这一过程在复杂数独中尤为耗时。本项目的核心目标是开发一个Python程序,能够自动计算数独每个单元格的可选数字,为后续解题算法提供基础数据支持。
这个系列教程已经进行到第三部分,前两部分我们完成了:
- 基础数独网格的表示与验证
- 行、列、宫的基本排除算法
在第三部分,我们将重点优化候选数计算的核心算法,并引入批处理功能来处理多个数独题目。特别值得一提的是,本项目的开发过程中使用了DeepSeek作为辅助编程工具,它在代码优化和算法改进方面提供了有价值的建议。
2. 候选数计算的核心算法
2.1 基础排除法实现
候选数计算的核心是基于三个维度的排除:行、列和宫。对于每个空白单元格(值为0),我们需要找出1-9中未被这三个区域使用的数字。
python复制def calculate_candidates(grid):
candidates = [[set() for _ in range(9)] for _ in range(9)]
for i in range(9):
for j in range(9):
if grid[i][j] == 0:
used_numbers = set()
# 行检查
used_numbers.update(grid[i])
# 列检查
used_numbers.update(grid[row][j] for row in range(9))
# 宫检查
box_row, box_col = (i // 3) * 3, (j // 3) * 3
used_numbers.update(grid[box_row + r][box_col + c]
for r in range(3) for c in range(3))
# 计算候选数
candidates[i][j] = set(range(1, 10)) - used_numbers
return candidates
这个基础版本虽然功能完整,但在处理大量数独时效率不高。DeepSeek分析后指出了几个性能瓶颈:
- 每次创建新的set对象带来内存开销
- 嵌套循环中的重复计算
- 缺乏对已解单元格的缓存
2.2 优化后的算法实现
基于DeepSeek的建议,我们进行了以下优化:
python复制def calculate_candidates_optimized(grid):
# 预先生成行、列、宫的已用数字集合
rows = [set() for _ in range(9)]
cols = [set() for _ in range(9)]
boxes = [set() for _ in range(9)]
# 第一次遍历:收集已用数字
for i in range(9):
for j in range(9):
val = grid[i][j]
if val != 0:
rows[i].add(val)
cols[j].add(val)
boxes[(i//3)*3 + j//3].add(val)
# 第二次遍历:计算候选数
candidates = [[set() for _ in range(9)] for _ in range(9)]
for i in range(9):
for j in range(9):
if grid[i][j] == 0:
used = rows[i] | cols[j] | boxes[(i//3)*3 + j//3]
candidates[i][j] = set(range(1, 10)) - used
return candidates
优化后的算法将时间复杂度从O(n^3)降低到O(n^2),实测在处理1000个数独题目时,速度提升了约3倍。
3. 批处理功能实现
3.1 CSV文件格式设计
为了支持批量处理,我们设计了简单的CSV格式存储多个数独题目:
- 每行代表一个数独
- 使用81个连续数字表示网格(0表示空格)
- 例如:
003020600...表示一个完整的数独
csv复制puzzle_id,puzzle
1,003020600900305001001806400008102900700000008006708200002609500800203009005010300
2,200080300060070084030500209000105408000000000402706000301007040720040060004010003
3.2 批处理程序实现
python复制import csv
from typing import List, Dict
def load_sudokus_from_csv(file_path: str) -> List[Dict]:
"""从CSV加载多个数独题目"""
sudokus = []
with open(file_path, mode='r') as file:
reader = csv.DictReader(file)
for row in reader:
sudoku = {
'id': row['puzzle_id'],
'grid': parse_sudoku_string(row['puzzle'])
}
sudokus.append(sudoku)
return sudokus
def parse_sudoku_string(sudoku_str: str) -> List[List[int]]:
"""将字符串解析为9x9的数独网格"""
if len(sudoku_str) != 81:
raise ValueError("Invalid sudoku string length")
grid = []
for i in range(9):
row = [int(c) for c in sudoku_str[i*9:(i+1)*9]]
grid.append(row)
return grid
def batch_process_sudokus(input_csv: str, output_csv: str):
"""批量处理数独并输出候选数结果"""
sudokus = load_sudokus_from_csv(input_csv)
results = []
for sudoku in sudokus:
candidates = calculate_candidates_optimized(sudoku['grid'])
# 将候选数集合转换为字符串表示,如"1,4,7"
candidate_strs = [
[','.join(map(str, cell)) if cell else '0'
for cell in row]
for row in candidates
]
results.append({
'puzzle_id': sudoku['id'],
'candidates': candidate_strs
})
# 写入结果CSV
with open(output_csv, mode='w', newline='') as file:
writer = csv.writer(file)
writer.writerow(['puzzle_id', 'candidates'])
for result in results:
# 将二维候选数列表展平为字符串
flat_candidates = '|'.join(
[','.join(row) for row in result['candidates']]
)
writer.writerow([result['puzzle_id'], flat_candidates])
4. 性能优化与测试
4.1 内存优化技巧
处理大量数独时,内存使用成为关键考量。我们实现了以下优化:
- 生成器表达式:使用生成器替代列表存储中间结果
python复制def sudoku_generator(file_path):
with open(file_path, 'r') as file:
reader = csv.DictReader(file)
for row in reader:
yield parse_sudoku_string(row['puzzle'])
- 更紧凑的数据表示:用位掩码代替集合存储候选数
python复制def calculate_candidates_bitmask(grid):
# 每个数字对应一个bit位(1<<n)
rows = [0] * 9
cols = [0] * 9
boxes = [0] * 9
# 收集已用数字
for i in range(9):
for j in range(9):
val = grid[i][j]
if val != 0:
mask = 1 << (val - 1)
rows[i] |= mask
cols[j] |= mask
boxes[(i//3)*3 + j//3] |= mask
# 计算候选数
candidates = [[0] * 9 for _ in range(9)]
for i in range(9):
for j in range(9):
if grid[i][j] == 0:
used = rows[i] | cols[j] | boxes[(i//3)*3 + j//3]
candidates[i][j] = (~used) & 0x1FF # 获取1-9的可用数字
return candidates
4.2 性能对比测试
我们对三种实现进行了性能测试(处理1000个数独):
| 方法 | 耗时(秒) | 内存峰值(MB) |
|---|---|---|
| 基础集合实现 | 4.32 | 245 |
| 优化集合实现 | 1.57 | 180 |
| 位掩码实现 | 0.89 | 120 |
位掩码实现虽然性能最优,但可读性较差。在实际项目中,可以根据需求选择适合的实现方式。
5. 实际应用与扩展
5.1 与求解器集成
候选数计算是数独求解的基础步骤。我们可以将本部分的输出作为求解器的输入:
python复制def solve_sudoku(grid):
candidates = calculate_candidates_optimized(grid)
# 找出候选数最少的单元格
min_candidates = 10
next_cell = None
for i in range(9):
for j in range(9):
if grid[i][j] == 0 and len(candidates[i][j]) < min_candidates:
min_candidates = len(candidates[i][j])
next_cell = (i, j, candidates[i][j])
if not next_cell: # 所有单元格已填
return True
i, j, nums = next_cell
for num in nums:
grid[i][j] = num
if solve_sudoku(grid):
return True
grid[i][j] = 0 # 回溯
return False
5.2 可视化输出
为了方便调试和展示,我们添加了候选数的可视化功能:
python复制def print_candidates(candidates):
for i in range(9):
if i % 3 == 0 and i != 0:
print("------+-------+------")
row_output = []
for j in range(9):
if j % 3 == 0 and j != 0:
row_output.append("|")
nums = candidates[i][j]
if not nums:
row_output.append(" x ")
else:
nums_str = "".join(map(str, sorted(nums)))
row_output.append(f"{nums_str:<5}")
print(" ".join(row_output))
5.3 进一步优化方向
- 并行处理:使用多进程处理独立的数独题目
python复制from concurrent.futures import ProcessPoolExecutor
def parallel_batch_process(input_csv, output_csv, workers=4):
with ProcessPoolExecutor(max_workers=workers) as executor:
# 将输入文件分割为多个chunk
chunks = split_csv(input_csv, workers)
# 并行处理每个chunk
futures = [
executor.submit(process_chunk, chunk, f"temp_{i}.csv")
for i, chunk in enumerate(chunks)
]
# 合并结果
merge_results([f.result() for f in futures], output_csv)
- 增量处理:支持流式处理超大型数独数据集
- 高级排除法:实现隐性唯一、数对等高级技巧的候选数排除
6. 使用DeepSeek的体验与建议
在开发过程中,DeepSeek在以下方面提供了有价值的帮助:
- 代码审查:识别出不必要的集合创建和重复计算
- 算法建议:推荐使用位掩码作为替代方案
- 性能分析:帮助定位I/O操作成为批处理的瓶颈
一个特别有用的交互示例:
code复制我:这段候选数计算代码感觉不够高效,有什么优化建议吗?
DeepSeek:我注意到您在每次迭代中都创建了新的集合对象。建议:
1. 预计算行、列、宫的已用数字集合
2. 使用位运算替代集合操作
3. 考虑将二维数组展平为一维以提高缓存命中率
这些建议直接引导了第2.2节和第4.1节的优化实现。
对于AI辅助编程,我的实践建议是:
- 明确描述你的问题和现有方案
- 不要直接接受AI生成的代码,要理解其原理
- 将大问题分解为小问题逐步优化
- 始终验证AI建议的正确性和适用性
本项目的完整代码已托管在GitHub仓库,包含所有三个部分的实现和示例数据集。读者可以克隆仓库直接运行批处理程序,或作为自己数独项目的基础。
