使用Java生成全部数独(Sudoku)布局

2023-10-26

[b]引言[/b]

  数独相信很多人都玩过,趣味性很强,十分的耐玩。可有没有程序员想过玩实现一个数独布局的算法呢?

  算法是个很有意思,很神奇的东西。我第一次接触算法是刚学C的时候,写DOS下的挖雷程序(当时还是WIN32,C的编译器还是TC3.0)。当时不知种子填充算法为何物,怎么也实现不了挖雷时,左键点到空地上自动打开相邻的全部空地。

  到了后来,在计算机图形学上偶然看到种子填充算法,惊奇的发现,这正是挖雷所需要的。

  数独布局,需要的只是回溯算法。恶补了一下回溯算法的知识,花了一个周末的时间,终于把数独布局算法写好了。


[b]特点[/b]


  可以随机生成一局(多次调用,可以生成指定局数的随机布局)、可以生成全部布局(注意是全部)。算法没有使用到递归,也没有大量使用链表,以使用数组为主,效率很高。

  用来布局,只要把生成的随机遮到一些就可以了。


[b]算法[/b]


package com.wallimn.game;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.io.FileWriter;
import java.io.FileOutputStream;
public class Sudou {
/**
* 类入口点
*
*
* 版本:V1.0 作者:wallimn 时间:2009-1-15--下午08:47:38
* 博客:http://blog.csdn.net/wallimn 
*
* @param args
*/
public static void main(String[] args) {
Sudou sd = new Sudou();
sd.init(9);
sd.generateRandom(20);
}
public Sudou() {
init(9);
}
public Sudou(int num) {
init(num);
}
private int fieldNum;
private byte[] layout = null;// 布局
private byte[] outbytes = null;
private int curPos;// 当前处理的布局位置
private Random random = new Random();
private byte[] ansPosArr = null;// 每个布局位置解空间使用标识(指向下一次要处理的解)
private byte[][] ansArr = null;// 记录每个位置的解空间
private boolean randomLayout = false;
public void setRandomLayout(boolean r) {
randomLayout = r;
}
private void init(int num) {
curPos = 0;
fieldNum = num;
if (layout == null)
layout = new byte[num * num];
if (outbytes == null)
outbytes = new byte[(num * num + 1) / 2];
if (ansPosArr == null)
ansPosArr = new byte[num * num];
// 用来记录布局中某个位置的可能解
if (ansArr == null)
ansArr = new byte[num * num][num];
for (int i = 0; i < num * num; i++) {
layout[i] = -1;// 将布局全部设置为未填状态
ansPosArr[i] = 0;// 用来记录解的位置,回溯时从这个位置往后处理
for (int j = 0; j < num; j++)
ansArr[i][j] = -1;// 初始化为无解,程序运行中动态求取
}
}
public void outData() {
for (int i = 0; i < fieldNum * fieldNum; i++) {
if (i % 9 == 0)
System.out.println();
// System.out.print((String.valueOf(i).length() == 1 ? ("0" + i +
// ":")
// : (i + ":"))
// + (layout[i] != -1 ? layout[i] : " ") + ";");
System.out.print((layout[i] != -1 ? (layout[i] + 1) : " ") + ",");
}
}
/**
*以文本方式输出布局到文件
*
*
* 版本:V1.0 作者:wallimn 时间:2009-1-15--下午08:48:37
* 博客:http://blog.csdn.net/wallimn 
*
* @param fw
*/
public void outData(FileWriter fw) {
try {
for (int i = 0; i < fieldNum * fieldNum; i++) {
fw.write(String.valueOf(layout[i] + 1));
if ((i + 1) % fieldNum == 0)
fw.write("\n");
}
fw.write("\n");
} catch (Exception e) {
}
}
/**
* 以二进制方式输出布局
*
*
* 版本:V1.0 作者:wallimn 时间:2009-1-15--下午08:49:29
* 博客:http://blog.csdn.net/wallimn 
*
* @param fos
*/
public void outData(FileOutputStream fos) {
int pos = 0;
boolean bEven = true;
// 两个格合并到一个字节中存储,节省一半的空间。
for (int i = 0; i < fieldNum * fieldNum; i++) {
if (bEven) {
outbytes[pos] = (byte) ((layout[i] + 1) << 4);
} else {
outbytes[pos] |= (layout[i] + 1) & 0xf;
pos++;
}
bEven = !bEven;
}
try {
fos.write(outbytes);
} catch (Exception e) {
}
}
/**
* 返回指定位置的可用解
*
*
* 版本:V1.0 作者:wallimn 时间:2009-1-15--下午08:49:50
* 博客:http://blog.csdn.net/wallimn 
*
* @param pos
*/
private void getAnswer(int pos) {
for (byte i = 0; i < fieldNum; i++)
ansArr[pos][i] = i;// 假定包含所有解
// 去除已经包含的
int x = pos / fieldNum, y = pos % fieldNum;
for (int i = 0; i < fieldNum; i++) {
if (layout[i * fieldNum + y] != -1)
ansArr[pos][layout[i * fieldNum + y]] = -1;// 去除列中包含的元素
if (layout[x * fieldNum + i] != -1)
ansArr[pos][layout[x * fieldNum + i]] = -1;// 去除行中包含的元素
}
int subnum = (int) Math.sqrt(fieldNum);
int x2 = x / subnum, y2 = y / subnum;
// boolean bOver = false;//这个优化应该是没有问题的
for (int i = x2 * subnum; i < subnum + x2 * subnum; i++) {
// if (bOver)
// break;
for (int j = y2 * subnum; j < subnum + y2 * subnum; j++) {
if (layout[i * fieldNum + j] != -1)
ansArr[pos][layout[i * fieldNum + j]] = -1;// 去小方格中包含的元素
}
}
if (randomLayout == true)
dealAnswer(pos);
// outData();
// System.out.print("\n位置:" + curPos + " 可用解:");
// for (int i = 0; i < fieldNum; i++) {
// if (ansArr[pos][i] != -1)
// System.out.print(ansArr[pos][i] + ";");
// }
// System.out.println("[" + ansPosArr[curPos] + "]");
// return list;
}
/**
* 可用解随机排序
*
*
* 版本:V1.0 作者:wallimn 时间:2009-1-15--下午08:50:04
* 博客:http://blog.csdn.net/wallimn 
*
* @param pos
*/
private void dealAnswer(int pos) {
// 随机调整一下顺序
List list = new LinkedList();
for (int i = 0; i < fieldNum; i++)
list.add(ansArr[pos][i]);
int rdm = 0, idx = 0;
while (list.size() != 0) {
rdm = Math.abs(random.nextInt()) % list.size();
ansArr[pos][idx] = list.get(rdm);
list.remove(rdm);
idx++;
}
list = null;
}
/**
* 取解的数量
*
*
* 版本:V1.0 作者:wallimn 时间:2009-1-15--下午08:50:20
* 博客:http://blog.csdn.net/wallimn 
*
* @param pos
* @return
*/
private int getAnswerCount(int pos) {
// 计算可用解的数量
int count = 0;
for (int i = 0; i < fieldNum; i++)
if (ansArr[pos][i] != -1)
count++;
return count;
}
/**
* 取指定位置、第几个解
*
*
* 版本:V1.0 作者:wallimn 时间:2009-1-15--下午08:50:31
* 博客:http://blog.csdn.net/wallimn 
*
* @param fieldPos
* @param ansPos
* @return
*/
private byte getAnswerNum(int fieldPos, int ansPos) {
// 返回指定布局方格中指定位置的解
int cnt = 0;
for (int i = 0; i < fieldNum; i++) {
// 找到指定位置的解,返回
if (cnt == ansPos && ansArr[fieldPos][i] != -1)
return ansArr[fieldPos][i];
if (ansArr[fieldPos][i] != -1)
cnt++;// 是解,调整计数器
}
return -1;// 没有找到,逻辑没有问题的话,应该不会出现这个情况
}
/**
* 打开文件,用来输出布局
*
*
* 版本:V1.0 作者:wallimn 时间:2009-1-15--下午08:50:48
* 博客:http://blog.csdn.net/wallimn 
*
* @param name
* @return
*/
private FileWriter openFileWriter(String name) {
try {
return new FileWriter(name);
} catch (Exception e) {
}
return null;
}
/**
* 打开流,用来输出布局
*
*
* 版本:V1.0 作者:wallimn 时间:2009-1-15--下午08:50:55
* 博客:http://blog.csdn.net/wallimn 
*
* @param name
* @return
*/
private FileOutputStream openFileStream(String name) {
try {
return new FileOutputStream(name);
} catch (Exception e) {
}
return null;
}
/**
* 生成布局,这个是按顺序生成的。
*
*
* 版本:V1.0 作者:wallimn 时间:2009-1-14--下午12:49:02
* 博客:http://blog.csdn.net/wallimn 
*
* @param layoutCount
*  要生成的布局数,如果为-1,表示要全部生成。
*/
public long generate(long layoutCount) {
// FileWriter out = openFileWriter("layout.txt");
//如果要保存布局,把这个注释打开
//FileOutputStream out = openFileStream("layout.bin");
curPos = 0;
long count = 0;
while (count < layoutCount || layoutCount == -1) {
if (ansPosArr[curPos] == 0)
getAnswer(curPos);// 如果这个位置没有被回溯过,就不用重新计算解空间
int ansCount = getAnswerCount(curPos);
if (ansCount == ansPosArr[curPos] && curPos == 0)
break;// 全部回溯完毕
if (ansCount == 0) {
ansPosArr[curPos] = 0;// 无可用解,应该就是0
// System.out.println("无可用解,回溯!");
curPos--;
layout[curPos] = -1;
continue;
}
// 可用解用完
else if (ansPosArr[curPos] == ansCount) {
// System.out.println("可用解用完,回溯!");
ansPosArr[curPos] = 0;
curPos--;
layout[curPos] = -1;
continue;
} else {
// 返回指定格格中,第几个解
layout[curPos] = getAnswerNum(curPos, ansPosArr[curPos]);
// System.out.println("位置:"+curPos+" 填写:"+layout[curPos]);
ansPosArr[curPos]++;
curPos++;
}
if (fieldNum * fieldNum == curPos) {
// System.out.print("\n========"+count+"========");
outData();
System.out.println();
// if (out != null)
// outData(out);
count++;
curPos--;
layout[curPos] = -1;// 最后位置清空
ansPosArr[curPos] = 1;// 解位置标识请零//人为促使继续回溯
}
}
// try {
// out.close();
// } catch (Exception e) {
// }
//System.out.println("处理完毕!共生成:" + count);
return count;
}
/**
* 以随机的方式生成布局
*
*
* 版本:V1.0 作者:wallimn 时间:2009-1-15--下午08:54:10
* 博客:http://blog.csdn.net/wallimn 
*
* @param count
*/
public void generateRandom(int count) {
setRandomLayout(true);
for (int i = 0; i < count; i++) {
init(9);
generate(1);
}
}
}

