數獨游戲求解:解法適用於任意階數的數獨


第一節 數獨簡介

  數獨是一種數字演算游戲。

  分類

  4 階:可填數字范圍 1~4,宮格2階

  9 階:可填數字范圍 1~9,宮格3階

  16 階:可填數字范圍 1~16,宮格4階 *圖見附錄  

  規則

  以九階數獨為例,玩家需要根據 9×9 盤面上的已知數字,推理出所有剩余空格的數字

  填入的數字需滿足每一行、每一列、每一宮內的數字均含 1-9,且不重復

圖1-1 四階數獨

圖1-2 九階數獨

第二節 數獨的表示

  編程中,對於 N 階數獨可以用一個 N*N 的二維數組表示。

  數獨階數

  數獨階數 GridRank = N

  宮格階數 SubGridRank = Sqrt(N)

  數獨包含宮的階數 SubGridIncludeRank = Sqrt(N)

  數值范圍

  可填最大數字 MaxValue = N

  可填最小數字 MinValue = 1 

  任意階數的數獨

  任意階數獨的表示(N≠K2,K>1,K∈Z

  任意階數獨僅有一個宮,所以數獨階數和宮階數相等

  以 7 階為例,設置 GridRank = 7,SubGridRank = 7,SubGridIncludeRank = 1,即可

圖2-1 數獨結構解析

第三節 數獨的求解

  采用回溯法,直接尋找到一個空白格,嘗試填入所有可能的數字,繼續填下一個空白格,直至填滿或者不能繼續填入為止(不符合規則)。

  尋找空白格

  首先填入哪個空白格至關重要,不同的格子意味着不同的迭代和搜索次數

  當一個格子的限制越多,也就是可填入數字越少時,優先選擇該空白格,這樣搜索次數最少

  填充有效值

  當確定空白格后就需要向其中填入數字了一一篩選出符合規則的數字

  規則很簡單,格子當前行,當前列,當前宮不能有相同數值出現

  回溯法求解

  遞歸尋找下一個空白格並填入有效值,直至中途不能再填值(不能解決的沖突)或者得出正確答案

  出現不能解決的沖突時,回到上一個空白格,換一個有效值繼續迭代搜索

附錄

  VB.NET 類 & C# 類,在線工具轉換,僅供參考。

Public Class SudokuClass
    Public Property Rank As Integer '數獨的階數
        Get
            Return GridRank
        End Get
        Set(ByVal value As Integer)
            GridRank = value
            SubGridRank = CType(Math.Sqrt(value), Integer)
            SubGridIncludeRank = SubGridRank
            DataMaxValue = value
            DataMinValue = 1
        End Set
    End Property
    Public Property GridData As Integer?(,) '數獨的數據
    Private GridRank As Integer '數獨的階數
    Private SubGridRank As Integer '子數獨(宮)的階數
    Private SubGridIncludeRank As Integer '數獨包含子數獨(宮)的階數
    Private DataMaxValue As Integer '數獨可填最大數值
    Private DataMinValue As Integer '數獨可填最小數值
    ''' <summary>
    ''' 實例化一個指定階數的數獨類
    ''' </summary>
    ''' <param name="nRank">指定的階數</param>
    Public Sub New(nRank As Integer)
        Me.Rank = nRank
    End Sub

    Public Function GenerateInitialNumbers() As Integer?(,)
        ReDim GridData(GridRank - 1, GridRank - 1)
        For i = 0 To GridRank - 1
            For j = 0 To GridRank - 1
                GridData(i, j) = 0 '暫無初始數字生成規則,請從數獨文件導入
            Next
        Next
        Return GridData '返回一個空白數獨
    End Function

    Public Function IsImpossible(ByVal Numbers As Integer?(,)) As Boolean
        Dim temp As Integer?
        For i = 0 To GridRank - 1
            For j = 0 To GridRank - 1
                If Not Numbers(i, j) = 0 Then
                    temp = Numbers(i, j)
                    Numbers(i, j) = 0
                    If GetExisting(Numbers, i, j, CInt(temp)) Then Numbers(i, j) = temp : Return True
                    Numbers(i, j) = temp
                End If
            Next
        Next
        Return False
    End Function
    Public Function IsWin(ByVal Numbers As Integer?(,)) As Boolean
        For i = 0 To GridRank - 1
            For j = 0 To GridRank - 1
                If Not Numbers(i, j).HasValue Then Return False '出現空格
            Next
        Next
        Dim TempInt As New List(Of Integer)
        '判斷行重復
        For i = 0 To GridRank - 1
            TempInt.Clear()
            For j = 0 To GridRank - 1
                TempInt.Add(CInt(Numbers(i, j)))
            Next
            If IsDuplicate(TempInt.ToArray) Then Return False
        Next
        '判斷列重復
        For j = 0 To GridRank - 1
            TempInt.Clear()
            For i = 0 To GridRank - 1
                TempInt.Add(CInt(Numbers(i, j)))
            Next
            If IsDuplicate(TempInt.ToArray) Then Return False
        Next
        '判斷宮格重復
        For i = 0 To GridRank - 1 Step SubGridRank
            For j = 0 To GridRank - 1 Step SubGridRank
                TempInt.Clear()
                For i2 = 0 To SubGridRank - 1
                    For j2 = 0 To SubGridRank - 1
                        TempInt.Add(CInt(Numbers(i + i2, j + j2)))
                    Next
                Next
                If IsDuplicate(TempInt.ToArray) Then Return False
            Next
        Next
        Return True
    End Function
    ''' <summary>
    ''' 判斷一個序列是否有重復數字
    ''' </summary>
    ''' <param name="Numbers"></param>
    ''' <returns></returns>
    Private Function IsDuplicate(ByVal Numbers() As Integer) As Boolean
        Array.Sort(Numbers)
        If Numbers.Length > 1 Then
            For i = 0 To Numbers.Length - 2
                If Numbers(i) = Numbers(i + 1) Then Return True
            Next
        End If
        Return False
    End Function
    ''' <summary>
    ''' 返回指定位置的所有可填數字的序列
    ''' </summary>
    ''' <param name="Numbers">原數組</param>
    ''' <param name="gX">指定的位置的X值,從0開始</param>
    ''' <param name="gY">指定的位置的Y值,從0開始</param>
    ''' <returns></returns>
    Public Function GetEnabledNum(ByVal Numbers As Integer?(,), gX As Integer, gY As Integer) As Integer()
        Dim NumList As New List(Of Integer)
        For i = DataMinValue To DataMaxValue
            If GetExisting(Numbers, gX, gY, i) = False Then NumList.Add(i)
        Next
        Return NumList.ToArray
    End Function
    '遞歸求解數獨
    Private Function GetValue(ByVal gData As Integer?(,)) As List(Of Integer?(,))
        Dim ResultList As New List(Of Integer?(,))
        Dim i, j As Integer
        Dim tempPoint As Point = getStartPoint(gData)
        i = tempPoint.X : j = tempPoint.Y
        If i >= 0 AndAlso j >= 0 Then
            For value = DataMinValue To DataMaxValue
                If GetExisting(gData, i, j, value) = False Then
                    gData(i, j) = value
                    GetValue(gData)
                    gData(i, j) = 0
                End If
            Next
        Else
            '新增一個結果
            ResultList.Add(gData)
        End If
        Return ResultList
    End Function
    '查找當前空白格(最佳格)
    Private Function getStartPoint(ByRef data As Integer?(,)) As Point
        Dim gPoint As Point
        Dim tempValue As Integer
        Dim maxValue As Integer
        '查找限制最多的空白格
        For i = 0 To GridRank - 1
            For j = 0 To GridRank - 1
                If data(i, j) = 0 Then
                    tempValue = 0
                    For k = 0 To GridRank - 1
                        If data(i, k) > 0 Then tempValue += 1
                        If data(k, j) > 0 Then tempValue += 1
                        If data((i \ SubGridIncludeRank) * SubGridIncludeRank + k \ SubGridIncludeRank, (j \ SubGridIncludeRank) * SubGridIncludeRank + (k Mod SubGridIncludeRank)) > 0 Then tempValue += 1
                    Next
                    If tempValue > maxValue Then
                        maxValue = tempValue
                        gPoint.X = i
                        gPoint.Y = j
                    End If
                End If
            Next
        Next
        If maxValue > 0 Then
            Return gPoint
        Else
            gPoint.X = -1
            gPoint.Y = -1
            Return gPoint
        End If
    End Function
    '判斷同行同列同宮是否已經存在
    Private Function GetExisting(ByRef data As Integer?(,), ByVal gX As Integer, ByVal gY As Integer, ByVal gValue As Integer) As Boolean
        For k = 0 To GridRank - 1
            If data(gX, k) = gValue OrElse data(k, gY) = gValue OrElse data((gX \ SubGridIncludeRank) * SubGridIncludeRank + k \ SubGridIncludeRank, (gY \ SubGridIncludeRank) * SubGridIncludeRank + (k Mod SubGridIncludeRank)) = gValue Then
                Return True
            End If
        Next
        Return False
    End Function
End Class
VB.NET
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class SudokuClass
{
    public int Rank {
        //數獨的階數
        get { return GridRank; }
        set {
            GridRank = value;
            SubGridRank = Convert.ToInt32(Math.Sqrt(value));
            SubGridIncludeRank = SubGridRank;
            DataMaxValue = value;
            DataMinValue = 1;
        }
    }
    public int?[,] GridData { get; set; }
    //數獨的數據
        //數獨的階數
    private int GridRank;
        //子數獨(宮)的階數
    private int SubGridRank;
        //數獨包含子數獨(宮)的階數
    private int SubGridIncludeRank;
        //數獨可填最大數值
    private int DataMaxValue;
        //數獨可填最小數值
    private int DataMinValue;
    /// <summary>
    /// 實例化一個指定階數的數獨類
    /// </summary>
    /// <param name="nRank">指定的階數</param>
    public SudokuClass(int nRank)
    {
        this.Rank = nRank;
    }

    public int?[,] GenerateInitialNumbers()
    {
        GridData = new Nullable<int>[GridRank, GridRank];
        for (i = 0; i <= GridRank - 1; i++) {
            for (j = 0; j <= GridRank - 1; j++) {
                GridData[i, j] = 0;
                //暫無初始數字生成規則,請從數獨文件導入
            }
        }
        return GridData;
        //返回一個空白數獨
    }

    public bool IsImpossible(int?[,] Numbers)
    {
        int? temp = default(int?);
        for (i = 0; i <= GridRank - 1; i++) {
            for (j = 0; j <= GridRank - 1; j++) {
                if (!(Numbers[i, j] == 0)) {
                    temp = Numbers[i, j];
                    Numbers[i, j] = 0;
                    if (GetExisting(ref Numbers, i, j, Convert.ToInt32(temp))){Numbers[i, j] = temp;return true;}
                    Numbers[i, j] = temp;
                }
            }
        }
        return false;
    }
    public bool IsWin(int?[,] Numbers)
    {
        for (i = 0; i <= GridRank - 1; i++) {
            for (j = 0; j <= GridRank - 1; j++) {
                if (!Numbers[i, j].HasValue)
                    return false;
                //出現空格
            }
        }
        List<int> TempInt = new List<int>();
        //判斷行重復
        for (i = 0; i <= GridRank - 1; i++) {
            TempInt.Clear();
            for (j = 0; j <= GridRank - 1; j++) {
                TempInt.Add(Convert.ToInt32(Numbers[i, j]));
            }
            if (IsDuplicate(TempInt.ToArray()))
                return false;
        }
        //判斷列重復
        for (j = 0; j <= GridRank - 1; j++) {
            TempInt.Clear();
            for (i = 0; i <= GridRank - 1; i++) {
                TempInt.Add(Convert.ToInt32(Numbers[i, j]));
            }
            if (IsDuplicate(TempInt.ToArray()))
                return false;
        }
        //判斷宮格重復
        for (i = 0; i <= GridRank - 1; i += SubGridRank) {
            for (j = 0; j <= GridRank - 1; j += SubGridRank) {
                TempInt.Clear();
                for (i2 = 0; i2 <= SubGridRank - 1; i2++) {
                    for (j2 = 0; j2 <= SubGridRank - 1; j2++) {
                        TempInt.Add(Convert.ToInt32(Numbers[i + i2, j + j2]));
                    }
                }
                if (IsDuplicate(TempInt.ToArray()))
                    return false;
            }
        }
        return true;
    }
    /// <summary>
    /// 判斷一個序列是否有重復數字
    /// </summary>
    /// <param name="Numbers"></param>
    /// <returns></returns>
    private bool IsDuplicate(int[] Numbers)
    {
        Array.Sort(Numbers);
        if (Numbers.Length > 1) {
            for (i = 0; i <= Numbers.Length - 2; i++) {
                if (Numbers[i] == Numbers[i + 1])
                    return true;
            }
        }
        return false;
    }
    /// <summary>
    /// 返回指定位置的所有可填數字的序列
    /// </summary>
    /// <param name="Numbers">原數組</param>
    /// <param name="gX">指定的位置的X值,從0開始</param>
    /// <param name="gY">指定的位置的Y值,從0開始</param>
    /// <returns></returns>
    public int[] GetEnabledNum(int?[,] Numbers, int gX, int gY)
    {
        List<int> NumList = new List<int>();
        for (i = DataMinValue; i <= DataMaxValue; i++) {
            if (GetExisting(ref Numbers, gX, gY, i) == false)
                NumList.Add(i);
        }
        return NumList.ToArray();
    }
    //遞歸求解數獨
    private List<int?[,]> GetValue(int?[,] gData)
    {
        List<int?[,]> ResultList = new List<int?[,]>();
        int i = 0;
        int j = 0;
        Point tempPoint = getStartPoint(ref gData);
        i = tempPoint.X;
        j = tempPoint.Y;
        if (i >= 0 && j >= 0) {
            for (value = DataMinValue; value <= DataMaxValue; value++) {
                if (GetExisting(ref gData, i, j, value) == false) {
                    gData[i, j] = value;
                    GetValue(gData);
                    gData[i, j] = 0;
                }
            }
        } else {
            //新增一個結果
            ResultList.Add(gData);
        }
        return ResultList;
    }
    //查找當前空白格(最佳格)
    private Point getStartPoint(ref int?[,] data)
    {
        Point gPoint = default(Point);
        int tempValue = 0;
        int maxValue = 0;
        //查找限制最多的空白格
        for (i = 0; i <= GridRank - 1; i++) {
            for (j = 0; j <= GridRank - 1; j++) {
                if (data[i, j] == 0) {
                    tempValue = 0;
                    for (k = 0; k <= GridRank - 1; k++) {
                        if (data[i, k] > 0)
                            tempValue += 1;
                        if (data[k, j] > 0)
                            tempValue += 1;
                        if (data[(i / SubGridIncludeRank) * SubGridIncludeRank + k / SubGridIncludeRank, (j / SubGridIncludeRank) * SubGridIncludeRank + (k % SubGridIncludeRank)] > 0)
                            tempValue += 1;
                    }
                    if (tempValue > maxValue) {
                        maxValue = tempValue;
                        gPoint.X = i;
                        gPoint.Y = j;
                    }
                }
            }
        }
        if (maxValue > 0) {
            return gPoint;
        } else {
            gPoint.X = -1;
            gPoint.Y = -1;
            return gPoint;
        }
    }
    //判斷同行同列同宮是否已經存在
    private bool GetExisting(ref int?[,] data, int gX, int gY, int gValue)
    {
        for (k = 0; k <= GridRank - 1; k++) {
            if (data[gX, k] == gValue || data[k, gY] == gValue || data[(gX / SubGridIncludeRank) * SubGridIncludeRank + k / SubGridIncludeRank, (gY / SubGridIncludeRank) * SubGridIncludeRank + (k % SubGridIncludeRank)] == gValue) {
                return true;
            }
        }
        return false;
    }
}
C#

圖4-1 十六階數獨

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM