实验任务、要求、工具上一篇里有,此处略去。
总结:本来以为是贪婪算法的思想,局部优化推导到全局优化,后来心态爆炸,怎么写也写不出来。然后发现是理解有问题。
A*算法,yyds。本人略得皮毛,没有按照原本思想进行写,只有openlist,没有closedlist和node类,以便得到最优路径,大概算是删减版的A*算法,
体现了启发式搜索。
写的时候发现求哈希值(全排列映射的值),用到康托展开,有Hashmap自然不用去求,但是这是建立在String的基础上,如果是数组就要求了。还有可以预先判断能不能匹配,是除点以外数字的逆序数,点移动时不会改变逆序数的奇偶性。
感觉八数码比普通寻路算法难写,迷宫的话要更难些。
估价函数的选取直接决定A*算法的效率,一般对于八数码问题有三种估价函数的选法:
- 以不在位的数码的个数为估价函数
- 以不在位的数码归位所需的最短距离和即曼哈顿距离为估价函数
- 将逆序对数作为估价函数
第一种太乐观了,效率很低。第三种肉眼可见的费时间,9!。选择了第二种。由于估价还是乐观的,存在概率找到的不是最优路径。
看代码吧。。
import java.util.*;
//12345678.
//123.46758
//13524678.
//46758123.
public class Rearrange2 {
public static int dict[][]={{0,-1},{0,1},{1,0},{-1,0}};//方向数组(左右上下遍历)
public static String src_st;
public static String des_st;
public static Map<String,Integer> visited=new HashMap<>();
public static Map<String,Integer> depth=new HashMap<>();
public static Map<String,Integer> fmap;
public static List<String>list;
public static void getF(String new_st) {//得到曼哈顿距离(所有字符)+移动距离
int h = 0;
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (new_st.charAt(i) == des_st.charAt(j)) {
h += Math.abs(i / 3 - j / 3) + Math.abs(i % 3 - j % 3);
}
}
}
if (depth.containsKey(new_st)) {
fmap.put(new_st, h + depth.get(new_st));
} else fmap.put(new_st, h );
}
public static boolean judge() {//判断能不能找到
int st=0;
int et=0;
for(int i = 0; i < 9 ; ++i)
{
if (src_st.charAt(i)=='.') continue;
for(int j =i+1 ; j < 9 ; ++j)
{
if (src_st.charAt(j)=='.') continue;
if (src_st.charAt(i)>src_st.charAt(j))
st++;
}
}
for(int i = 0; i < 9 ; ++i)
{
if (des_st.charAt(i)=='.') continue;
for(int j =i+1 ; j < 9 ; ++j)
{
if (des_st.charAt(j)=='.') continue;
if (des_st.charAt(i)>des_st.charAt(j))
et++;
}
}
return st%2==et%2;
}
public static int Astar() {
if (src_st.equals(des_st)) return 0;
fmap = new HashMap<>();
list = new ArrayList<>();
list.add(src_st);
while (true) {
String first = list.get(getmin());
if (first.equals(des_st)) {
return depth.get(first);
}
list.remove(list.get(getmin()));
int curX = 3;
int curY = 3;
for (int i = 0; i < first.length(); i++) {//定位空格位置(句点)
if (first.charAt(i) == '.') {
curX = i / 3;
curY = i % 3;
}
}
int newX, newY;
for (int i = 0; i < 4; i++) {
newX = dict[i][0] + curX;
newY = dict[i][1] + curY;
if (newX >= 0 && newX <= 2 && newY >= 0 && newY <= 2) {//判断边界条件
String new_st = first;
char curtemp = new_st.charAt(curX * 3 + curY);
char newtemp = new_st.charAt(newX * 3 + newY);
new_st = new_st.replace(curtemp, '-');//字符串交换字符
new_st = new_st.replace(newtemp, curtemp);
new_st = new_st.replace('-', newtemp);
depth.put(new_st,depth.get(first)+1);
if (!visited.containsKey(new_st)) {//这个图未访问过
visited.put(new_st,depth.get(new_st));
getF(new_st);
list.add(new_st);
}else {
if (depth.get(new_st)<visited.get(new_st)){//如果这个图访问过,,但是现在访问代价更小,,则更新对该图的访问代价
visited.put(new_st,depth.get(new_st));
getF(new_st);
list.add(new_st);
}
}
}
}
String s=list.get(getmin());
System.out.println(s);
}
}
public static int getmin(){//得到f值最小的字符串索引
int min = 0;
for (int j = 1; j < list.size(); j++) {
if (fmap.get(list.get(j)) < fmap.get(list.get(min))) {
min=j;
}
}
return min;
}
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
System.out.println("请输入九宫起点字符串:");
src_st=scanner.next();//九宫起点状态
System.out.println("请输入九宫终点字符串:");
des_st=scanner.next();//九宫终点状态
visited.put(src_st,0);
depth.put(src_st,0);
boolean ismatched =judge();
if (!ismatched){
System.out.println("人生不相见,动如参与商");
}else {
int num=Astar();
System.out.println("九宫重排搜索次数:"+"\t"+num);
}
}
}