利用DeepSeek修改豆包的使用DFS+MRV策略求解数独C程序

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

一开始总是不对,后来在find_min_candidate_cell函数中返回前加了调试语句,发现所有最低可选位置都只尝试了一次,没有回溯过

他终于找到
关键问题:MRV策略中,当尝试一个数字失败回溯后,该位置的候选数可能变为0。原来的find_min_candidate_cell在遇到候选数为0的格子时会直接返回false,导致提前结束递归。实际上,即使候选数为0,这个格子仍然需要被尝试(只是所有可能的数字都已经被排除了)。

解决:在find_min_candidate_cell中,不因为候选数为0就跳过格子,而是仍然选择它(因为它是需要填充的位置)。然后在solve_sudoku_dfs中尝试所有可能的数字,如果都无效则回溯。

这样修改后,MRV版本也能正常工作了。

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>

// 数独大小定义
#define SIZE 9
#define TOTAL_CELLS 81
#define ALL_BITS 0x1FF  // 二进制 111111111,代表1-9所有数字

// 数独结构体,只需要存储值
typedef struct {
    int value;  // 0表示空,1-9表示已填充数字
} SudokuCell;

SudokuCell sudoku[SIZE][SIZE];

// 初始化数独
void init_sudoku(const char *input) {
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            int idx = i * SIZE + j;
            sudoku[i][j].value = input[idx] - '0';
        }
    }
}

// 计算指定位置的候选数掩码(基于当前棋盘状态)
uint16_t get_candidate_mask(int row, int col) {
    if (sudoku[row][col].value != 0) {
        return 0;  // 已填充的格子无候选数
    }

    uint16_t mask = ALL_BITS;

    // 1. 排除当前行已存在的数字
    for (int c = 0; c < SIZE; c++) {
        int val = sudoku[row][c].value;
        if (val != 0) {
            mask &= ~(1 << (val - 1));
        }
    }

    // 2. 排除当前列已存在的数字
    for (int r = 0; r < SIZE; r++) {
        int val = sudoku[r][col].value;
        if (val != 0) {
            mask &= ~(1 << (val - 1));
        }
    }

    // 3. 排除当前3x3宫格已存在的数字
    int box_row = (row / 3) * 3;
    int box_col = (col / 3) * 3;
    for (int r = box_row; r < box_row + 3; r++) {
        for (int c = box_col; c < box_col + 3; c++) {
            int val = sudoku[r][c].value;
            if (val != 0) {
                mask &= ~(1 << (val - 1));
            }
        }
    }

    return mask;
}

// 统计二进制中1的个数
int count_bits(uint16_t mask) {
    int count = 0;
    while (mask) {
        count += mask & 1;
        mask >>= 1;
    }
    return count;
}

// 迭代填充唯一候选数
int fill_unique_candidates() {
    int filled_count = 0;
    bool changed;
    
    do {
        changed = false;
        
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                if (sudoku[i][j].value != 0) {
                    continue;
                }
                
                uint16_t mask = get_candidate_mask(i, j);
                int count = count_bits(mask);
                
                if (count == 1) {
                    // 找到唯一候选数
                    for (int num = 1; num <= SIZE; num++) {
                        if (mask & (1 << (num - 1))) {
                            sudoku[i][j].value = num;
                            filled_count++;
                            changed = true;
                            break;
                        }
                    }
                }
            }
        }
    } while (changed);
    
    return filled_count;
}

// 检查在位置(row,col)填入num是否有效
bool is_valid(int row, int col, int num) {
    // 检查行
    for (int c = 0; c < SIZE; c++) {
        if (sudoku[row][c].value == num) {
            return false;
        }
    }
    
    // 检查列
    for (int r = 0; r < SIZE; r++) {
        if (sudoku[r][col].value == num) {
            return false;
        }
    }
    
    // 检查3x3宫
    int box_row = (row / 3) * 3;
    int box_col = (col / 3) * 3;
    for (int r = box_row; r < box_row + 3; r++) {
        for (int c = box_col; c < box_col + 3; c++) {
            if (sudoku[r][c].value == num) {
                return false;
            }
        }
    }
    
    return true;
}


