隐式图的搜索问题——代码实现

大致框架及思路在上一篇博客中已经初步建立完毕,现在就是实现的问题

接收器

用于接收用户传递进来的初始表信息
这里我是用字符串存储九宫格信息
上下左右关系对应于下标-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;
}

我最初始的想法是这样的

  1. 在open表中找出综合代价最小的结点,且找出当前结点的行动代价
  2. 在用综合代价最小结点寻找下一结点的同时,清除open表中当前结点的父类同级结点

这一步是减少open表中冗余的结点,既然当前结点的父类(只要是综合代价最小的)已经转入close表中,那么其余综合代价高的父级结点就没有遍历价值,甚至可以避免一种可能出现的负担:
当九宫重排需要很多步骤时,第n层结点综合代价会比低层结点(比如第2层)综合代价高,程序反而会去低层结点寻找新结点

  1. 当前结点从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值还有跟多可以深入的地方,如何让待探寻节点有个性化?如何让最终路径近似平滑的曲线?在这里有深刻的思考与君共勉

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值