15数码问题:一个4*4的16宫格棋盘上,摆放有15个将牌,每一个都刻有1-15中的某一个数码。棋盘中留有一个空格,允许其周围的某一个将牌向空格移动,这样通过移动将牌就可以不断改变将牌的布局。所要求解的问题:是给定一种初始布局(初始状态)和一个目标布局(目标状态),问如何移动数码实现从初始状态到目标状态的转变。
A*算法,A*(A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法,也是解决许多搜索问题的有效算法。A*算法是一种启发式搜索算法,启发式搜索就是在状态空间中的搜索对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到达到目标。在搜索过程中需要根据启发函数来帮助选择搜索路径。
①将初始状态结点加入OPEN
②找到OPEN中f(n)最小的结点 n
③如果该结点(n)达到目标状态,回溯结果进行输出,程序结束。否则进行下一步
④从OPEN中移除结点n,加入CLOSED,遍历n所有邻近结点(即可以移动到达的位置所对应的状态),设置这些邻近结点父结点为n,并给其它字段赋值,判断邻近结点是否在CLOSED,若在则不进行操作,否则进行下一步
⑤将该邻近结点加入OPEN,返回②
Github仓库,点击进入
import java.util.Arrays;
/**
* @program: A*
* @description:
* @author: wtongxue
* @create: 2021-11-29 12:35
**/
public class Node {
/**
* 0数码坐标行
*/
private int row;
/**
* 0数码坐标列
*/
private int col;
/**
* f(n)
*/
private int f;
/**
* g(n)
*/
private int g;
/**
* 结点状态矩阵
*/
private int[][] matrix;
/**
* 父结点
*/
Node parent;
public Node(){}
public Node(int row, int col) {
this.row = row;
this.col = col;
}
public Node(int row, int col, int[][] matrix) {
this.row = row;
this.col = col;
this.matrix = matrix;
}
public int getRow() {
return row;
}
public void setRow(int row) {
this.row = row;
}
public int getCol() {
return col;
}
public void setCol(int col) {
this.col = col;
}
public int getG() {
return g;
}
public void setG(int g) {
this.g = g;
}
public int getF() {
return f;
}
public void setF(int f) {
this.f = f;
}
public int[][] getMatrix() {
return matrix;
}
public void setMatrix(int[][] matrix) {
this.matrix = matrix;
}
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
}
import java.util.*;
/**
* @program: A*
* @description:
* @author: wtongxue
* @create: 2021-11-28 13:50
**/
public class Solution {
/*
启发函数:f(n) = g(n) + P(n)
g(n):从初始结点 s 到结点 n 路径的耗散值
P(n):每一个数码与其目标位置间的距离的总和
*/
/**
* 初始状态
*/
int[][] matrix = {{5, 1, 2, 4}, {9, 6, 3, 8}, {13, 15, 10, 11}, {14, 0, 7, 12}};
/**
* 目标状态
*/
int[][] result = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 0}};
/**
* OPEN表
*/
ArrayList<Node> open = new ArrayList<>();
/**
* CLOSED表
*/
ArrayList<Node> closed = new ArrayList<>();
public Solution() {
int row=0;
int col=0;
//查找初始状态中空位数码0的坐标
for (int i=0;i<matrix.length;i++){
for (int j=0;j<matrix[0].length;j++){
if (matrix[i][j]==0){
row = i;
col = j;
break;
}
}
}
//构造初始状态结点加入OPEN表
Node initStateNode = new Node(row, col, matrix);
open.add(initStateNode);
}
/**
* 以矩阵形式输出结点
*
* @param matrix
*/
private void printMatrix(int[][] matrix) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
System.out.printf("%-6d", matrix[i][j]);
System.out.print(" ");
}
System.out.println();
}
}
/**
* 复制矩阵
*
* @param matrix
* @return
*/
private int[][] cloneMatrix(int[][] matrix) {
int[][] newMatrix = new int[matrix.length][matrix[0].length];
for (int i = 0; i < newMatrix.length; i++) {
for (int j = 0; j < newMatrix[0].length; j++) {
newMatrix[i][j] = matrix[i][j];
}
}
return newMatrix;
}
/**
* 计算f(n)
*
* @return
*/
private int calculate(Node node) {
int pN = 0;
//计算P(n)
int[][] matrix = node.getMatrix();
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
if (matrix[i][j] != 0 && matrix[i][j] != (4 * i) + j + 1) {
//目标坐标行数
int targetRow = matrix[i][j] / 4;
//目标坐标列数
int targetCol = matrix[i][j] - (targetRow * 4) - 1;
if (targetCol==-1){
targetRow-=1;
targetCol = 3;
}
pN += Math.abs(targetRow - i) + Math.abs(targetCol - j);
}
}
}
return node.getG() + pN;
}
/**
* 更新OPEN表
*
* @return
*/
public void updateOpen(Node node) {
if (node.getRow()>0){
//上移
addNode(node,node.getRow()-1,node.getCol());
}
if (node.getRow()<result.length-1){
//下移
addNode(node,node.getRow()+1,node.getCol());
}
if (node.getCol()>0){
//左移
addNode(node,node.getRow(),node.getCol()-1);
}
if (node.getCol()<result[0].length-1){
//右移
addNode(node,node.getRow(),node.getCol()+1);
}
}
/**
* 判断结点状态,将符合要求的结点加入OPEN表
*
* @param parentNode
* @param row
* @param col
*/
public void addNode(Node parentNode,int row,int col){
Node node = new Node(row,col);
//复制矩阵,更新复制后的矩阵为移动后的状态
int[][] nodeMatrix = cloneMatrix(parentNode.getMatrix());
int tmp = nodeMatrix[node.getRow()][node.getCol()];
nodeMatrix[node.getRow()][node.getCol()] = nodeMatrix[parentNode.getRow()][parentNode.getCol()];
nodeMatrix[parentNode.getRow()][parentNode.getCol()] = tmp;
node.setMatrix(nodeMatrix);
//设置结点字段值
node.setParent(parentNode);
node.setF(calculate(node));
node.setG(node.getG()+1);
if (isIn(closed,node)){
//如果在CLOSED表中,代表以及拓展过该结点,不进行操作
return;
}
if (!isIn(open,node)) {
//如果不在OPEN表,加入OPEN表
open.add(node);
}
}
/**
* 判断是否到达目标状态
*
* @param node
* @return
*/
public boolean isTarget(Node node) {
int[][] nodeMatrix = node.getMatrix();
for (int i = 0; i < result.length; i++) {
for (int j = 0; j < result[0].length; j++) {
if (result[i][j] != nodeMatrix[i][j]) {
return false;
}
}
}
return true;
}
/**
* 判断是否在open或closed表中
*
* @param table
* @param node
* @return
*/
public boolean isIn(ArrayList<Node> table,Node node){
for (Node tableNode : table){
if (isSameMatrix(tableNode.getMatrix(),node.getMatrix())){
return true;
}
}
return false;
}
/**
* 判断两个状态的数码矩阵是否相同
*
* @param matrix1
* @param matrix2
* @return
*/
public boolean isSameMatrix(int[][]matrix1,int[][]matrix2){
for (int i=0;i<matrix2.length;i++){
for (int j=0;j<matrix2[0].length;j++){
if (matrix1[i][j]!=matrix2[i][j]){
return false;
}
}
}
return true;
}
public static void main(String[] args) {
Solution solution = new Solution();
while (!solution.open.isEmpty()){
//将OPEN表按照f(n)递增排序
Collections.sort(solution.open, new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2) {
return o1.getF() - o2.getF();
}
});
//选择f(n)最小的结点
Node firstNode = solution.open.get(0);
if (solution.isTarget(firstNode)){
//达到目标状态
Stack<Node> stack = new Stack<>();
Node node = firstNode;
while (node!=null){
//回溯结果
stack.push(node);
node = node.getParent();
}
//输出结果
int step = 0;
while (!stack.isEmpty()){
System.out.println("step:"+step++);
solution.printMatrix(stack.pop().getMatrix());
}
break;
}else{
//将结点n移出open表,加入closed表
solution.closed.add(firstNode);
solution.open.remove(0);
//遍历结点n的邻近结点
solution.updateOpen(firstNode);
}
}
}
}