// 找到候选数最少的空格(MRV策略)
bool find_min_candidate_cell(int *out_row, int *out_col) {
    int min_count = SIZE + 1;
    *out_row = -1;
    *out_col = -1;

    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            if (sudoku[i][j].value != 0) {
                continue;
            }

            // 关键:在递归过程中,一个位置可能暂时没有候选数
            // 但这不代表它应该被跳过
            uint16_t mask = get_candidate_mask(i, j);
            int count = count_bits(mask);
            
            // 总是选择count最小的,即使count=0
            if (count < min_count) {
                min_count = count;
                *out_row = i;
                *out_col = j;
            }
        }
    }

    return (*out_row != -1);  // 找到空格就返回true
}

// DFS求解数独(使用MRV策略)
bool solve_sudoku_dfs() {
    int row, col;
    
    // 找到候选数最少的空格
    if (!find_min_candidate_cell(&row, &col)) {
        // 没有空格了,数独已解
        return true;
    }
    
    // 尝试这个位置的所有候选数
    uint16_t mask = get_candidate_mask(row, col);
    
    for (int num = 1; num <= SIZE; num++) {
        if (mask & (1 << (num - 1))) {
            // 双重检查有效性(因为候选数掩码可能过时)
            if (is_valid(row, col, num)) {
                sudoku[row][col].value = num;
                
                if (solve_sudoku_dfs()) {
                    return true;
                }
                
                // 回溯
                sudoku[row][col].value = 0;
            }
            // 如果无效,继续尝试下一个数字
        }
    }
    
    // 所有候选数都尝试失败
    return false;
}
// DFS求解数独
// 检查数独是否完全填充
bool is_sudoku_complete() {
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            if (sudoku[i][j].value == 0) {
                return false;
            }
        }
    }
    return true;
}

// 生成输出字符串
void generate_output_string(char *output) {
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            int idx = i * SIZE + j;
            output[idx] = sudoku[i][j].value + '0';
        }
    }
    output[TOTAL_CELLS] = '\0';
}

// 统计剩余空格数
int count_empty_cells() {
    int count = 0;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            if (sudoku[i][j].value == 0) {
                count++;
            }
        }
    }
    return count;
}

// 主函数
int main() {
    char input[TOTAL_CELLS + 2];
    int total_sudokus = 0;
    int solved_sudokus = 0;
    
    while (fgets(input, sizeof(input), stdin) != NULL) {
        // 去除换行符
        size_t len = strlen(input);
        if (len > 0 && input[len-1] == '\n') {
            input[len-1] = '\0';
            len--;
        }
        
        if (len != TOTAL_CELLS) {
            fprintf(stderr, "警告:无效的数独长度 %zu,跳过该行\n", len);
            continue;
        }
        
        total_sudokus++;
        
        // 初始化数独
        init_sudoku(input);
        
        // 第一步:填充唯一候选数
        fill_unique_candidates();
        
        // 第二步:如果未完全解决,使用DFS求解
        if (!is_sudoku_complete()) {
            solve_sudoku_dfs();
        }
        
        // 输出结果
        char output[TOTAL_CELLS + 1] = {0};
        generate_output_string(output);
        int empty_cells = count_empty_cells();
        
        printf("%s,%d\n", output, empty_cells);
        
        if (empty_cells == 0) {
            solved_sudokus++;
        }
    }
    
    fprintf(stderr, "总计处理数独数量:%d\n", total_sudokus);
    fprintf(stderr, "完全解决的数独数量:%d\n", solved_sudokus);
    
    return 0;
}

单个执行如下

gcc fill2.c -o fill2
./fill2
800000000003600000070090200050007000000045700000100030001000068008500010090000400
812753649943682175675491283154237896369845721287169534521974368438526917796318452,0
总计处理数独数量:1
完全解决的数独数量:1

批量执行如下

./fill2 <sudoku17.txt >sudoku17out.txt
^C

real    1m55.297s
user    1m55.102s
sys     0m0.002s
gcc fill2.c -o fill2 -O3
./fill2 <sudoku17.txt >sudoku17out.txt
^C

real    1m55.586s
user    1m56.015s
sys     0m0.001s

不加-O3选项编译,17个已知数,2分钟1500题,加了-O3选项编译,2分钟5000题,速度感人。

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值