Java DFS实现六数码问题
问题描述
现有一两行三列的表格如下:
A B C
D E F
把1、2、3、4、5、6六个数字分别填入A、B、C、D、E、F格子中,每个格子一个数字且各不相同。每种不同的填法称为一种布局。如下:
1 3 5
2 4 6
布局1
2 5 6
4 3 1
布局2
定义α变换如下:把A格中的数字放入B格,把B格中的数字放入E格,把E格中的数字放入D格,把D格中的数字放入A格。
定义β变换如下:把B格中的数字放入C格,把C格中的数字放入F格,把F格中的数字放入E格,把E格中的数字放入B格。
问:对于给定的布局,可否通过有限次的α变换和β变换变成下面的目标布局:
1 2 3
4 5 6
输入:本题有多个测例,第一行为输入测例的个数n,下面是n行测例,每个测例的输入是1到6这六个数字的一个排列,空格隔开,表示初始布局ABCDEF格中依次填入的数字。
输出:每个输出占一行。输出转换到目标格局需要变换的最少次数。(若不能转换则输出-1)
输入样例:
2
2 5 3 1 4 6
2 3 6 1 5 4
输出样例:
1
2
注意不能转换到目标格局的情况应输出-1;
输出格式为:printf(“%d\n”,min);
问题理解
通过输入的6位数字,将数字转换为2*3的二维数组,将数组通过有限次数的α变换和β变换得到目标数组。根据排列组合,输入的情况总共有720种(6 * 5 * 4 * 3 * 2 * 1)。本题可以采用回溯法解决。
问题解的形式表示为(change,len,flag)组成,change表示变换的方式序列,分别用a、b表示,len表示最少变换的次数,flag表示是否是问题的解。
显式约束:通过9次变换得到目标数组。
隐式约束:变换过程中不能变换出之前变换出的数组。
解空间:根据显示约束,每一次变换有两种变换方式,最多可以变换9次,所以解空间大小为2^9种。
代码
node.java
public class Node {
/**
* value 记录当前节点的值,
* change 记录到当前节点经过了哪些变换,变换方式只有a或b。
* len 记录当前节点变换了多少次
* flag 记录当前节点是否是一个解
*/
public int[] value;
public String change;
public int len;
public boolean flag = false;
public int[] getValue() {
return value;
}
public void setValue(int[] value) {
this.value = value;
}
public String getChange() {
return change;
}
public void setChange(String change) {
this.change = change;
}
public int getLen() {
return len;
}
public void setLen(int len) {
this.len = len;
}
public Node(int[] value, String change, int len) {
this.value = value;
this.change = change;
this.len = len;
}
/**
* a变换
* @return 返回变换后的数组
*/
public int[] changeA(){
int[] ans = this.value.clone();
int tem = ans[0];
ans[0] = ans[3];
ans[3] = ans[4];
ans[4] = ans[1];
ans[1] = tem;
return ans;
}
/**
* b变换
* @return 返回变换后的数组
*/
public int[] changeB(){
int[] ans = this.value.clone();
int tem = ans[1];
ans[1] = ans[4];
ans[4] = ans[5];
ans[5] = ans[2];
ans[2] = tem;
return ans;
}
/**
* 将int数组得到String
* @return String
*/
public String getString(){
String tem = "";
int[] ans = this.value.clone();
for (int i=0;i<ans.length;i++){
tem = tem + ans[i];
}
return tem;
}
}
Tree.java
public class Tree {
public Node node;
public Tree left = null;
public Tree right = null;
public Node getNode() {
return node;
}
public void setNode(Node node) {
this.node = node;
}
public Tree getLeft() {
return left;
}
public void setLeft(Tree left) {
this.left = left;
}
public Tree getRight() {
return right;
}
public void setRight(Tree right) {
this.right = right;
}
public Tree(Node node) {
this.node = node;
}
public Tree(int[] ans,String change ,int len){
this.node = new Node(ans,change,len);
}
}
do.java
public class Do {
public static Set<String> set = new TreeSet<>();
public static Set<String> set1 = new TreeSet<>();
final static String last = "123456";
// public static void main(String[] args) {
// //实习要求的主程序
// Main();
// //判断有多少种有解情况
countAll();
// //计算所有输入情况,并判断解是否正确
countSolve();
//
// }
//实习要求的主程序
public static void Main(){
Scanner input = new Scanner(System.in);
int n = input.nextInt();
Tree root ;
int[] tem;
for(int i = 0; i < n; i++){
tem = inputArray(6);
// String origin = ""+tem[0]+tem[1]+tem[2]+tem[3]+tem[4]+tem[5];
// tem = getArrByString("123456");
root = new Tree(tem,"",0);
// DFS1(root,origin);
root = new Tree(tem,"",0);
DFS(root);
System.out.println(searchResult(root).len);
}
}
/**
* 生成结果树
* @param root 根节点
*/
public static void DFS(Tree root){
String value = root.node.getString();
if (set.contains(value) || set.size()>9){
return ;
}
if (value.equals(last)){
root.node.flag = true;
return ;
}
set.add(value);
root.left = new Tree(root.node.changeA(),root.node.change+"a",root.node.len+1);
DFS(root.left);
root.right = new Tree(root.node.changeB(),root.node.change+"b",root.node.len+1);
DFS(root.right);
set.remove(value);
}
public static void DFS1(Tree root,String origin){
String value = root.node.getString();
if (set.contains(value)){
return ;
}
if (value.equals(origin)){
root.node.flag = true;
return ;
}
set.add(value);
root.left = new Tree(root.node.changeA(),root.node.change+"a",root.node.len+1);
DFS1(root.left,origin);
root.right = new Tree(root.node.changeB(),root.node.change+"b",root.node.len+1);
DFS1(root.right,origin);
set.remove(value);
}
/**
* 查询结果集
* @param root 根节点
* @return 最小的结果节点
*/
public static Node searchResult(Tree root){
if (root.node.flag){
return root.node;
}
Queue<Tree> queue = new LinkedList<Tree>();
queue.offer(root);
Node min = new Node(null,"",Integer.MAX_VALUE);
Tree tem ;
while (!queue.isEmpty()){
tem = queue.poll();
if (tem.left!=null){
queue.offer(tem.left);
}
if (tem.right!=null){
queue.offer(tem.right);
}
if (tem.node.flag==true && tem.node.len < min.len){
min = tem.node;
}
}
if (min.len==Integer.MAX_VALUE){
min.len=-1;
}
return min;
}
/**
* 构建数组
* @param len 输入数组的长度
* @return 返回输入的数组
*/
public static int[] inputArray(int len){
int[] ans = new int[len];
Scanner in = new Scanner(System.in);
for (int i=0; i<len;i++){
ans[i] = in.nextInt();
}
return ans;
}
// 初始化输入 将生成的所有输入保存到set1集合中。
public static void DFS_Input_init(String s){
if (s.length()==6){
set1.add(s);
}
for(int i=1;i<7;i++){
if (s.contains(""+i)){
continue;
}else {
DFS_Input_init(s+i);
}
}
}
// 将字符串转换为二维数组
public static int[] getArrByString(String s){
int ans[] = new int[s.length()];
for (int i=0;i<s.length();i++){
ans[i] = Integer.parseInt(s.substring(i,i+1));
}
return ans;
}
//判断当前节点是否能按照结果变换到目标字符串
public static boolean check(Node node,String context){
boolean t = false;
int[] tem;
for (int i=0;i<node.change.length();i++){
if (node.change.charAt(i) =='a'){
tem = node.changeA();
node.value = tem.clone();
}else {
tem = node.changeB();
node.value = tem.clone();
}
}
if (node.getString().equals(context)){
t = true;
}
return t;
}
/**
* 生成游街情况的树,set集合中元素的个数即为有解情况的种数
* @param root 生成树的根节点
*/
public static void count(Tree root){
String value = root.getNode().getString();
if (set.contains(value)){
return ;
}
set.add(value);
int[] temp = root.getNode().changeA();
root.node.value = temp.clone();
temp = root.getNode().changeA();
root.node.value = temp.clone();
temp = root.getNode().changeA();
root.node.value = temp.clone();
root.left = new Tree(temp,"a"+root.node.change,root.node.len+1);
count(root.left);
temp = root.getNode().changeA();
root.node.value = temp.clone();
temp = root.getNode().changeB();
root.node.value = temp.clone();
temp = root.getNode().changeB();
root.node.value = temp.clone();
temp = root.getNode().changeB();
root.node.value = temp.clone();
root.right = new Tree(temp,"b"+root.node.change,root.node.len+1);
count(root.right);
}
//计算有解情况的种数主程序
public static void countAll(){
set.clear();
Tree root = new Tree(new int[]{1,2,3,4,5,6},"",0);
count(root);
System.out.println(set.size());
}
//计算所有输入情况,并判断解是否正确
public static void countSolve(){
Tree root;
DFS_Input_init("");
Iterator<String> iterator = set1.iterator();
while (iterator.hasNext()){
set.clear();
String temp = iterator.next();
int[] arr = getArrByString(temp);
root = new Tree(arr,"",0);
DFS(root);
Node node = searchResult(root);
boolean t = true;
if (node.len!=-1){
node.value = (getArrByString(temp)).clone();
t = check(node,"123456");
}
System.out.println(temp+"\t"+node.len+"\t"+node.change+"\t"+t);
}
}
}
增加web页面
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>六数码问题求解</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<div id="main">
<div id="titel">
<h2>六数码问题求解</h2>
<label for="main-display">演示框</label>
<table id="main-display" border="1px">
<tr>
<td class="tableimg" id="img0" value=""></td>
<td class="tableimg" id="img1" value=""></td>
<td class="tableimg" id="img2" value=""></td>
</tr>
<tr>
<td class="tableimg" id="img3" value=""></td>
<td class="tableimg" id="img4" value=""></td>
<td class="tableimg" id="img5" value=""></td>
</tr>
</table>
</div>
<label for="main-input">点击图片输入到演示框</label>
<table id="main-input" border="1px" >
<tr>
<td class="tableimg" >
<img id="imginput0" class="input" src="/img/blue_1.png" alt="无图片" value="1">
<!-- <img id="imginput0" class="input" src="./../static/img/blue_1.png" alt="无图片" value="1"> -->
</td>
<td class="tableimg" >
<img id="imginput1" class="input" src="/img/blue_2.png" alt="无图片" value="2">
<!-- <img id="imginput1" class="input" src="./../static/img/blue_2.png" alt="无图片" value="2"> -->
</td>
<td class="tableimg" >
<img id="imginput2" class="input" src="/img/blue_3.png" alt="无图片" value="3">
<!-- <img id="imginput2" class="input" src="./../static/img/blue_3.png" alt="无图片" value="3"> -->
</td>
</tr>
<tr>
<td class="tableimg" >
<img id="imginput3" class="input" src="/img/blue_4.png" alt="无图片" value="4">
<!-- <img id="imginput3" class="input" src="./../static/img/blue_4.png" alt="无图片" value="4"> -->
</td>
<td class="tableimg" >
<img id="imginput4" class="input" src="/img/blue_5.png" alt="无图片" value="5">
<!-- <img id="imginput4" class="input" src="./../static/img/blue_5.png" alt="无图片" value="5"> -->
</td>
<td class="tableimg" >
<img id="imginput5" class="input" src="/img/blue_6.png" alt="无图片" value="6">
<!-- <img id="imginput5" class="input" src="./../static/img/blue_6.png" alt="无图片" value="6"> -->
</td>
</tr>
</table>
<button id="rewrite">复位</button>
<button id="solve">运算</button>
<button id="display">演示</button>
<!-- <img src="./../static/img/blue_1.png" id="m12"> -->
<!-- <label for="ans">最少运算次数</label> -->
<!-- <cite th:text="${session.ans}" id="ans"></cite> -->
</div>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
<script>
$(document).ready(function(){
var len = 0;
var ans = ""
var index = 0
$(".input").click(function(){
$(this).hide();
var temid = "img"+len;
var imgsrc = this.src;
// console.log(imgsrc)
var value = this.id;
value =value.replace("imginput","");
value++;
// console.log(value)
document.getElementById(temid).innerHTML="<img id='img1"+len+"' src="+imgsrc+">"
temid="#"+temid
$(temid).val(value)
console.log($(temid).val())
len++;
})
$("#rewrite").click(function(){
len=0
$(".input").show();
document.getElementById("img0").innerHTML=""
document.getElementById("img1").innerHTML=""
document.getElementById("img2").innerHTML=""
document.getElementById("img3").innerHTML=""
document.getElementById("img4").innerHTML=""
document.getElementById("img5").innerHTML=""
})
$("#solve").click(function(){
if(len!=6){
alert("请先填完数字!")
return;
}
var a0 = $("#img0").val()
var a1 = $("#img1").val()
var a2 = $("#img2").val()
var a3 = $("#img3").val()
var a4 = $("#img4").val()
var a5 = $("#img5").val()
$.ajax({
type:'get',
url:'/solve?a0='+a0+'&a1='+a1+'&a2='+a2+'&a3='+a3+'&a4='+a4+'&a5='+a5,
success(data){
alert(data.message+data.status+"结果为"+data.ans+"\n"+data.change)
ans = data.change
index = 0;
}
})
})
$("#display").click(function(){
if(ans==""){
alert("不需要变换")
return;
}
if(ans=="-1"){
alert("不能变换到目标数组")
return;
}
if(index>=ans.length){
alert("变换完成")
return;
}
if(ans.charAt(index)=='a'){
changeA()
}else{
changeB()
}
index++
console("index:"+index)
})
function changeA(){
$('#img10').animate({
left:'57px',
}
,1000)
$("#img11").animate({
top:'57px',
}
,1000)
$("#img14").animate({
left:'-57px',
}
,1000)
$("#img13").animate({
top:'-57px',
}
,1000)
setTimeout(function(){
var temsrc0 = document.getElementById("img10").getAttribute("src");
var temsrc1 = document.getElementById("img11").getAttribute("src");
var temsrc3 = document.getElementById("img13").getAttribute("src");
var temsrc4 = document.getElementById("img14").getAttribute("src");
document.getElementById("img10").setAttribute("src",temsrc3);
document.getElementById("img13").setAttribute("src",temsrc4);
document.getElementById("img14").setAttribute("src",temsrc1);
document.getElementById("img11").setAttribute("src",temsrc0);
document.getElementById("img10").removeAttribute("style");
document.getElementById("img13").removeAttribute("style");
document.getElementById("img14").removeAttribute("style");
document.getElementById("img11").removeAttribute("style");
// document.getElementById("img10").setAttribute("style","");
// document.getElementById("img13").setAttribute("style","");
// document.getElementById("img14").setAttribute("style","");
// document.getElementById("img11").setAttribute("style","");
},1500)
}
function changeB(){
$('#img11').animate({
left:'57px',
}
,1000)
$("#img12").animate({
top:'57px',
}
,1000)
$("#img15").animate({
left:'-57px',
}
,1000)
$("#img14").animate({
top:'-57px',
}
,1000)
setTimeout(function(){
var temsrc1 = document.getElementById("img11").getAttribute("src");
var temsrc2 = document.getElementById("img12").getAttribute("src");
var temsrc4 = document.getElementById("img14").getAttribute("src");
var temsrc5 = document.getElementById("img15").getAttribute("src");
document.getElementById("img11").setAttribute("src",temsrc4);
document.getElementById("img14").setAttribute("src",temsrc5);
document.getElementById("img15").setAttribute("src",temsrc2);
document.getElementById("img12").setAttribute("src",temsrc1);
document.getElementById("img11").removeAttribute("style");
document.getElementById("img14").removeAttribute("style");
document.getElementById("img15").removeAttribute("style");
document.getElementById("img12").removeAttribute("style");
// document.getElementById("img11").setAttribute("style","");
// document.getElementById("img14").setAttribute("style","");
// document.getElementById("img15").setAttribute("style","");
// document.getElementById("img12").setAttribute("style","");
},1500)
}
})
</script>
</html>
带Web页面效果截图
演示框可将运算后的结果进行演示(演示过程中别点复位!!!),点一次变换一次。
带页面的项目文件下载[项目已打包成jar 可直接运行]
下载文件后,
打开cmd
进入到包含jar文件的目录下
输入:
java -jar [文件名].jar
效果展示:
刚开始启动时的画面:
启动成功的画面:
在浏览器中输入127.0.0.1:8080 即可