银河

SKYIV STUDIO

  博客园 :: 首页 ::  ::  :: 订阅 订阅 :: 管理 ::
  221 随笔 :: 2 文章 :: 2262 评论 :: 48 引用
数独游戏 在9x9的方格内进行, 分为3x3的小方格,被称为“区”。
数独游戏首先从已经填入数字的格子开始。
数独游戏的目的是根据下列规则,用1至9之间的数字填满空格:
每个数字在每一行、每一列和每一区只能出现一次。
我在 Linux 服务器(请参见“在 Linux 下运行 ASP.NET 2.0”)上用 ASP.NET 2.0 实现了一个数独解算器
http://www.sudoku.name 网站上也有一个用户界面相当不错的“数独解算器” ,但是其算法太差了,运算速度比我的算法慢多了。以其网站上的“#5328”谜题(也是我的数独解算器的例题)为例,它需要大约四个小时才能给出答案,而我的解算器不到一秒钟就可以给出答案。从它的运算过程来算,估计是逐个空格进行解算。而我的算法是先找出能填入数字个数最少的空格进行解算。算法这个微小的改进,就极大地提高了计算效率。好了,废话少说,下面就是源程序:

1. sudoku.aspx:
 1<%@ Page Language="C#" inherits="Skyiv.Ben.Web.SudokuPage" %>
 2<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
 3<html xmlns="http://www.w3.org/1999/xhtml" >
 4<head runat="server">
 5  <title>银河 - 数独</title>
 6</head>
 7<body>
 8  <form id="form1" runat="server">
 9  <asp:Button Text="返回" OnClick="BtnUriHome_Click" runat="server" />
10  <asp:Button Text="开始" OnClick="BtnSubmit_Click" runat="server" />
11  <hr />
12  <div>
13  <href="http://www.sudoku.name/index-cn.php" target="_blank">数独游戏</a>
14  在9x9的方格内进行, 分为3x3的小方格,被称为“区”。<br />
15  数独游戏首先从已经填入数字的格子开始。<br />
16  数独游戏的目的是根据下列规则,用1至9之间的数字填满空格:<br />
17  每个数字在每一行、每一列和每一区只能出现一次。<br />
18  </div>
19  <div>
20  <asp:TextBox Runat="Server" Id="tbxInput" MaxLength="512" Wrap="False"
21    TextMode="MultiLine" Columns="15" Rows="14" />
22  <asp:TextBox Runat="Server" Id="tbxOutput" ReadOnly="True" Wrap="False"
23    TextMode="MultiLine" Columns="15" Rows="14" />
24  </div>
25  </form>
26</body>
27</html>
28

2. sudoku.aspx.cs:
 1using System;
 2using System.IO;
 3using System.Web.UI;
 4using System.Web.UI.WebControls;
 5using Skyiv.Ben.Etc;
 6
 7namespace Skyiv.Ben.Web
 8{
 9  public class SudokuPage : Page
10  {
11    protected TextBox tbxInput;
12    protected TextBox tbxOutput;
13
14    public void Page_Load(object sender, EventArgs e)
15    {
16      if (IsPostBack) return;
17      tbxInput.Text =    "+---+---+---+\n|2..|||\n|8..|1..|.2.|\n"
18        + "|..5|.7.|.3.|\n+---+---+---+\n|.7.|.3.|1..|\n|.9.|.4.|.8.|\n"
19        + "|..4||.7.|\n+---+---+---+\n|.3.|.9.|6..|\n|.8.|..5|..3|\n"
20        + "|||..5|\n+---+---+---+";
21    }

22
23    public void BtnUriHome_Click(object sender, EventArgs e)
24    {
25      Response.Redirect(Pub.UriHome);
26    }

27
28    public void BtnSubmit_Click(object sender, EventArgs e)
29    {
30      try
31      {
32        Sudoku sudoku = new Sudoku(new StringReader(tbxInput.Text));
33        StringWriter writer = new StringWriter();
34        sudoku.Out(writer);
35        tbxOutput.Text = writer.ToString();
36      }

37      catch (Exception ex)
38      {
39        tbxOutput.Text = "Error: " + ex.Message;
40      }

41    }

42  }

43}

44