//程序完

  程序看起来有点长,但大量的是注释,逻辑很容易理解。


  运行这个程序,生成全部布局的时候,很耗时,我运行了一个上午,还没有生成完毕,就放弃了。程序运行过程中,我发现一个有趣的现象,单核的CPU,CPU会占用接近100%,双核的会占用接近50%。看来写运算量大的程序,需要学学如何使程序针对多核进行优化。


  有人搞过吗?说说。
/***********本人原创,欢迎转载,转载请保留本人信息*************/
作者:wallimn 电邮:wallimn@sohu.com 时间:2009-02-09
博客:[url]http://blog.csdn.net/wallimn[/url] [url]http://wallimn.iteye.com[/url]
网络硬盘:http://wallimn.ys168.com
/***********文章发表请与本人联系,作者保留所有权利*************/
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用Java生成全部数独(Sudoku)布局 的相关文章

随机推荐

  • 【深度学习】语义分割-源代码汇总

    目录 Transformer 1 vit 2 Swin Transformer 3 PVT 4 SETR 5 segformer Transformer 1 vit 1 官方 vision transformer 2 Swin Transf
  • Linux内核:内存管理——CMA预留内存

    平时看 proc meminfo中 总能看到CMA的身影 且好像总是被用光了 他是做什么的呢 为啥作为预留内存能用的干干净净呢 一起看下 CmaTotal 438272 kB CmaFree 0 kB Contiguous Memory A
  • 电路设计中LDO与DC-DC的选择问题(DC-DC篇)

    版权声明 本文为博主原创文章 转载请注明出处 https blog csdn net NeverImagine article details 93193105 接上文 上文讨论了LDO的原理和特性 本文再分析一下DC DC 二 DC DC
  • 查看mysql root账户密码

    cat root mysql secret 查看root账号密码
  • 虹膜识别系统 python实现

    先上传效果图 main py An highlighted block Demonstration of the GazeTracking library Check the README md for complete documenta
  • 【从嵌入式视角学习香山处理器】六、NutShell代码结构(乱画的框图)

    文章目录 一 前言 二 简单粗暴版 最终成品的框图 三 不要太凌乱版 去掉连线后的框图 一 前言 这是从上一篇文章 从嵌入式视角学习香山处理器 五 香山开发工作流实践1 主要子模块工程之间的关系 引出的对果壳核 NutShell 一个简单入
  • MySQL存储过程入门教程及实例详解

    1 存储过程简介 存储过程是可编程的函数 在数据库中创建并保存 可以由SQL语句和控制结构组成 当想要在不同的应用程序或平台上执行相同的函数 或者封装特定功能时 存储过程是非常有用的 数据库中的存储过程可以看做是对编程中面向对象方法的模拟
  • 不同集合中判断元素相同的方法

    判断集合中的元素是否相同 对于增删改查有重要意义 不同Collection的实现的判断依据不同 1 List类 线性表 统一标准是equals 2 HashSet和HashMap 哈希表 先hashcode 后equals 3 TreeSe
  • k8s selector_k8s(二)——kubectl的使用以及创建deployment

    kubectl的使用以及创建deployment kubectl的使用 常见概念 kubectl管理命令概要 管理和使用deployment 基于deployment创建nginx pod 有一个副本 查看k8s对象状态 发布应用 服务伸缩
  • SpringBoot:切面AOP实现权限校验:实例演示与注解全解

    1 理解AOP 1 1 什么是AOP AOP Aspect Oriented Programming 面向切面思想 是Spring的三大核心思想之一 两外两个 IOC 控制反转 DI 依赖注入 那么AOP为何那么重要呢 在我们的程序中 经常
  • SM4 CBC模式加密的C语言实现

    因为工作的关系 最近在研究国密算法 其中无线局域网使用的SM4算法颇为神秘 网上资源也是少的可怜 不过在笔者的努力下 还是成功搞定了 有感于SM4相关正确资料的稀少 同时也算是自我的学习积累 故写下此文 希望可以帮助后来人少走些弯路 此处给
  • 有意思的心理学现象

  • Elasticsearch 实战之三:ES 基本操作

    目录 0 数据格式说明 1 ES的基本操作 1 1 索引操作 1 1 1 建立索引 1 1 2 删除索引 1 1 3 查询索引 1 2 映射操作 1 2 1 建立映射 1 2 2 查询映射 1 3 基本操作 CRUD 1 3 1 新增和替换
  • Zephyr-环境搭建

    目录 1 前言 2 安装主机依赖 3 获取源码 4 安装工具链 5 编译一个Demo 1 前言 Zephyr支持Ubuntu macOS Windows环境下开发 本文介绍基于Ubuntu的环境搭建 包括 Ubuntu开发环境搭建 主要是工
  • VBA设置默认/缺省运行路径的方法

    在设计VBA脚本时 有时我们需要在指定目录中运行或打开第三方程序 文件 那么最好的方法就是使用ChDir命令更改当前默认的运行路径 以便脚本能轻松地找到所需文件 更改缺省文件夹或驱动器 ChDir 语句和ChDrive语句 使用ChDir语
  • 改进的PSO-BP算法在工业机器人末端位姿误差补偿中的应用

    摘要 提出了一种全梯度标准粒子群优化反馈 FGSPSO BP 神经网络的工业机器人末端位姿补偿模型 首先 提出一种运动学逆变换算法 通过机器人末端位姿对机器人各关节角度值进行计算 并采用Matlab验证了运动学逆变换算法的准确性 然后 提出
  • VS2019打包WPF安装程序最新教程

    VS2019打包WPF安装程序最新教程 使用Visual Studio 2019开发的WPF程序如果想要打包为安装程序 除了在VS2019找到WPF项目类库直接右键发布之外 更常用的还是将其打包为exe或者msi的安装程序 打包成安装程序的
  • 2022电赛小车开源代码讲解开源

    2022电赛小车我认为有主要是几个主要的问题 我将分这几个部分来讲解 目录 一 循迹 二 蓝牙通信 双车数据传输 三 起始路口的识别 四 分叉路口的识别 五 源码 2022电赛 双车稳定行驶 哔哩哔哩 bilibili 一 循迹 循迹我们组
  • 自定义yaml文件调整pandoc和pandoc-crossref将markdown转word过程中公式行距

    markdown借助pandoc和pandoc crossref将写好的学术论文导出成word时 公式为OMML格式 这个格式可以通过word中的mathtype插件批量转换 但是公式的基准为正文 如果正文文本的段落格式为固定值20磅 比较
  • 使用Java生成全部数独(Sudoku)布局

    b 引言 b 数独相信很多人都玩过 趣味性很强 十分的耐玩 可有没有程序员想过玩实现一个数独布局的算法呢 算法是个很有意思 很神奇的东西 我第一次接触算法是刚学C的时候 写DOS下的挖雷程序 当时还是WIN32 C的编译器还是TC3 0 当