大致框架及思路在上一篇博客中已经初步建立完毕,现在就是实现的问题
接收器
用于接收用户传递进来的初始表信息
这里我是用字符串存储九宫格信息
上下左右关系对应于下标-3 +3 -1 +1
实例对象
每个步骤抽象成一个结点,我觉得其应该包含以下属性,含义都在注释里
方法就是传统的get set方法
值得一提的是,我在设置目标代价G的同时就会自动生成综合代价F
接收对象
public class Receiver {
Scanner input = new Scanner(System.in);
private String s;
//以字符串存储九宫格信息,左右关系为下标-1 +1,上下关系-3 +3
public Receiver() {
System.out.println("请输入初始九宫格,按照从左往右,从上往下的顺序输出\n"+
"空缺部分以0代替,例如203184765");
s = input.nextLine();
}
public String getS() {
return s;
}
}
现在看来好像有点多余,甚至可以写入实体类,成为实体类专门调用的方法
也许会更好?
处理器
具体对重排问题的操作都在这里了,总体结构如图
H值的判定
如何计算不在位的个数?
和最终的图逐一匹配一下即可
/*以下是查询不在位置数字数量*/
public int findH(String s){
String target = "123804765";
int count=0;
for(int i=0;i<9;i++){
if(s.charAt(i)!=target.charAt(i)){
count++;
}
}
return count;
}
结点的选择
结点是在open表中选取综合代价f最小的结点,进行下一步的寻路
返回值来确定是否找到了目标结点
public boolean selectNode(){
int min=10;//用于存储综合代价
for(Step step:open_list){
if(step.getF()<=min){
min = step.getF();
}
}//已经获取了最小综合代价,已获取当前行动代价
for(int i=0;i<open_list.size();i++){
if(open_list.get(i).getF()==min){
close_list.add(open_list.get(i));
if(open_list.get(i).getH()==0){
return true;
}//已经找到了最终结点,退出循环
findNext(open_list.get(i));
//当前结点在找寻完下一个子节点后转入close_list
open_list.remove(open_list.get(i));
i--;
//选择最小代价寻找下一结点
}
}
open_list.addAll(newNodes);
newNodes.clear();
return false;
}
我最初始的想法是这样的
- 在open表中找出综合代价最小的结点,且找出当前结点的行动代价
- 在用综合代价最小结点寻找下一结点的同时,清除open表中当前结点的父类同级结点
这一步是减少open表中冗余的结点,既然当前结点的父类(只要是综合代价最小的)已经转入close表中,那么其余综合代价高的父级结点就没有遍历价值,甚至可以避免一种可能出现的负担:
当九宫重排需要很多步骤时,第n层结点综合代价会比低层结点(比如第2层)综合代价高,程序反而会去低层结点寻找新结点
- 当前结点从open表转入close表
这就保证了open表始终是最新的一批结点,不走回头路
但现实存在这种回溯的需求,这也是我在测试代码时才认识到的问题
比如这个九宫格
乍一看,好家伙,只有5是在位置上的
分析一下周边各节点综合代价
毫无疑问,和2交换了
换完之后,没什么悬念,换3
此时综合代价又回到了8,如果像我最初的想法
清除了父类节点,那就没回头路了,一条道走到黑,而且是死循环(我傻傻的等了半分钟,应该找了几亿个结点了吧)
其实正确的路径是当初和1交换
到这一步应该都能看出来,右上角四个数顺时针转270°就行
所以此时就体现出保留未探寻节点的重要性给自己留条后路
新结点的查找
探寻当前结点上下左右,再判断会不会重复
public void findNext(Step now){
int index = now.getChart().indexOf("0");
String chart = now.getChart();
if(index%3!=0){//证明空格不在第1列,则空格可以左移
Step s = new Step();
StringBuilder t = new StringBuilder(chart);
t.replace(index,index+1,chart.charAt(index-1)+"");
t.replace(index-1,index,"0");
s.setChart(t.toString());
/*交换空格和左边的数字*/
judge(s,now);
}
if(index%3!=2){//证明空格不在第3列,则空格可以右移
Step s = new Step();
StringBuilder t = new StringBuilder(chart);
t.replace(index,index+1,chart.charAt(index+1)+"");
t.replace(index+1,index+2,"0");
s.setChart(t.toString());
/*交换空格和右边的数字*/
judge(s,now);
}
if(index>2){//证明空格不在第1层,则空格可以上移
Step s = new Step();
StringBuilder t = new StringBuilder(chart);
t.replace(index,index+1,chart.charAt(index-3)+"");
t.replace(index-3,index-2,"0");
s.setChart(t.toString());
/*交换空格和上边的数字*/
judge(s,now);
}
if(index<6){//证明空格不在第3层,则空格可以下移
Step s = new Step();
StringBuilder t = new StringBuilder(chart);
t.replace(index,index+1,chart.charAt(index+3)+"");
t.replace(index+3,index+4,"0");
s.setChart(t.toString());
/*交换空格和下边的数字*/
judge(s,now);
}
/*此时能够操作的格子都已放入newNodes中*/
}
/*以下是判定新节点是否重复*/
public void judge(Step s,Step now){//判断当前创造的结点是否会成为重复步骤
if(now.getPre()==null || s.getChart()!=now.getPre().getChart()){
s.setG(now.getG()+1);
s.setH(findH(s.getChart()));
s.setPre(now);
newNodes.add(s);
}
}
展示状态图
逐个输出字符,满三个换一行
想美观点数字间加个空格就行
/*以下是显示移动图*/
public void showChart(Step s){
char[] chart = s.getChart().toCharArray();
System.out.println("当前结点九宫格为:");
for(int i=0;i<chart.length;i++){
System.out.print(chart[i]+" ");
if(i%3==2){
System.out.println();
}
}
System.out.println("---------");
}
启动函数
初始要把用户输入的状态图生成结点存入open表中用于遍历
只要找不到目标图(不在位数字为0)就不停止
找到了之后,利用各节点存储了父级的指向,返回去寻找路径
并且一路把彼此间的父子关系,之前只有父关系pre,现在回溯的时候设置一下子关系suc,其实就是个双向链表
public void start(){
boolean flag=false;
receiver = new Receiver();
Step origin = new Step();
origin.setChart(receiver.getS());
origin.setG(0);
origin.setH(findH(origin.getChart()));
showChart(origin);
open_list.add(origin);
do{
flag = selectNode();
}while (flag!=true);
/*此时已经找到了路径,都存放在close_list中*/
Step p = close_list.get(close_list.size()-1);//获取最后一个结点
Step t;
while (p.getPre()!=origin){
t = p.getPre();
t.setSuc(p);
p = t;
}//此时已建立从头到尾的指向关系,并且p指向第二个
while(p.getSuc()!=null){
showChart(p);
p = p.getSuc();
}//除了最后一个,其他全部输出完毕
showChart(p);
}
关系捋顺了就可以从头输出了
项目演示
小结
通过本次项目的开发,我深入学习了A算法,这一算法有效地规避了许多无用的操作
让最有希望的结点去探寻最终的结果,极大节约了时间和空间上的开销
A算法中的H值还有跟多可以深入的地方,如何让待探寻节点有个性化?如何让最终路径近似平滑的曲线?在这里有深刻的思考与君共勉