接下来开始我们的程序,首先定义类!

数独是由9*9的81个小格子组成,所以很容易我们会想到把每个小格子看做一个类,整个数独是由81个小格子的9*9的二维数组组成。

1.既然把一个单元格看做一个类,那它具有哪些属性呢?首先肯定需要有一个Value属性表示单元格的值,还没赋值的单元格我们默认将它赋值为0。

2.还有就是上一篇我们提到的Candidate属性表示候选数。候选数当然不一定只有一个,所以他是一个int类型的列表。

3.我们还需要一个DuplicateDel属性,该属性是一个字典类型的列表,主键和值都是int类型,这个属性用来记录被重复删除的候选数的值和次数。在我们后面的程序中会详细说明此属性的意义,为了照顾心急的朋友,在这里我简单说明一下:显然,当我们为单元格填数的时候会相应删除掉与之相关的单元格中该值的候选数,但是如果某些相关单元格中本来就已经没有此候选数,将会重复删除,不会有任何效果。但是一旦我们尝试填数失败返回到上一步,便需要将候选数从这些相关单元格恢复,但是如果是全部恢复,将会和最初状态不一致!所以需要一个属性来记录!越说越糊涂了吧,所以嘛,我都说了后面会详细说明的,暂时先不管这个属性吧,用到再说。

下面是定义好的类,啥也不多说,上图!

代码

    /// <summary>
    /// 单元格
    /// </summary>
    class Cell
    {
        private List<int> candidate; //候选数
        public List<int> Candidate
        {
            get
            {
                if (candidate == null)
                    candidate = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
                return candidate;
            }
            set { candidate = value; }
        }
        private Dictionary<int, int> duplicateDel; //重复删除候选数次数的记录,以便恢复       
        public Dictionary<int, int> DuplicateDel
        {
            get
            {
                if (duplicateDel == null)
                    duplicateDel = new Dictionary<int, int>();
                return duplicateDel;
            }
            set { duplicateDel = value; }
        }
        private int value = 0;
        //单元格的值
        public int Value { get{ return this.value; }set { this.value = value; } }
    }

看上面的代码,很明显,初始化的时候每个单元格的候选数都有1~9这九个数。

定义好类了,下面我们将开始写方法了!

  • 方法

我门统一把要用到的所有方法放在一个CellMethod类里面。

SetValue

首先我们先写最简单的方法,那就是为单元格赋值。其实就是把单元格类的Value属性赋值。很显然,这个赋值不能从1~9挨个遍历去试,那样就会向上一篇文章中所说的,我们的电脑又会杯具了。既然我们有候选数这个属性,为什么不用呢?根据候选数的定义,一个单元格所能填的数只能从这个单元格的候选数列表中选。所以我们将会从Candidate属性中选一个值来赋给Value属性。赋值完成后我们还需要把这个赋过的值从Candidate属性中移除。见下图:

代码

/// <summary>
/// 为单元格设定值
/// </summary>
/// <param name="cell">单元格</param>
/// <returns>返回结果</returns>
public bool SetValue(Cell cell)
{
    if (cell.Value != 0)
    {
        throw new InvalidOperationException("异常!不能重复对一个单元格进行赋值!");
    }
    if (cell.Candidate.Count == 0)
    {
        return false;
    }
    //随机选取单元格中的一个候选数为值,并移除该候选数
    series = random.Next(0, cell.Candidate.Count - 1);
    cell.Value = cell.Candidate[series];
    cell.Candidate.RemoveAt(series);
    return true;
}

这里我们事先对单元格逻辑进行了异常处理:

1.如果是这个单元格的值不等于0,那证明这个单元格是已经有值的,于是我们便不能为它赋值了!

2.如果这个单元格的候选数列表的个数为0,则表示这个单元格没有候选数可选了,这就意味着这个单元格取1~9这9个数都不会满足要求,这是我们返回一个标志false,表示赋值失败。

还要注意一点就是,这里我们定义了一个series变量,他是产生的一个随机数。其实最初我这个程序是直接写0,即是永远取候选数列表中的第一个赋值,并移除第一个候选数。但是那样每次都会生成一样的数独,因为取数的顺序是一样的,每次生成的结果必然一样。所以这里用到了一个随机产生的数,每次为单元格赋值的时候,随机从候选数列表中取一个数赋值,这样每次运行这个程序生成的数独都不会一样了。

这里series变量和random变量都是定义在类中的,而没定义在方法中,所以截图中没有他们的定义。

RemoveCellCandidate

下面我们将一起来看移除单元格候选数的方法:

代码

