【人工智能】Java实现贪婪最佳优先搜索(Greedy Best-first Search)

1.原理

无信息搜索策略(Uninformed Search Strategies)是盲目的,不是很有效,当然也没有除问题定义的状态信息外的额外信息。而有信息搜索策略(Informed Search Strategies)提供了一些额外的信息,如到达目标的直线距离等,因此有信息搜索策略能更有效地进行问题求解。

最佳优先搜索(Best-first Search)使用评估函数f(n)(一般使用启发函数h(n))来选择被扩展结点。h(n)一般只依赖于目标结点,如在下图中要求4=>6的路径,可以提供6到所有结点的直线距离作为h(n),当然h(6)=0(这个直线距离如何求?地图上有比例尺啊)。假设6到其他结点的距离分别为<3,1,2,5,4,0,1,1,6>,也就是到1为3,到2为1,到3为2......。

(1)首先扩展结点4,其后继结点有3、5、9,这三个顶点到6的直线距离分别为2、4、6,显然最短的是结点3,优先级队列:<43:2,  45:4,  49:6>。

(2)下一次出队的就是43,将扩展结点3,其后继结点是2、5、9、4(在当前路径上,丢弃),其中2、5、9到6的直线距离是1、4、6,优先级队列:<432:1,  435:4,  45:4,  39:6,  439:6>。

(3)现在出队的是432,将扩展结点2,其后继结点是1、6、8、3(在当前路径上,丢弃),同样1、6、8到6的直线距离为3、0、1,优先级队列:<4326:0,  4328:1,  4321:3,  435:4,  45:4,  439:6,  49:6>。

(4)4326出栈,发现找到目标结点6,所以搜索结束。(Java的优先队列用迭代器输出时并不保证排好序,但队首一定是最小的(不改变比较器排序规则))

2.编码实现

