数独的优化回朔算法(四)

FillCell

这个方法是填充数独表的递归方法,看长长滴代码:

代码
/// <summary>
/// 填充单元格
/// </summary>
/// <param name="table"></param>
/// <param name="index">索引</param>
/// <returns>返回结果</returns>
private bool FillCell(Cell[,] table, int index)
{
    RelativeCellMethod removeCandidateMethod 
= new RelativeCellMethod(RemoveCellCandidate);
    RelativeCellMethod recoverCandidateMethod 
= new RelativeCellMethod(RecoverCellCandidate);

    
if (index >= 81)
    {
//如果索引超出范围,则表示数独已成功生成,直接返回
        return true;
    }
    
if (table[index / 9, index % 9].Value != 0)
    {
//如果索引的单元格已赋值,则直接跳到下一个索引赋值
        return FillCell(table, index + 1);
    }
    
bool flag = true;
    List
<int> nextCandidates = new List<int>();
    
//预先保存好改单元格的候选数序列,如果所有候选数都不成功,则把候选数全部还原之后再返回
    nextCandidates.AddRange(table[index / 9, index % 9].Candidate);

    
while (table[index / 9, index % 9].Candidate.Count > 0 && flag)
    {
//如果单元格候选数个数大于0,且标记为真,则循环试探候选数
        SetValue(table[index / 9, index % 9]);//为单元格赋值
        flag &= DealRelativeCell(removeCandidateMethod, table, index);//移除相关单元格的对应这个值的候选数
        if (!flag)
        {
//如果移除候选数失败,则恢复候选数,并继续下个循环
            DealRelativeCell(recoverCandidateMethod, table, index);
        }
        
else
        {
//如果移除候选数成功,则继续试探填充下一个单元格
            flag &= FillCell(table, index + 1);
            
if (!flag)
            {
//如果填充下一个单元格失败,则恢复候选数,并继续下个循环
                DealRelativeCell(recoverCandidateMethod, table, index);
            }
            
else
            {
//如果填充下一个单元格成功,则直接返回(运行到这里肯定表示整个数独已成功生成!)
                return true;
            }
        }
        flag 
= !flag;//把标志取反,继续下个循环
    }
    
if (table[index / 9, index % 9].Candidate.Count == 0)
    {
//如果所有候选数都是过了且全部失败,恢复此单元格的候选数,并返回false
        table[index / 9, index % 9].Candidate.AddRange(nextCandidates);
        
return false;
    }
    
return flag;
}

前两行定义了两个代理方法,在while方法中,一个一个的试单元格的候选数填入到单元格中,如果填入失败,则恢复候选数,继续尝试下一个候选数,如果所有候选数都不能正确生成数独,那么跳出while循环,函数返回false,返回到上一个单元格,将上一个单元格的值重置为0,试另外的候选数,以此类推。直到索引index大于等于81,则表示所有单元格都已成功赋值,那么全部返回true。

接下来只需要写一个显示数独的方法:

 

Show

如下:

代码
/// <summary>
/// 显示数独
/// </summary>
/// <param name="table"></param>
private void Show(Cell[,] table)
{
    
for (int i = 0; i < 9; i++)
    {
        
for (int j = 0; j < 9; j++)
        {
            Console.Write(
"{0,2}", table[i, j].Value);

        }
        Console.WriteLine();
    }
    Console.WriteLine(
"----------------------------------------------");

}

还有一个初始化数独表,然后开始填充数独的方法:

StartFillTable

代码
/// <summary>
/// 开始生成数独
/// </summary>
public void StartFillTable()
{
    Cell[,] table 
= new Cell[99];
    
while (true)
    {
        
for (int i = 0; i < 9; i++)
            
for (int j = 0; j < 9; j++//初始化数独表
                table[i, j] = new Cell();

        
bool flag = FillCell(table, 0);//填充数独表
        if (flag)//如果生成数独成功,则显示这个数独
            Show(table);
        Console.ReadKey();
    }
}

我是用控制台应用程序编写的,如果是用Silverlight或者其他的呈现会更好看!,下面是Main方法和结果显示:

 

class Program
{
    
static void Main(string[] args)
    {
        CellMethod method 
= new CellMethod();
        method.StartFillTable();
    }
}

只要按回车键就可以生成一个不一样的新数独!

对于有兴趣的读者,如果是要继续开发成数独游戏,可以随机的遮罩一定数量的格子,难度越高遮罩的格子越多,这样能保证的是数独游戏一定有解,但是解不一定唯一。如果要保证解唯一,那么就需要再用一个类似这个的解数独的方法,让计算机先做我们遮罩后的数独,看是不是有且仅有一个解,如果不是,那么重新选择遮罩区域,直到计算机发现解唯一为止,然后输出到前端,给玩家来玩!

下一篇是整个程序的源代码。

原文地址:https://www.cnblogs.com/cdts_change/p/1656260.html