/// <summary>
/// 移除单元格的候选数
/// </summary>
/// <param name="table">表</param>
/// <param name="i">行</param>
/// <param name="j">列</param>
/// <param name="index">索引</param>
/// <returns>返回结果</returns>
static private bool RemoveCellCandidate(Cell[,] table, int i, int j, int index)
{
    int value = table[index / 9, index % 9].Value;
    bool flag = true;
    if (table[i, j].Candidate.Contains(value))
    {//如果单元格候选数有此数,移除之
        table[i, j].Candidate.Remove(value);
        if (table[i, j].Candidate.Count == 0 && table[i, j].Value == 0)
        {//如果单元格移除此候选数之后,并未赋值且候选数量为0,则失败,回滚
            flag = false;
        }
    }
    else if (table[i, j].DuplicateDel.ContainsKey(value))
    {//如果单元格候选数没有此数,且在重复删除的字典里有此数,则重复删除字典此数对应的键值的值+1
        table[i, j].DuplicateDel[value]++;
    }
    else
    {//如果单元格候选数没有此数,且在重复删除的字典里没有此数,则重复删除字典添加此数的键值,并赋值为1
        table[i, j].DuplicateDel.Add(value, 1);
    }
    return flag;
}

这个方法有很多东西要说,一个一个来。table表示这个9*9的二维数独,所以它的类型是Cell[,],我们定义的时候定义的是Cell[9,9]。i,j表示这个要处理的单元格的行列标号,即第i行第j列(从0开始数)。index表示被赋值的那个单元格的索引。这个索引需要解释一下,从第一行开始数,数完一行换行继续数,具体的索引对应单元格见下图:

其实索引也可以用i,j来表示:如果是对应同一个单元格,index=i*9+j;

i,j当然也可以用索引表示:i=index/9,j=index%9("/"表示除后的商,"%"表示除后的余数)。但是,此处的index和i,j是指的不同的单元格。例如:我们需要为单元格A赋值,假设从A的候选数中我们选出了值1,那么我们将需要找到与单元格A相关(相关的意义见文章一)的其他单元格,将这些单元格中的候选数1移除。假如现在我们发现单元格B与单元格A相关,那么我们要移除B中的候选数1,这是我们为这个方法传递的参数i,j就是B的行列标号1,3,参数index就是A的索引31,见下图:

其中用蓝色框起来的区域,除了A本身以外,都是叫A的相关单元格。B只是其中一个。

现在我们再看看这个方法里我们都做了些什么事。假设当我们为单元格A赋值1的时候,我们需要更新单元格A的相关单元格的候选数,这就是我们这个方法要做的。我们现在只讨论单元格B的变动,其他相关单元格变动与B一样。

情况1.假设在为A赋值之前,B有候选数1,2,3,6,8,9。为A赋值之后,B的候选数将变为2,3,6,8,9。候选数1将被移除。

情况2.假设在为A赋值之前,B有候选数2,3,6,8,9。为A赋值之后,B的候选数将还是2,3,6,8,9。

这样将会产生一个问题!如果是A赋值1后,我们发现无论如何都不能成功生成数独,那么我们便知道,A的值不能等于1。这时我们便需要为A重新赋值另外一个数。而这个时候,与A相关的单元格会有什么变化呢?既然A不等于1了,那也就是说,A的相关单元格的候选数1又可以使用了。我们把它称为候选数的还原。这时,如果是情况1,B的候选数会正确还原到为A赋值之前的状态。但如果是情况2,就不那么走运了。在为A赋值之前,B本身就没有候选数1,但这时如果恢复候选数,那么B的候选数将变为1,2,3,6,8,9。这很明显不是最开始B的状态,所以属性DuplicateDel就诞生了,它就是用来处理这个问题的!看下面的图:

情况1就不用解释了,我主要说明一下情况2、3。

情况2就是刚才我们所说的情况,现在有了属性DuplicateDel就好办了,如果是在移除候选数1时我们发现B中候选数本来就没有1,那么就去找DuplicateDel列表,看里面有没有主键是1的,发现没有,那么我们就添加一条记录,主键是1,值是1,表示1这个候选数我们重复删除了1次。在恢复的时候,我们先去找DuplicateDel列表,看里面有没有主键是1的。发现有一条,那么就把他的值减一,如果减完后发现主键1的值为0,那么把它从DuplicateDel列表中删除。

情况3是另一种情况,我们首先发现候选数没有1,又发现DuplicateDel列表中已经有了主键1,他的值为2,这就意味着1这个候选数已经被重复删除了2次了。现在我们又要删除1,说明这将是1的第3次,因此DuplicateDel列表中为主键1的值加1,变成3。当我们要恢复时,便再从主键1的值中减去1。

以上三种情况包括了所有删除候选数1的情况,由图可以看出,有了DuplicateDel列表,我们都可以完美的移除候选数,恢复候选数,而不会引起错误。

本篇内容讲了2个方法,一个SetValue,一个RemoveCellCandidate,下一篇我们将讲述另外两个方法:恢复候选数RecoverCellCandidate和处理相关单元格DealRelativeCell

转载于:https://www.cnblogs.com/cdts_change/archive/2010/01/24/1654990.html