本次编码的数据结构如前文(参见https://blog.csdn.net/obestboy/article/details/86604030)所述一样。

思路:贪婪最佳搜索与一致代价搜索UCS有点像(考虑将启发式函数设置为代价函数?),用优先级队列来存储候选路径,需要按照启发函数估计出的代价来排序,因此需要重新其比较器,用explored存储已探索结点,用allPath存储已找到路径。在寻找后继结点时,将其添加到候选路径优先级队列里。

public  ArrayList<costPath>  gbfs(int src,int dst,int[]  estimateDistance) {
        if (src < 0 && dst < 0 && src >= verNum && dst >= verNum) return null;
         //allPath存储找到的路径
        ArrayList<costPath> allPath=new ArrayList<>();
        if (src == dst)
        {
            allPath.add(new costPath(String.valueOf(src), 0));
            return allPath;
        }
        //priFrontier存储候选路径队列,用优先级队列实现,这点与一致代价搜索UCS很像
        //costPath是一个路径、代价、估计代价(启发式函数计算出的值)结构
     PriorityQueue<costPath> priFrontier=new PriorityQueue<>(new Comparator<costPath>() {
            @Override
            public int compare(costPath o1, costPath o2) {
                return o1.estCost-o2.estCost; //按估计代价排序
            }
        });
        //exploried存储已探索结点
        ArrayList<Integer>  explored=new ArrayList<>();
        priFrontier.offer(new costPath(String.valueOf(src), 0));
        while(!priFrontier.isEmpty())
        {
            costPath cp = priFrontier.remove(); //出队
            //拆分路径,结点间以“-”符号隔开
            String[] pathSplit=path.split("-");
            //当前路径最后一个结点,将要被扩展的结点
            int v=Integer.parseInt(pathSplit[pathSplit.length-1]);
            //表示该结点已探索
            explored.add(v);
            if(v==dst)
            {
                allPath.add(cp); //找到路径,添加到结果路径集里
               // return  allPath; //只要求找到的话,直接返回即可
               // 也可设置计数器,返回指定条数路线(如果有的话)
            }
            else
            {
                for(int i=0;i<edges.length;i++)
                     //isInStringArray结点i是否在pathSplit当前路径数组
                    if(edges[v][i]>0 && (!explored.contains(i)
                            || !isInStringArray(pathSplit,i)))
                         priFrontier.offer(new costPath(cp.path+"-"+i,
                              cp.cost+edges[v][i],estimateDistance[i]));
            }

        }
        return  allPath;
    }

3.测试、结果

代码与前文差不多。

public class Main {

    public static void main(String[] args)
    {
        String[] vers = {"c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9"};
        int[][]  edges={
                {0,5,0,0,0,0,0,2,0},
                {5,0,2,0,0,1,0,0,0},
                {0,2,0,4,5,0,0,0,2},
                {0,0,4,0,2,0,0,0,3},
                {0,0,5,2,0,6,0,0,0},
                {0,1,0,0,6,0,1,2,0},
                {0,0,0,0,0,1,0,3,0},
                {2,0,0,0,0,2,3,0,0},
                {0,0,2,3,0,0,0,0,0}
        };
        Graph  g=new Graph(vers,edges,9,13);
        int[] estimateDistance={3,1,2,5,4,0,1,1,6};//代替启发函数h(n)
        System.out.println("Greedy best-first Search:");
        g.parsePath1(g.gbfs(g.searchVertex("c4"),g.searchVertex("c6"),estimateDistance));
    }
}

运行结果如下:
Greedy best-first Search:

c4=>c3=>c2=>c6,Cost:7
c4=>c3=>c2=>c1=>c8=>c6,Cost:15
c4=>c3=>c2=>c1=>c8=>c7=>c6,Cost:17
c4=>c3=>c5=>c6,Cost:15
c4=>c5=>c6,Cost:8
c4=>c5=>c3=>c2=>c6,Cost:10
c4=>c5=>c3=>c2=>c1=>c8=>c6,Cost:18
c4=>c5=>c3=>c2=>c1=>c8=>c7=>c6,Cost:20
c4=>c9=>c3=>c2=>c6,Cost:8
c4=>c9=>c3=>c2=>c1=>c8=>c6,Cost:16
c4=>c9=>c3=>c2=>c1=>c8=>c7=>c6,Cost:18
c4=>c9=>c3=>c5=>c6,Cost:16

比较幸运的是最短路径排在最前面,但接下来并不是按照由小到大顺序排序的。一方面按照从源扩展开始,先将离目标结点直线距离最近的结点依次扩展,直到查找到目标或找不到为止,这的确与DFS很像,所以可以说DFS是最佳优先搜索的特例。

4.分析

最佳优先搜索是一个通用的算法框架,将评估函数f(n)实例化后,就称为贪婪最佳优先搜索(Greedy Best-first Search),它总是扩展代价(估计的代价、启发式函数计算出的估计代价)最小的结点。贪婪最佳优先搜索显然不是最优的,也不是完备的(有限状态空间的图搜索是完备的,但无限的不是完备的),其算法的时间、空间复杂度,在最坏情况下仍然为O(b^m),但是如果有好的启发函数,复杂度应该可以得到有效下降。

5.A*搜索

由于最佳优先搜索(Best-first Search)评估结点的函数只是一个估计值,而没有考虑搜索过程中已经获取的信息,正因为此,它不是最优的。为了该进这个算法,结合一致代价搜索UCS的已经花费的代价来综合启发式误差,使得代价评估更接近真实状况。换句话说:f(n)=g(n)+h(n)。当然也可以设置g(n)、h(n)的权重比例。

这样A*搜索就拥有了g(n)的特征,同时也拥有了h(n)的特征,也就是兼具了最佳优先搜索的速度与一致代价搜索的最优性与完备性。关于A*搜索代码的实现,其实只要对贪婪最佳优先搜索修改一个地方的代码即可。

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值