MATLAB数独求解程序

使用回溯算法实现数独求解。这个程序可以解决任何有效的数独谜题,并提供了图形用户界面(GUI)用于输入和显示数独。

程序

classdef SudokuSolver < handle
    % SUDOKUSOLVER 数独求解器类
    
    properties
        board           % 数独棋盘 (9x9矩阵,0表示空格)
        originalBoard   % 原始数独棋盘(用于区分用户输入和求解结果)
        solution        % 数独的解
        isSolved = false % 标记是否已解决
        
        % GUI组件
        fig             % 主窗口
        axesHandle      % 数独棋盘坐标轴
        cellTexts       % 格子文本对象 (9x9)
        solveButton     % 求解按钮
        clearButton     % 清除按钮
        loadButton      % 加载示例按钮
        statusText      % 状态文本
    end
    
    methods
        function obj = SudokuSolver()
            % SUDOKUSOLVER 构造函数
            obj.initializeBoard();
            obj.createGUI();
        end
        
        function initializeBoard(obj)
            % INITIALIZEBOARD 初始化数独棋盘
            obj.board = zeros(9, 9);
            obj.originalBoard = zeros(9, 9);
            obj.isSolved = false;
        end
        
        function createGUI(obj)
            % CREATEGUI 创建图形用户界面
            
            % 创建主窗口
            obj.fig = figure('Name', 'MATLAB数独求解器', ...
                'NumberTitle', 'off', ...
                'MenuBar', 'none', ...
                'Position', [100, 100, 600, 650], ...
                'Resize', 'off', ...
                'CloseRequestFcn', @(~,~) delete(obj));
            
            % 创建数独棋盘坐标轴
            obj.axesHandle = axes('Parent', obj.fig, ...
                'Position', [0.1, 0.25, 0.8, 0.7], ...
                'XLim', [0, 9], ...
                'YLim', [0, 9], ...
                'YDir', 'reverse', ...
                'XTick', [], ...
                'YTick', [], ...
                'Box', 'on', ...
                'DataAspectRatio', [1, 1, 1]);
            
            % 绘制网格线
            hold on;
            for i = 0:9
                lineWidth = 1;
                if mod(i, 3) == 0
                    lineWidth = 2; % 每3条线加粗
                end
                line([i, i], [0, 9], 'Color', 'k', 'LineWidth', lineWidth);
                line([0, 9], [i, i], 'Color', 'k', 'LineWidth', lineWidth);
            end
            hold off;
            
            % 创建格子文本对象
            obj.cellTexts = gobjects(9, 9);
            for row = 1:9
                for col = 1:9
                    obj.cellTexts(row, col) = text(col-0.5, row-0.5, '', ...
                        'HorizontalAlignment', 'center', ...
                        'VerticalAlignment', 'middle', ...
                        'FontSize', 16, ...
                        'FontWeight', 'bold', ...
                        'ButtonDownFcn', {@obj.cellClicked, row, col});
                end
            end
            
            % 创建按钮
            obj.solveButton = uicontrol('Style', 'pushbutton', ...
                'String', '求解', ...
                'Position', [50, 20, 80, 30], ...
                'Callback', @(~,~) obj.solveSudoku(), ...
                'FontSize', 12);
            
            obj.clearButton = uicontrol('Style', 'pushbutton', ...
                'String', '清除', ...
                'Position', [150, 20, 80, 30], ...
                'Callback', @(~,~) obj.clearBoard(), ...
                'FontSize', 12);
            
            obj.loadButton = uicontrol('Style', 'pushbutton', ...
                'String', '加载示例', ...
                'Position', [250, 20, 100, 30], ...
                'Callback', @(~,~) obj.loadExample(), ...
                'FontSize', 12);
            
            % 创建状态文本
            obj.statusText = uicontrol('Style', 'text', ...
                'String', '请输入数独题目', ...
                'Position', [370, 20, 180, 30], ...
                'FontSize', 12, ...
                'HorizontalAlignment', 'left');
            
            % 设置窗口键盘回调
            set(obj.fig, 'KeyPressFcn', @obj.keyPressed);
            
            % 更新显示
            obj.updateDisplay();
        end
        
        function cellClicked(obj, ~, ~, row, col)
            % CELLCLICKED 单元格点击回调函数
            if obj.isSolved
                return;
            end
            
            % 获取当前值
            currentValue = obj.board(row, col);
            
            % 循环增加数值 (0->1->2->...->9->0)
            newValue = mod(currentValue, 9) + 1;
            
            % 更新棋盘
            obj.board(row, col) = newValue;
            obj.originalBoard(row, col) = newValue;
            
            % 更新显示
            obj.updateDisplay();
        end
        
        function keyPressed(obj, ~, event)
            % KEYPRESSED 键盘按键回调函数
            if obj.isSolved
                return;
            end
            
            % 获取当前选中的对象
            currentObj = gco;
            
            % 检查是否是文本对象
            if isempty(currentObj) || ~any(currentObj == obj.cellTexts(:))
                return;
            end
            
            % 找到点击的单元格
            [row, col] = find(obj.cellTexts == currentObj);
            
            % 处理数字键
            if ismember(event.Key, {'0','1','2','3','4','5','6','7','8','9'})
                newValue = str2double(event.Key);
                obj.board(row, col) = newValue;
                obj.originalBoard(row, col) = newValue;
                
            % 处理退格键和删除键
            elseif ismember(event.Key, {'backspace', 'delete'})
                obj.board(row, col) = 0;
                obj.originalBoard(row, col) = 0;
            end
            
            % 更新显示
            obj.updateDisplay();
        end
        
        function updateDisplay(obj)
            % UPDATEDISPLAY 更新数独棋盘显示
            for row = 1:9
                for col = 1:9
                    value = obj.board(row, col);
                    if value == 0
                        set(obj.cellTexts(row, col), 'String', '');
                    else
                        set(obj.cellTexts(row, col), 'String', num2str(value));
                    end
                    
                    % 设置文本颜色
                    if obj.originalBoard(row, col) ~= 0
                        set(obj.cellTexts(row, col), 'Color', 'blue'); % 原始题目为蓝色
                    else
                        set(obj.cellTexts(row, col), 'Color', 'red'); % 求解结果为红色
                    end
                end
            end
        end
        
        function solveSudoku(obj)
            % SOLVESUDOKU 求解数独
            set(obj.statusText, 'String', '正在求解...');
            drawnow;
            
            % 复制当前棋盘
            obj.solution = obj.board;
            
            % 检查数独是否有效
            if ~obj.isValidSudoku(obj.solution)
                set(obj.statusText, 'String', '无效的数独题目!');
                return;
            end
            
            % 尝试求解
            if obj.solve(obj.solution)
                obj.isSolved = true;
                obj.board = obj.solution;
                set(obj.statusText, 'String', '求解完成!');
            else
                set(obj.statusText, 'String', '无解!');
            end
            
            % 更新显示
            obj.updateDisplay();
        end
        
        function success = solve(obj, board)
            % SOLVE 递归回溯求解数独
            % 找到一个空位置
            [row, col] = obj.findEmptyCell(board);
            
            % 如果没有空位置,说明已经解决
            if row == -1
                success = true;
                return;
            end
            
            % 尝试填入1-9的数字
            for num = 1:9
                if obj.isValidMove(board, row, col, num)
                    % 尝试填入数字
                    board(row, col) = num;
                    
                    % 递归尝试解决剩下的部分
                    if obj.solve(board)
                        success = true;
                        return;
                    end
                    
                    % 如果失败,回溯
                    board(row, col) = 0;
                end
            end
            
            % 如果没有数字有效,返回失败
            success = false;
        end
        
        function [row, col] = findEmptyCell(~, board)
            % FINDEMPTYCELL 找到下一个空单元格
            for row = 1:9
                for col = 1:9
                    if board(row, col) == 0
                        return;
                    end
                end
            end
            row = -1;
            col = -1;
        end
        
        function valid = isValidMove(~, board, row, col, num)
            % ISVALIDMOVE 检查在指定位置填入数字是否有效
            
            % 检查行
            if any(board(row, :) == num)
                valid = false;
                return;
            end
            
            % 检查列
            if any(board(:, col) == num)
                valid = false;
                return;
            end
            
            % 检查3x3宫格
            startRow = 3 * floor((row-1)/3) + 1;
            startCol = 3 * floor((col-1)/3) + 1;
            
            if any(any(board(startRow:startRow+2, startCol:startCol+2) == num))
                valid = false;
                return;
            end
            
            valid = true;
        end
        
        function valid = isValidSudoku(obj, board)
            % ISVALIDSUDOKU 检查数独题目是否有效
            
            % 检查行
            for row = 1:9
                rowValues = board(row, :);
                rowValues = rowValues(rowValues ~= 0);
                if length(rowValues) ~= length(unique(rowValues))
                    valid = false;
                    return;
                end
            end
            
            % 检查列
            for col = 1:9
                colValues = board(:, col);
                colValues = colValues(colValues ~= 0);
                if length(colValues) ~= length(unique(colValues))
                    valid = false;
                    return;
                end
            end
            
            % 检查3x3宫格
            for boxRow = 1:3
                for boxCol = 1:3
                    startRow = 3 * (boxRow-1) + 1;
                    startCol = 3 * (boxCol-1) + 1;
                    
                    boxValues = board(startRow:startRow+2, startCol:startCol+2);
                    boxValues = boxValues(boxValues ~= 0);
                    
                    if length(boxValues) ~= length(unique(boxValues))
                        valid = false;
                        return;
                    end
                end
            end
            
            valid = true;
        end
        
        function clearBoard(obj)
            % CLEARBOARD 清除数独棋盘
            obj.initializeBoard();
            obj.updateDisplay();
            set(obj.statusText, 'String', '棋盘已清除');
            obj.isSolved = false;
        end
        
        function loadExample(obj)
            % LOADEXAMPLE 加载示例数独题目
            
            % 一个中等难度的数独示例
            example = [
                5, 3, 0, 0, 7, 0, 0, 0, 0;
                6, 0, 0, 1, 9, 5, 0, 0, 0;
                0, 9, 8, 0, 0, 0, 0, 6, 0;
                8, 0, 0, 0, 6, 0, 0, 0, 3;
                4, 0, 0, 8, 0, 3, 0, 0, 1;
                7, 0, 0, 0, 2, 0, 0, 0, 6;
                0, 6, 0, 0, 0, 0, 2, 8, 0;
                0, 0, 0, 4, 1, 9, 0, 0, 5;
                0, 0, 0, 0, 8, 0, 0, 7, 9;
            ];
            
            obj.board = example;
            obj.originalBoard = example;
            obj.isSolved = false;
            obj.updateDisplay();
            set(obj.statusText, 'String', '示例已加载');
        end
        
        function delete(obj)
            % DELETE 析构函数
            if ishandle(obj.fig)
                delete(obj.fig);
            end
        end
    end
    
    methods (Static)
        function run()
            % RUN 启动数独求解器
            solver = SudokuSolver();
        end
        
        function solveFromCommandLine(board)
            % SOLVEFROMCOMMANDLINE 从命令行求解数独
            % 输入: 9x9矩阵,0表示空格
            
            if nargin == 0
                % 如果没有提供输入,使用示例
                board = [
                    5, 3, 0, 0, 7, 0, 0, 0, 0;
                    6, 0, 0, 1, 9, 5, 0, 0, 0;
                    0, 9, 8, 0, 0, 0, 0, 6, 0;
                    8, 0, 0, 0, 6, 0, 0, 0, 3;
                    4, 0, 0, 8, 0, 3, 0, 0, 1;
                    7, 0, 0, 0, 2, 0, 0, 0, 6;
                    0, 6, 0, 0, 0, 0, 2, 8, 0;
                    0, 0, 0, 4, 1, 9, 0, 0, 5;
                    0, 0, 0, 0, 8, 0, 0, 7, 9;
                ];
            end
            
            % 创建求解器实例
            solver = SudokuSolver();
            solver.board = board;
            solver.originalBoard = board;
            solver.updateDisplay();
            
            % 求解
            solver.solveSudoku();
            
            % 显示结果
            if solver.isSolved
                fprintf('数独求解成功:\n');
                disp(solver.board);
            else
                fprintf('数独无解\n');
            end
        end
    end
