银河

SKYIV STUDIO

  博客园 :: 首页 ::  ::  :: 订阅 订阅 :: 管理 ::
  133 随笔 :: 2 文章 :: 1006 评论 :: 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 银河 阅读(8394) 评论(23)  编辑 收藏 网摘 所属分类: 算法

评论

这个小游戏在这边叫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" target="_blank">数独解算器。

上面一段话就是我正文中给出的,其中的“数独解算器”这几个字是可以点击的,一点击就会给出我算法实现的一个页面。
我把该页面的地址给出如下:
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 搜索了一下, 找到以下网站: http://www.jigsawdoku.com/">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
一个解的快 另一个解的慢
慢的那个大概是因为它把遍历过程显示出来的了

  回复  引用    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 631233




相关文章:

相关链接: