• 数独游戏求解:解法适用于任意阶数的数独


    第一节 数独简介

      数独是一种数字演算游戏。

      分类

      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 十六阶数独

  • 相关阅读:
    kubernetes进阶(一) kubectl工具使用详解
    二进制安装kubernetes(七) 部署知识点总结
    1024程序员节:这就是国内开发者的现状?
    php 伪协议
    浅谈 PHP 与手机 APP 开发(API 接口开发)
    比RBAC更好的权限认证方式(Auth类认证)
    PHP获得毫秒数
    2020年PHP面试题附答案(实战经验)
    分享几套2019年各大公司最新的PHP面试题,几斤几两一试便知
    PHP面试题2019年百度工程师面试题及答案解析
  • 原文地址:https://www.cnblogs.com/experdot/p/4839511.html
Copyright © 2020-2023  润新知