end

方法一:使用图形界面

  1. 在MATLAB命令窗口中运行:

    SudokuSolver.run();
    
  2. 在打开的图形界面中:

    • 点击格子可以循环输入数字1-9,再次点击会清零
    • 也可以使用键盘直接输入数字(0-9)
    • 点击"求解"按钮求解数独
    • 点击"清除"按钮清空棋盘
    • 点击"加载示例"按钮加载一个示例数独

方法二:从命令行求解

% 定义一个数独矩阵(0表示空格)
sudokuBoard = [
    5, 3, 0, 0, 7, 0, 0, 0, 0;
    6, 0, 0, 1, 9, 5, 0, 0, 0;
    0, 9, 8, 0, 0, 0, 0, 6, 0;
    8, 0, 0, 0, 6, 0, 0, 0, 3;
    4, 0, 0, 8, 0, 3, 0, 0, 1;
    7, 0, 0, 0, 2, 0, 0, 0, 6;
    0, 6, 0, 0, 0, 0, 2, 8, 0;
    0, 0, 0, 4, 1, 9, 0, 0, 5;
    0, 0, 0, 0, 8, 0, 0, 7, 9;
];

% 求解数独
SudokuSolver.solveFromCommandLine(sudokuBoard);

参考代码 数独求解程序 www.youwenfan.com/contentcnf/23166.html

说明

这个数独求解器使用回溯算法,这是一种经典的暴力搜索算法,适用于解决约束满足问题如数独。算法步骤如下:

  1. 找到下一个空单元格
  2. 尝试填入数字1-9
  3. 检查填入的数字是否满足数独规则(行、列、3x3宫格内无重复)
  4. 如果有效,递归尝试解决剩下的部分
  5. 如果所有数字都无效,回溯到上一步

这个数独求解器展示了MATLAB在实现算法和创建图形用户界面方面的强大能力。

posted @ 2025-09-01 11:41  令小飞  阅读(37)  评论(0)    收藏  举报