179. 八数码
- ①. 题目
- ②. 思路
- ③. 学习点
- ④. 代码实现
①. 题目
②. 思路
- 这道题要使用A算法,但是A算法可用的前提是问题一定有解,否则A*算法就会退化为普通的BFS,使用优先队列对距离进行从小到大排序
- 这道八数码问题的一个重要的性质就是当且仅当八个数字中的逆序对的数量为偶数的时候问题才是有解的,
- 如果为奇数,是肯定无解的,所以只有为偶数的时候才使用A*算法来解。
- A算法(可以处理任意边权的问题, 使用A算法一定是问题保证有解的情况下,否则A*算法就会退化为普通的BFS)
- A*算法将BFS中的队列换成优先队列
- 队列中存储的元素包含的信息:1.当前状态 2. 从起点到当前点的真实距离 + 从当前点到终点的估计距离
- A*算法只能保证终点出队时的路径是最短的
- A*算法提高搜索效率的关键,就在于能否设计出一个优秀的估价函数。
- A*算法中的估价函数
估价函数:以任意“状态”为输入,计算出从该状态到目标状态所需代价的估计值。
③. 学习点
A*算法 优化BFS
④. 代码实现
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
public class _179_八数码_A_star算法 {
//内部类节点 str 当前字符串的状态 操作的步骤 操作的次数+估价函数计算出的距离
static class Node{
String str,op;
int d;
public Node(String str, String op, int d){
this.str = str;
this.op = op;
this.d = d;
}
}
static int N=9;
static int[] a=new int[N];
static int[] dx= {-1,0,1,0};
static int[] dy= {0,-1,0,1};
static char[] op= {'u','l','d','r'};
static String start="",end=""; // 记录初始状态和目标状态
// 估价函数,计算一个数字到它目标位置的估价距离 曼哈顿距离 d=|x1-x|+|y1-y|
//123456x78
static int f(String str){
int res = 0;
for(int i=0; i<str.length(); i++) {
if(str.charAt(i) == 'x') continue;
int x1 = i / 3; int y1 = i % 3;
int x2 = (str.charAt(i) - '1') / 3; int y2 = (str.charAt(i) - '1') % 3;
res += Math.abs(x1 - x2) + Math.abs(y1 - y2);
}
return res;
}
//交换字符位置
static void swap(char[] a,int i,int j) {
char tmp=a[i];
a[i]=a[j];
a[j]=tmp;
}
static String bfs() {
end="12345678x";
Map<String, Integer> dist = new HashMap<String,Integer>();
//使用优先队列对交换次数进行排序
PriorityQueue<Node> q = new PriorityQueue<Node>(new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2) {
return o1.d-o2.d;
}
});
q.add(new Node(start,"",f(start)));
dist.put(start,0);
while(!q.isEmpty()) {
Node cur = q.poll();
String str = cur.str;
int d=dist.get(str);
//当等于结束字符串 直接返回操作的步骤
if(str.equals(end)) return cur.op;
//找出当前x的位置
int x_1=-1;
for (int i = 0; i < str.length(); i++) {
if(str.charAt(i)=='x') {
x_1=i;
break;
}
}
int x=x_1/3;int y=x_1%3;
char[] arr = str.toCharArray();
//向上下左右四个方向进行扩展
for (int i = 0; i <4; i++) {
int a=x+dx[i];
int b=y+dy[i];
if(a<0||a>=3||b<0||b>=3) continue;
int x_2=a*3+b;
//进行交换
swap(arr,x_2,x_1);
String string = new String(arr);
//再换回来,因为它还可以移动到别的方向
swap(arr,x_2,x_1);
//判断如果dist集合已经包含了新产生的字符串并且距离大于等于原来的距离 直接跳出
if(dist.containsKey(string) && dist.get(str) + 1 >= dist.get(string)) continue;
//将新的字符串加入队列 操作步骤 和距离+估价函数产生的距离
q.add(new Node(string, cur.op + op[i], dist.get(str) + 1 + f(string)));
dist.put(string, dist.get(str) + 1);
}
}
return null;
}
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String str = in.readLine().trim();
int idx = 0;
String[] cur = str.split(" ");
for (int i = 0; i < cur.length; i++) {
if(cur[i].equals("x")) {
start+=cur[i];
continue;
}
//将起始字符存入数组 但是不要x
a[idx++]=Integer.parseInt(cur[i]);
start+=cur[i];
}
//求出当前八数码中的逆序对的数量
int cnt=0;
for (int i = 0; i < idx; i++) {
for (int j =i+1; j <idx; j++) {
if(a[i]>a[j]) {
cnt++;
}
}
}
//当且仅当八个数字中的逆序对的数量为偶数的时候问题才是有解的
if(cnt%2!=0) {
System.out.println("unsolvable");
}else {
System.out.println(bfs());
}
}
}