数独的优化回朔算法(二)相关推荐

  1. Levenshtein distance:算法整理 与 编辑操作推断 【回朔 + Python】

    本文讨论通过Levenshtein distance和单源最短路径搜索算法来推断两个字符串(句子)之间最佳的编辑操作序列.使用到的知识有:动态规划 和 单源最短路径搜索算法. Levenshtein ...

  2. 基于 2D 激光雷达和实时回环优化的 SLAM 算法

    基于 2D 激光雷达和实时回环优化的 SLAM 算法 原文: https://www.ixueshu.com/document/771d692c7d3c0c40318947a18e7f9386.htm ...

  3. 分油问题回朔法c语言算法,用回溯法求“韩信分油”问题所有解

    裴南平 摘要:回溯法是一种常用的计算机程序设计方法.使用回溯法解决"韩信分油问题"也称"泊松分酒问题",在算法中保存每一步执行的中间结果,程序扩展前,判斷程序是 ...

  4. 从零开始学数据结构和算法(二)线性表的链式存储结构

    链表 链式存储结构 定义 线性表的链式存储结构的特点是用一组任意的存储单元的存储线性表的数据元素,这组存储单元是可以连续的,也可以是不连续的. 种类 结构图 单链表 应用:MessageQueue 插 ...

  5. 【一起去大厂系列】什么是回表查询?怎么优化回表查询?

    提到什么是回表查询之前,不得不先解释一下InnoDB的索引. InnoDB的索引 InnoDB有两大类索引,一类是聚集索引(Clustered Index),一类是普通索引(Secondary Ind ...

  6. c语言五子棋人工智能算法,五子棋人工智能算法实现研究,优化五子棋智能算法的思路...

    五子棋是一种两人对弈的纯策略型棋类游戏,是起源于中国古代的传统黑白棋种之一.现代五子棋日文称之为"连珠",英译为"Renju",英文称之为"Goban ...

  7. 数据科学和机器学习中的优化理论与算法(下)

    数据科学和机器学习中的优化理论与算法(下) 数据科学和机器学习当前越来越热,其中涉及的优化知识颇多.很多人在做机器学习或者数据科学时,对其中和优化相关的数学基础,包括随机梯度下降.ADMM.KKT 条 ...

  8. 数据科学和机器学习中的优化理论与算法(上)

    数据科学和机器学习中的优化理论与算法(上) 数据科学和机器学习当前越来越热,其中涉及的优化知识颇多.很多人在做机器学习或者数据科学时,对其中和优化相关的数学基础,包括随机梯度下降.ADMM.KKT 条 ...

  9. 精英反向学习与黄金正弦优化的HHO算法

    文章目录 一.理论基础 1.哈里斯鹰优化算法(HHO) 2.融合精英反向学习与黄金正弦的EGHHO (1)精英反向学习机制 (2)黄金正弦算法 (3)EGHHO优化算法 二.实验仿真与结果分析 三.参 ...

  10. 文章品读:基于多个指标的多目标优化随机排序算法(SRA)

    文章品读:基于多个指标的多目标优化随机排序算法(Stochastic Ranking Algorithm for Many-Objective Optimization Based on Multip ...

最新文章

  1. node 压缩图片_1Mb压缩成100k,图片无损压缩我选择它
  2. SAP QM 内向交货单在完成包装之后就自动触发了检验批?
  3. CSS 温故知新 CSS垂直居中
  4. boost::property_tree模块自定义 ptree 的 data_type 需要执行的操作
  5. nginx模块开发—HTTP初始化之listen
  6. 提升Azure App Service的几个建议
  7. 【JZOJ3885】【长郡NOIP2014模拟10.22】搞笑的代码
  8. 代下单、抢红包,“羊毛党”们如何薅垮上市公司?
  9. java 数据类型之原码,补码,反码
  10. Android 小項目之--猜名字有獎!RadionButton 和RadionGroup應用(附源碼)
  11. 动手组装深度学习机器+RTX2070Super
  12. spingboot视图层实例(JSP+Freemaker)以及自定义配置读取
  13. centos7.2 部署k8s集群
  14. 常见公文写作规范,公文写作常见错误更正对照表!
  15. Linux安装pyaudio
  16. 贝塞尔曲线均匀插值算法
  17. 推荐三款好用的终端开发平台
  18. 嵌入式Linux工程师的成长经历
  19. MongoDB $lookup函数实现两个表的关联查询+筛选+取特定值
  20. Carsim2019_官方Help学习笔记_Running a VS FMU in Simulink (Carsim与Matlab联合仿真方法之一)

热门文章

  1. AU更新 AUTOIT
  2. XSRF(XSS+CSRF)
  3. [USACO08JAN]牛大赛Cow Contest
  4. MySQL Binlog--binlog_format参数
  5. 【Linux命令】Linux下的tar压缩解压缩命令详解(转)
  6. 构建基本脚本(转)*****好文章*****
  7. 是因为Session只能让服务器在一次连续的会话中记住你,而Cookie是记住浏览器一段时间...
  8. Sharepoint学习笔记—ECM系列—找不到Content Type Publishing链接
  9. AS数据库自动备份的DOS语句
  10. loadrunner- winsock 函数 一览表