3. sudoku.cs:
  1using System;
  2using System.IO;
  3using System.Collections.Generic;
  4
  5namespace Skyiv.Ben.Etc
  6{
  7  sealed class Sudoku
  8  {
  9    byte[,] input, output;
 10    int steps = 0;
 11
 12    public Sudoku(TextReader reader)
 13    {
 14      input = new byte[99];
 15      for (int y = 0; y < 9; )
 16      {
 17        string s = reader.ReadLine();
 18        if (s == nullbreak;
 19        s = s.Replace('.''0');
 20        int x = 0;
 21        for (int i = 0; i < s.Length; i++)
 22          if (Char.IsDigit(s, i) && x < 9) input[x++, y] = (byte)(s[i] - '0');
 23        if (x != 0) y++;
 24      }

 25    }

 26
 27    public void Out(TextWriter writer)
 28    {
 29      Compute(input);
 30      Out(writer, output);
 31    }

 32
 33    void Out(TextWriter writer, byte[,] output)
 34    {
 35      for (int y = 0; y <= output.GetLength(1); y++)
 36      {
 37        if (y % 3 == 0) writer.WriteLine("+---+---+---+");
 38        if (y >= output.GetLength(1)) break;
 39        for (int x = 0; x <= output.GetLength(0); x++)
 40        {
 41          if (x % 3 == 0) writer.Write('|');
 42          if (x >= output.GetLength(0)) break;
 43          writer.Write((output[x, y] == 0? '.' : (char)(output[x, y] + '0'));
 44        }

 45        writer.WriteLine();
 46      }

 47    }

 48
 49    bool Compute(byte[,] input)
 50    {
 51      List<byte[,]> list = StepIt(input);
 52      if (list == nullreturn true;
 53      foreach (byte[,] temp in list) if (Compute(temp)) return true;
 54      return false;
 55    }

 56
 57    // return null for finish
 58    List<byte[,]> StepIt(byte[,] input)
 59    {
 60      if (steps++ > 100000throw new Exception("太复杂了");
 61      output = input;
 62      int theX = -1, theY = -1;
 63      byte[] theDigits = null;
 64      for (int y = 0; y < input.GetLength(1); y++)
 65      {
 66        for (int x = 0; x < input.GetLength(0); x++)
 67        {
 68          if (input[x, y] != 0continue;
 69          byte[] digits = GetDigits(input, x, y);
 70          if (digits.Length == 0return new List<byte[,]>();
 71          if (theDigits != null && theDigits.Length <= digits.Length) continue;
 72          theX = x;
 73          theY = y;
 74          theDigits = digits;
 75        }

 76      }

 77      if (theDigits == nullreturn null;
 78      List<byte[,]> result = new List<byte[,]>();
 79      foreach (byte digit in theDigits)
 80      {
 81        byte[,] temp = (byte[,])input.Clone();
 82        temp[theX, theY] = digit;
 83        result.Add(temp);
 84      }

 85      return result;
 86    }

 87
 88    byte[] GetDigits(byte[,] input, int x, int y)
 89    {
 90      bool[] mask = new bool[10];
 91      for (int i = 0; i < 9; i++)
 92      {
 93        mask[input[x, i]] = true;
 94        mask[input[i, y]] = true;
 95      }

 96      for (int i = x / 3 * 3; i < x / 3 * 3 + 3; i++)
 97        for (int j = y / 3 * 3; j < y / 3 * 3 + 3; j++)
 98          mask[input[i, j]] = true;
 99      List<byte> list = new List<byte>();
100      for (int i = 1; i < mask.Length; i++if (!mask[i]) list.Add((byte)i);
101      return list.ToArray();
102    }

103  }

104}

105

以上代码都很简单,也不需要再另外解说了。
posted on 2007-01-26 15:09 银河 阅读(10021) 评论(24) 编辑 收藏

评论

这个小游戏在这边叫logic 9,报纸每天都有一个题,呵呵.
 回复 引用 查看   

#2楼[楼主] 2007-01-26 19:02 银河      
@极地银狐.NET:
你那边是哪里? 新加坡吗? :)
 回复 引用 查看   

#3楼[楼主] 2007-01-26 20:02 银河      
增加对输入合法性的检查, 将 sudoku.cs 程序中的 27 - 31 行之间的语句替换为:
 1    public void Out(TextWriter writer)
 2    {
 3      Check(input);
 4      Compute(input);
 5      Out(writer, output);
 6    }

 7
 8    void Check(byte[,] input)
 9    {
10      byte[] bs = new byte[9];
11      for (int i = 0; i < 9; i++)
12      {
13        for (int k = 0; k < 9; k++) bs[k] = input[i, k];
14        Check(bs);
15        for (int k = 0; k < 9; k++) bs[k] = input[k, i];
16        Check(bs);
17      }

18      for (int y0 = 0; y0 < 3; y0++)
19      {
20        for (int x0 = 0; x0 < 3; x0++)
21        {
22          int k = 0;
23          for (int y = 0; y < 3; y++)
24            for (int x = 0; x < 3; x++)
25              bs[k++= input[x0 * 3 + x, y0 * 3 + y];
26          Check(bs);
27        }

28      }

29    }

30      
31    void Check(byte[] bs)
32    {
33      bool[] mask = new bool[10];
34      foreach (byte elem in bs)
35      {
36        if (elem == 0continue;
37        if (mask[elem]) throw new Exception("输入不合法");
38        mask[elem] = true;
39      }

40    }
 回复 引用 查看   

#4楼 2007-01-26 21:13 Hunts.C      
Mark:)
 回复 引用 查看   

#5楼 2007-01-26 23:04 双鱼座      
难得又在园子里发现一个数独爱好者。不过我所理解的数独并不全部是9X9的,而是4X4、9X9、16X16、25X25、......,甚至还有一些长宽不等的。唯一的规则就是:纵、横和小区块内不能有相同的内容,而且内容也不仅仅限于数字。例如25X25就可以用A~Y一共25个字母。根据数学原理,9X9仅仅是一个特例。你的算法必须适应所有的情况而不仅仅是9X9。另外,完整的数独算法还可以根据难度自动出题,且保证每题仅有唯一解。
以前闲的时候专门研究过一阵,后来太忙,放弃了,但是兴趣依然非常浓。不过我的思路和你不同。我采用位域算法,每行、每列、每个区块有两个位域记录,一个描述哪些空格已填,另一个描述哪些记号已用。在每个位置,行、列、区块的记号位域进行与运算,剩余的就是可选的。遍历寻找可选项最少的项进行试探。如果可选项数只有一项就可以直接填入,并修改相关的位域记录,再递归。
另外,网上有很多java的数独解法可以参考。C#的比较少,实际上用C#来实现数独解法比java更灵活。
 回复 引用 查看   

#6楼[楼主] 2007-01-26 23:45 银河      
@双鱼座:
非常感谢你的建议。
我想9x9的数独应该是最常用的。不过算法要改为适应其他情况也不太难。
根据难度自动出题,且保证每题仅有唯一解可能会比较复杂,有时间的话可以可以研究一下。
 回复 引用 查看   

#7楼 2007-04-14 16:14 Ray[未注册用户]
不知道 你是怎么测出http://www.sudoku.name是四个小时才算出来的
我去试了 也就一秒就出结果
 回复 引用   

#8楼 2007-04-14 20:01 Ray[未注册用户]
额。。。
我看错了,不好意思
 回复 引用   

#9楼 2007-05-03 20:02 文泽[未注册用户]
谢了... 又学到新东西了... :P
 回复 引用   

#10楼 2007-05-15 12:41 rose[未注册用户]
我用了你写的程序,但出现错误,在sudoku.aspx.cs中,blic class SudokuPage : Page 出现:类型“skyiv.ben.web.sudokupage”的声明上缺少partial修饰符;存在此类型的其它分部声明。

这个问题怎么解决?

麻烦回复邮件,谢谢。
 回复 引用   

我是菜鸟,你能直接给出你算法实现的一个页面或者可以运行的文件马?

我不懂代码,http://www.sudoku.name 这个算的实在是太慢了………………


谢谢!!!!!
 回复 引用   

#12楼[楼主] 2007-08-20 20:27 银河      
@罗红文
我在 Linux 服务器(请参见“在 Linux 下运行 ASP.NET 2.0”)上用 ASP.NET 2.0 实现了一个数独解算器

上面一段话就是我正文中给出的,其中的“数独解算器”这几个字是可以点击的,一点击就会给出我算法实现的一个页面。
我把该页面的地址给出如下:
http://ben.skyiv.com/ben/aspx/sudoku.aspx

 回复 引用 查看   

#13楼 2007-08-25 02:27 Randy[未注册用户]
有人研究jigsaw sudoku吗?
 回复 引用   

#14楼[楼主] 2007-08-25 08:33 银河      
@Randy
我用 google 搜索了一下, 找到以下网站: Jigsaw Sudoku , 确实是一个相当不错的在线的数独游戏。

 回复 引用 查看   

#include <stdio.h>

bool fill(int map[]);
main()

{

int map[81]={0};

for(int i=0;i<81;i++)

scanf("%d",&map[i]);

if(!fill(map))

printf("无解!");

else

{

for(int i=0;i<81;i++)

{

printf("%d",map[i]);

if(i%9==8)printf("\n");

}

}

return 0;

}



bool fill(int map[])

{

int mapbk[81];

int herz[9]={0},vert[9]={0},item[9]={0};

const int trans[10]={0,1,2,4,8,16,32,64,128,256};

int i=0,remain=0;

for(i=0;i<81;i++)

{

mapbk[i]=map[i];

herz[i/9]|=trans[mapbk[i]];

vert[i%9]|=trans[mapbk[i]];

item[i/27*3+i%9/3]|=trans[mapbk[i]];

if(mapbk[i]==0)remain++;

}

for(;remain>0;remain--)

{

bool fillsuccess=false;

for(i=0;i<81;i++)

{

if(mapbk[i])continue;

int st=~(herz[i/9]|vert[i%9]|item[i/27*3+i%9/3])&0x1ff;

if(st==0)return false;

switch(st)

{

case 1:

mapbk[i]=1;

break;

case 2:

mapbk[i]=2;

break;

case 4:

mapbk[i]=3;

break;

case 8:

mapbk[i]=4;

break;

case 16:

mapbk[i]=5;

break;

case 32:

mapbk[i]=6;

break;

case 64:

mapbk[i]=7;

break;

case 128:

mapbk[i]=8;

break;

case 256:

mapbk[i]=9;

break;

}

if(mapbk[i])

{

herz[i/9]|=trans[mapbk[i]];

vert[i%9]|=trans[mapbk[i]];

item[i/27*3+i%9/3]|=trans[mapbk[i]];

fillsuccess=true;

break;

}



}

if(fillsuccess)continue;

for(i=0;i<81;i++)

{

if(mapbk[i])continue;

int st=~(herz[i/9]|vert[i%9]|item[i/27*3+i%9/3])&0x1ff;

if(st==0)return false;

for(int j=1;j<=9;j++)

{

if(st&trans[j])

{

mapbk[i]=j;

if(fill(mapbk))goto success;

}

}

return false;

}

}



success:

for(i=0;i<81;i++)

map[i]=mapbk[i];

return true;

}
 回复 引用   

这个也能秒杀5328,就是输入麻烦阿!!!
 回复 引用   

#17楼[楼主] 2007-08-26 09:56 银河      
@罗红文
> 这个也能秒杀5328,就是输入麻烦阿!!!
是呀, 我的网页的数独程序也比较麻烦.

不过,也可以按下列方式输入,虽然看起来不如上图整齐,但输入的工作量会减少一些(实际上我的程序忽略输入中一切除数字和'.'以外的字符):


 回复 引用 查看   

您能写一个带界面的exe吗?!
 回复 引用   

#19楼[楼主] 2007-08-29 07:57 银河      
@罗红文
可以考虑。主要是采用什么样的界面方便用户输入。
 回复 引用 查看   

类型“Skyiv.Ben.Web.SudokuPage”的声明上缺少 partial 修饰符;存在此类型的其他分部声明
 回复 引用   

这题怎写?
xxx657xxx
x93xx1xxx
5xxxxxxx1
8x6xx5xxx
xx7xxxx8x
xxxxxx629
x49x2xxxx
xxxxxx4xx
xxxx138xx
我急着要,麻烦了!
 回复 引用   

243x61
4x5x3x
xxx3xx
x5xx1x
xxxxxx
1xxxx2
 回复 引用   

那个网站提供两个solution
一个解的快 另一个解的慢
慢的那个大概是因为它把遍历过程显示出来的了
 回复 引用   

#24楼 2011-08-17 21:22 强盗罗吉      
这个算法可以做下面这种推断吗:
第1 2 3个数有3种相同可能,推断出其余6个数不能包含这3个数。
 回复 引用 查看