上文只计算了初始的唯一可选数

在上述程序基础上,将具有1个可选数的格子一次性都填上唯一可选数,用新字符串重新计算可选数,直到没有唯一可选数的格子,输出每轮迭代的结果,给出完整的python程序

def find_candidates(board):
    """找出数独中每个空格的所有可能数字"""
    # 初始化候选数集合,每个空格初始包含1-9
    candidates = [[set(range(1, 10)) for _ in range(9)] for _ in range(9)]
    
    # 根据已有数字减少候选数
    for r in range(9):
        for c in range(9):
            pos = r * 9 + c
            if board[pos] != '0':
                num = int(board[pos])
                # 清除同行、同列、同宫的候选数
                candidates[r][c] = {num}  # 已确定数字的位置
                
                # 清除同行
                for cc in range(9):
                    if cc != c and num in candidates[r][cc]:
                        candidates[r][cc].remove(num)
                
                # 清除同列
                for rr in range(9):
                    if rr != r and num in candidates[rr][c]:
                        candidates[rr][c].remove(num)
                
                # 清除同宫(3x3区块)
                start_r, start_c = 3 * (r // 3), 3 * (c // 3)
                for rr in range(start_r, start_r + 3):
                    for cc in range(start_c, start_c + 3):
                        if (rr != r or cc != c) and num in candidates[rr][cc]:
                            candidates[rr][cc].remove(num)
    
    return candidates

def format_cell(candidates, board, r, c):
    """格式化单个格子,确保输出为9个字符(不含括号)"""
    pos = r * 9 + c
    cell_chars = ""
    
    if board[pos] != '0':
        # 预置数字,用{}包裹
        cell_chars = f"{{{board[pos]}}}"
    else:
        cand_set = sorted(candidates[r][c])
        if not cand_set:
            # 无候选数的情况(理论上有候选数)
            cell_chars = "[]"
        elif len(cand_set) == 1:
            # 只有一个候选数,用<>包裹
            cell_chars = f"<{cand_set[0]}>"
        else:
            # 多个候选数,用[]包裹
            cand_str = "".join(str(n) for n in cand_set)
            cell_chars = f"[{cand_str}]"
    
    # 确保输出为9个字符
    # 计算需要填充的字符数
    content_len = len(cell_chars)  # 括号内的内容长度
    brackets_len = 2  # 两个括号
    total_len = content_len + brackets_len
    
    if total_len < 9:
        # 在括号内填充空格使总长度为9
        padding = 9 - total_len
        left_pad = padding // 2
        right_pad = padding - left_pad
        cell_chars = cell_chars[0] + " " * left_pad + cell_chars[1:-1] + " " * right_pad + cell_chars[-1]
    
    return cell_chars

def print_sudoku_candidates(board_str, candidates, iteration, changed_cells=None):
    """打印数独候选数矩阵"""
    print(f"\n{'=' * 90}")
    print(f"迭代轮次 {iteration}:")
    print(f"{'=' * 90}")
    
    # 打印每行
    for r in range(9):
        row_chars = []
        for c in range(9):
            cell = format_cell(candidates, board_str, r, c)
            row_chars.append(cell)
        
        # 添加宫格分隔符
        formatted_row = ""
        for i, cell in enumerate(row_chars):
            formatted_row += cell
            if i in [2, 5]:  # 第3、6列后加分隔符
                formatted_row += " | "
            elif i < 8:
                formatted_row += " "
        
        print(formatted_row)
        
        # 添加宫格行分隔符
        if r in [2, 5]:
            print("-" * 90)
    
    print("=" * 90)
    
    # 打印统计信息
    total_empty = board_str.count('0')
    single_candidates = 0
    multi_candidates = 0
    
    for r in range(9):
        for c in range(9):
            pos = r * 9 + c
            if board_str[pos] == '0':
                if len(candidates[r][c]) == 1:
                    single_candidates += 1
                else:
                    multi_candidates += 1
    
    print(f"空格总数: {total_empty}")
    print(f"只有一个候选数的位置: {single_candidates}")
    print(f"有多个候选数的位置: {multi_candidates}")
    
    if changed_cells:
        print(f"本轮填充的格子: {changed_cells}")

def fill_unique_candidates(board_str, candidates):
    """填充所有只有一个候选数的格子,返回新的数独字符串"""
    board_list = list(board_str)
    changed_cells = []
    
    for r in range(9):
        for c in range(9):
            pos = r * 9 + c
            if board_list[pos] == '0' and len(candidates[r][c]) == 1:
                # 只有一个候选数,填充它
                num = next(iter(candidates[r][c]))
                board_list[pos] = str(num)
                changed_cells.append((r, c, num))
    
    new_board = "".join(board_list)
    return new_board, changed_cells

def solve_iteratively(initial_board):
    """迭代求解数独,每次填充唯一候选数"""
    print("开始迭代求解数独...")
    print(f"初始数独: {initial_board}")
    
    current_board = initial_board
    iteration = 0
    all_iterations = []
    
    while True:
        iteration += 1
        
        # 计算候选数
        candidates = find_candidates(current_board)
        
        # 统计当前状态
        total_empty = current_board.count('0')
        single_candidates_count = 0
        for r in range(9):
            for c in range(9):
                pos = r * 9 + c
                if current_board[pos] == '0' and len(candidates[r][c]) == 1:
                    single_candidates_count += 1
        
        print(f"\n{'*' * 90}")
        print(f"第 {iteration} 轮迭代 - 空格数: {total_empty}, 唯一候选数: {single_candidates_count}")
        print(f"{'*' * 90}")
        
        # 打印当前候选数矩阵
        print_sudoku_candidates(current_board, candidates, iteration)
        
        # 保存当前状态
        all_iterations.append({
            'board': current_board,
            'candidates': [[sorted(candidates[r][c]) for c in range(9)] for r in range(9)],
            'iteration': iteration
        })
        
        # 检查是否有唯一候选数可以填充
        if single_candidates_count == 0:
            print(f"\n迭代结束:没有更多唯一候选数可填充")
            break
        
        # 填充唯一候选数
        new_board, changed_cells = fill_unique_candidates(current_board, candidates)
        
        # 检查是否有变化
        if new_board == current_board:
            print(f"\n迭代结束:没有发现需要填充的格子")
            break
        
        # 显示填充的信息
        if changed_cells:
            print(f"\n本轮填充了 {len(changed_cells)} 个格子:")
            for r, c, num in changed_cells:
                print(f"  位置 ({r+1}, {c+1}) 填充数字 {num}")
        
        # 更新当前数独
        current_board = new_board
        
        # 检查是否已经解决
        if current_board.count('0') == 0:
            print(f"\n🎉 数独已完全解决!")
            break
        
        # 安全检查:最多迭代20次
        if iteration >= 20:
            print(f"\n⚠️  已达到最大迭代次数(20次),停止迭代")
            break
    
    return current_board, all_iterations

def print_summary(initial_board, final_board, all_iterations):
    """打印求解摘要"""
    print(f"\n{'=' * 90}")
    print("求解摘要")
    print(f"{'=' * 90}")
    
    initial_empty = initial_board.count('0')
    final_empty = final_board.count('0')
    filled_cells = initial_empty - final_empty
    
    print(f"初始空格数: {initial_empty}")
    print(f"最终空格数: {final_empty}")
    print(f"填充的格子数: {filled_cells}")
    print(f"迭代轮次: {len(all_iterations)}")
    
    # 显示最终结果
    if final_empty == 0:
        print(f"\n🎉 数独已完全解决!最终结果:")
    else:
        print(f"\n⚠️  数独未完全解决,剩余 {final_empty} 个空格。最终状态:")
    
    # 显示最终数独的网格形式
    print("\n最终数独网格:")
    print("+" + "-" * 23 + "+")
    for r in range(9):
        row = ""
        for c in range(9):
            pos = r * 9 + c
            num = final_board[pos]
            if num == '0':
                row += ". "
            else:
                row += num + " "
            
            if c in [2, 5]:
                row += "| "
        
        print("| " + row + "|")
        
        if r in [2, 5]:
            print("|" + "-" * 23 + "|")
    
    print("+" + "-" * 23 + "+")

def main():
    """主函数"""
    print("数独唯一候选数迭代求解器")
    print("=" * 50)
    
    # 示例数独
    examples = {
        "1": "530070000600195000098000060800060003400803001700020006060000280000419005000080079",  # 中等难度
        "2": "000000010400000000020000000000050407008000300001090000300400200050100000000806000",  # 困难
        "3": "000000000000003085001020000000507000004000100090000000500000073002010000000040009",  # 极难
        "4": "800000000003600000070090200050007000000045700000100030001000068008500010090000400",  # 世界最难数独之一
    }
    
    print("可用示例:")
    for key, board in examples.items():
        empty = board.count('0')
        print(f"  示例{key}: {board} (空格数: {empty})")
    
    while True:
        print("\n" + "=" * 50)
        choice = input("请选择:\n1. 使用示例\n2. 手动输入数独\n3. 退出\n选择 (1/2/3): ").strip()
        
        if choice == '3':
            print("程序退出")
            break
        
        if choice == '1':
            ex_choice = input("选择示例 (1/2/3/4): ").strip()
            if ex_choice in examples:
                board_str = examples[ex_choice]
                print(f"使用示例{ex_choice}: {board_str}")
            else:
                print("无效选择,使用示例1")
                board_str = examples['1']
        
        elif choice == '2':
            print("\n请输入81个字符的数独字符串(0表示空格):")
            while True:
                board_str = input("数独: ").strip()
                
                if len(board_str) != 81:
                    print(f"错误:输入长度必须为81字符,当前长度为{len(board_str)}")
                    continue
                
                if not set(board_str).issubset(set('0123456789')):
                    print("错误:输入只能包含数字0-9")
                    continue
                
                if board_str.count('0') == 81:
                    print("错误:数独不能全为空")
                    continue
                
                break
        
        else:
            print("无效选择")
            continue
        
        # 开始迭代求解
        final_board, all_iterations = solve_iteratively(board_str)
        
        # 打印摘要
        print_summary(board_str, final_board, all_iterations)
        
        # 询问是否继续
        cont = input("\n是否继续求解另一个数独? (y/n): ").strip().lower()
        if cont != 'y':
            print("程序退出")
            break

if __name__ == "__main__":
    main()

输出如下

C:\d\1230>python candnum2.py
数独唯一候选数迭代求解器
==================================================
可用示例:
  示例1: 530070000600195000098000060800060003400803001700020006060000280000419005000080079 (空格数: 51)
  示例2: 000000010400000000020000000000050407008000300001090000300400200050100000000806000 (空格数: 64)
  示例3: 000000000000003085001020000000507000004000100090000000500000073002010000000040009 (空格数: 64)
  示例4: 800000000003600000070090200050007000000045700000100030001000068008500010090000400 (空格数: 60)

请选择:
1. 使用示例
2. 手动输入数独
3. 退出
选择 (1/2/3): 1
选择示例 (1/2/3/4): 1
使用示例1: 530070000600195000098000060800060003400803001700020006060000280000419005000080079
开始迭代求解数独...
初始数独: 530070000600195000098000060800060003400803001700020006060000280000419005000080079

******************************************************************************************
第 1 轮迭代 - 空格数: 51, 唯一候选数: 4
******************************************************************************************

==========================================================================================
迭代轮次 1:
==========================================================================================
{  5  } {  3  } [ 124 ] | [ 26  ] {  7  } [2468 ] | [1489 ] [1249 ] [ 248 ]
{  6  } [ 247 ] [ 247 ] | {  1  } {  9  } {  5  } | [3478 ] [ 234 ] [2478 ]
[ 12  ] {  9  } {  8  } | [ 23  ] [ 34  ] [ 24  ] | [13457] {  6  } [ 247 ]
------------------------------------------------------------------------------------------
{  8  } [ 125 ] [1259 ] | [ 579 ] {  6  } [ 147 ] | [4579 ] [2459 ] {  3  }
{  4  } [ 25  ] [2569 ] | {  8  } <  5  > {  3  } | [ 579 ] [ 259 ] {  1  }
{  7  } [ 15  ] [1359 ] | [ 59  ] {  2  } [ 14  ] | [4589 ] [ 459 ] {  6  }
------------------------------------------------------------------------------------------
[ 139 ] {  6  } [134579] | [ 357 ] [ 35  ] <  7  > | {  2  } {  8  } <  4  >
[ 23  ] [ 278 ] [ 237 ] | {  4  } {  1  } {  9  } | [ 36  ] <  3  > {  5  }
[ 123 ] [1245 ] [12345] | [2356 ] {  8  } [ 26  ] | [1346 ] {  7  } {  9  }
==========================================================================================
空格总数: 51
只有一个候选数的位置: 4
有多个候选数的位置: 47

本轮填充了 4 个格子:
  位置 (5, 5) 填充数字 5
  位置 (7, 6) 填充数字 7
  位置 (7, 9) 填充数字 4
  位置 (8, 8) 填充数字 3

******************************************************************************************
第 2 轮迭代 - 空格数: 47, 唯一候选数: 5
******************************************************************************************

==========================================================================================
迭代轮次 2:
==========================================================================================
{  5  } {  3  } [ 124 ] | [ 26  ] {  7  } [2468 ] | [1489 ] [1249 ] [ 28  ]
{  6  } [ 247 ] [ 247 ] | {  1  } {  9  } {  5  } | [3478 ] [ 24  ] [ 278 ]
[ 12  ] {  9  } {  8  } | [ 23  ] [ 34  ] [ 24  ] | [13457] {  6  } [ 27  ]
------------------------------------------------------------------------------------------
{  8  } [ 125 ] [1259 ] | [ 79  ] {  6  } [ 14  ] | [4579 ] [2459 ] {  3  }
{  4  } <  2  > [ 269 ] | {  8  } {  5  } {  3  } | [ 79  ] [ 29  ] {  1  }
{  7  } [ 15  ] [1359 ] | <  9  > {  2  } [ 14  ] | [4589 ] [ 459 ] {  6  }
------------------------------------------------------------------------------------------
[ 139 ] {  6  } [1359 ] | [ 35  ] <  3  > {  7  } | {  2  } {  8  } {  4  }
<  2  > [ 278 ] [ 27  ] | {  4  } {  1  } {  9  } | <  6  > {  3  } {  5  }
[ 123 ] [1245 ] [12345] | [2356 ] {  8  } [ 26  ] | [ 16  ] {  7  } {  9  }
==========================================================================================
空格总数: 47
只有一个候选数的位置: 5
有多个候选数的位置: 42

本轮填充了 5 个格子:
  位置 (5, 2) 填充数字 2
  位置 (6, 4) 填充数字 9
  位置 (7, 5) 填充数字 3
  位置 (8, 1) 填充数字 2
  位置 (8, 7) 填充数字 6

*
*
*


******************************************************************************************
第 10 轮迭代 - 空格数: 2, 唯一候选数: 2
******************************************************************************************

==========================================================================================
迭代轮次 10:
==========================================================================================
{  5  } {  3  } {  4  } | {  6  } {  7  } {  8  } | {  9  } {  1  } {  2  }
{  6  } {  7  } {  2  } | {  1  } {  9  } {  5  } | {  3  } {  4  } {  8  }
{  1  } {  9  } {  8  } | {  3  } {  4  } {  2  } | {  5  } {  6  } {  7  }
------------------------------------------------------------------------------------------
{  8  } {  5  } <  9  > | {  7  } {  6  } {  1  } | {  4  } {  2  } {  3  }
{  4  } {  2  } {  6  } | {  8  } {  5  } {  3  } | {  7  } {  9  } {  1  }
{  7  } {  1  } {  3  } | {  9  } {  2  } {  4  } | {  8  } {  5  } {  6  }
------------------------------------------------------------------------------------------
{  9  } {  6  } {  1  } | {  5  } {  3  } {  7  } | {  2  } {  8  } {  4  }
{  2  } {  8  } {  7  } | {  4  } {  1  } {  9  } | {  6  } {  3  } {  5  }
{  3  } <  4  > {  5  } | {  2  } {  8  } {  6  } | {  1  } {  7  } {  9  }
==========================================================================================
空格总数: 2
只有一个候选数的位置: 2
有多个候选数的位置: 0

本轮填充了 2 个格子:
  位置 (4, 3) 填充数字 9
  位置 (9, 2) 填充数字 4

🎉 数独已完全解决!

==========================================================================================
求解摘要
==========================================================================================
初始空格数: 51
最终空格数: 0
填充的格子数: 51
迭代轮次: 10

🎉 数独已完全解决!最终结果:

最终数独网格:
+-----------------------+
| 5 3 4 | 6 7 8 | 9 1 2 |
| 6 7 2 | 1 9 5 | 3 4 8 |
| 1 9 8 | 3 4 2 | 5 6 7 |
|-----------------------|
| 8 5 9 | 7 6 1 | 4 2 3 |
| 4 2 6 | 8 5 3 | 7 9 1 |
| 7 1 3 | 9 2 4 | 8 5 6 |
|-----------------------|
| 9 6 1 | 5 3 7 | 2 8 4 |
| 2 8 7 | 4 1 9 | 6 3 5 |
| 3 4 5 | 2 8 6 | 1 7 9 |
+-----------------------+

是否继续求解另一个数独? (y/n): y

==================================================
请选择:
1. 使用示例
2. 手动输入数独
3. 退出
选择 (1/2/3): 1
选择示例 (1/2/3/4): 2
使用示例2: 000000010400000000020000000000050407008000300001090000300400200050100000000806000
开始迭代求解数独...
初始数独: 000000010400000000020000000000050407008000300001090000300400200050100000000806000

******************************************************************************************
第 1 轮迭代 - 空格数: 64, 唯一候选数: 1
******************************************************************************************

==========================================================================================
迭代轮次 1:
==========================================================================================
[56789] [36789] [35679] | [235679] [234678] [2345789] | [56789] {  1  } [2345689]
{  4  } [136789] [35679] | [235679] [123678] [1235789] | [56789] [2356789] [235689]
[156789] {  2  } [35679] | [35679] [134678] [1345789] | [56789] [3456789] [345689]
------------------------------------------------------------------------------------------
[ 269 ] [ 369 ] [2369 ] | [ 236 ] {  5  } [1238 ] | {  4  } [2689 ] {  7  }
[25679] [4679 ] {  8  } | [ 267 ] [12467] [1247 ] | {  3  } [2569 ] [12569]
[2567 ] [3467 ] {  1  } | [2367 ] {  9  } [23478] | [ 568 ] [2568 ] [2568 ]
------------------------------------------------------------------------------------------
{  3  } [16789] [ 679 ] | {  4  } <  7  > [ 579 ] | {  2  } [56789] [15689]
[26789] {  5  } [24679] | {  1  } [ 237 ] [2379 ] | [6789 ] [346789] [34689]
[1279 ] [1479 ] [2479 ] | {  8  } [ 237 ] {  6  } | [1579 ] [34579] [13459]
==========================================================================================
空格总数: 64
只有一个候选数的位置: 1
有多个候选数的位置: 63

本轮填充了 1 个格子:
  位置 (7, 5) 填充数字 7

******************************************************************************************
第 2 轮迭代 - 空格数: 63, 唯一候选数: 0
******************************************************************************************

==========================================================================================
迭代轮次 2:
==========================================================================================
[56789] [36789] [35679] | [235679] [23468] [2345789] | [56789] {  1  } [2345689]
{  4  } [136789] [35679] | [235679] [12368] [1235789] | [56789] [2356789] [235689]
[156789] {  2  } [35679] | [35679] [13468] [1345789] | [56789] [3456789] [345689]
------------------------------------------------------------------------------------------
[ 269 ] [ 369 ] [2369 ] | [ 236 ] {  5  } [1238 ] | {  4  } [2689 ] {  7  }
[25679] [4679 ] {  8  } | [ 267 ] [1246 ] [1247 ] | {  3  } [2569 ] [12569]
[2567 ] [3467 ] {  1  } | [2367 ] {  9  } [23478] | [ 568 ] [2568 ] [2568 ]
------------------------------------------------------------------------------------------
{  3  } [1689 ] [ 69  ] | {  4  } {  7  } [ 59  ] | {  2  } [5689 ] [15689]
[26789] {  5  } [24679] | {  1  } [ 23  ] [ 239 ] | [6789 ] [346789] [34689]
[1279 ] [1479 ] [2479 ] | {  8  } [ 23  ] {  6  } | [1579 ] [34579] [13459]
==========================================================================================
空格总数: 63
只有一个候选数的位置: 0
有多个候选数的位置: 63

迭代结束:没有更多唯一候选数可填充

==========================================================================================
求解摘要
==========================================================================================
初始空格数: 64
最终空格数: 63
填充的格子数: 1
迭代轮次: 2

⚠️  数独未完全解决,剩余 63 个空格。最终状态:

最终数独网格:
+-----------------------+
| . . . | . . . | . 1 . |
| 4 . . | . . . | . . . |
| . 2 . | . . . | . . . |
|-----------------------|
| . . . | . 5 . | 4 . 7 |
| . . 8 | . . . | 3 . . |
| . . 1 | . 9 . | . . . |
|-----------------------|
| 3 . . | 4 7 . | 2 . . |
| . 5 . | 1 . . | . . . |
| . . . | 8 . 6 | . . . |
+-----------------------+

是否继续求解另一个数独? (y/n):
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