搜索算法总结

来源:互联网 发布:北京折叠知乎 编辑:程序博客网 时间:2024/05/17 17:54

搜索算法广泛应用于人工智能领域,但是由于时间复杂度是指数级别,计算机科学家在朴素的搜索算法(广度优先,深度优先搜索)上优化得到了一系列搜索算法

 

本文介绍的搜索算法主要指:广度优先搜索,深度优先搜索,以及在此基础上优化得来的

A*算法,分支限界算法。如有错误欢迎指正。

 

为了便于描述,搜索算法适用解决在一张有权无向图中,找到从原点到终点的最短路径。

基础搜索算法的通式是这样的:

Wait_arr[]数组存放待扩展的节点。

初始化:把初始节点root加入到wait_arr[]数组中

While( wait_arr[]数组不为空 ){

wait_arr[]数组中取出左边第一个节点n

把取出节点扩展出邻接子节点

For( 遍历筛选每个邻接子节点 ){

     If( 子节点没有出现在path中 ),留下这个子节点,然后记录这个子节点的父亲是节点

}

If( 还存在子节点 )

     子节点加入到wait_arr[]中。

}

注:由于每个子节点都保留了它的父亲节点,所以很容易逆推出到达该子节点的路径path

深度优先与广度优先搜索的区别与联系:

深度优先和广度优先的区别,从上面的通式中可以解释为红字部分的不同:

深度优先搜索:邻接子节点加入wait_arr[]数组的最左边,所以每次扩展的节点都是新的节点。

广度优先搜索:邻接子节点加入wait_arr[]数组的最右边,所以每次扩展的节点都是老一辈的节点。

 

A*算法与朴素搜索算法的区别与联系:

A*算法实际上是模拟这么一个过程,在广场上,有一些遮挡物,人要从B点越过遮挡物到达出口E点,应该怎么做呢?人每一次可以选择向前,向后,向左,向右四种走法。但是人比较聪明,每一次选择自己记忆中直线距离离E点最近的那个方向迈步,如果不行再回溯试其他的方向。这就是A*算法。

A*算法和之前的朴素的搜索算法有什么区别和联系呢?

 

估价函数 

f(n) = h(n) + g(n);   其中f(n)是代表当前节点n的估计代价,g(n)是从源节点到当前节点的已知代价,h(n)是当前节点到目标节点的估计代价。

  • A*算法和朴素搜索算法的区别与联系之一依然在基础搜索算法通式的红字部分。每次把新的节点加入到wait_arr[]最左边之后,a*算法会计算新的节点的估价函数,然后把wait_arr所有点根据估价函数进行排序,把最优的节点放在wait_arr的最左边。这样每次扩展的节点都是目前认为最优的方向。
  • 另一个区别在于,当找到一个目标节点后,a*算法就退出。

 

A*算法的通式:

A*算法的通式 1

 

Wait_arr[]数组存放待扩展的节点。

初始化:把初始节点root加入到wait_arr[]数组中

While( wait_arr[]数组不为空 ){

wait_arr[]数组中取出左边第一个节点n

If( 是目标节点 )  break

把取出节点扩展出邻接子节点

For( 遍历筛选每个邻接子节点 ){

    If( 子节点没有出现在path中 ),留下这个子节点,然后记录这个子节点的父亲是节点

}

If( 还存在子节点 )

    子节点加入到wait_arr[]中。并计算估值函数f(n)

把所有的点按照估值函数排序。

}

 


A*算法通式 2

Open表 存放待扩展节点

Close表 存放访问过的节点

初始化:把初始节点root加入open表中

While( open表不空 ){

open表中取出左边第一个节点,加入到close表中

If( 是目标节点 输出路径,break

把取出节点扩展出邻接子节点

for( 遍历筛选每个邻接子节点 ){

    子节点中记录下父节点

    求出子节点估值函数f(n)

        If( 子节点不在open表中 也不在close表中 ){

            加入open

        }else if( 子节点在open表中 ){

            If( 新的估值函数比open表中原有子节点的更优 ){

               Open表中原来的子节点被替换

            }

        } else{ //close表中

            If( 新的估值函数比close表中原有子节点的更优 ){//可以保证不会出现回路,不用判断这个子节点是否曾经出现在path

                Close表中原来的子节点被删除 

                Open表中加入新的子节点

             }

        }

}

open表按照估价函数从优到劣排序。

}

 

通式1和通式2都可以得到正确解

通式相较于 通式有什么优点? 

1. 通式1如果只维护一条路径(就是单纯记录扩展节点的父节点),在算法执行过程



红色路径是先前的较短路径,扩展到a点后发现c点的估价函数更优,转而扩展c得到c的子节点b (毕竟b不曾出现在蓝色路径上)。但实际上蓝色路径比最左边红色那一段路径长,这就会使得起始点到b点的路径被覆盖为蓝色路径(因为b的父节点会被替换为c)。  然而通式2只有在蓝色路径比最左边一小段红色路径短的时候才会将b的父节点替换为c

 

进一步我们可以想见,对于广度优先搜索,最优优先搜索都会面临路径被覆盖的问题,而深度优先搜索则不会,所以如果要输出所有路径,优先使用深度优先搜索(如果想要优化,可以结合分支限界法剪枝)

 

 

A*算法的相关定理:

A*算法能否找出最优解,取决于估价函数f(n)中的h(n)部分。

假设:F(n) = H(n) + g(n)   H(n)是从当前节点到目标节点的实际路径长(当然这是目前无法得知的)

如果总有H(n) >= h(n),那么找到的路径一定是最短路径。

证明:假如最短路径p的长度是s,那么由于总有H(n) >= h(n),所以F(n) >=f(n)

假如,找到的路径pp长度是ss > s,那么还未搜索到的最短路径的f(n) <= s < ss,不满足A*算法每次扩展f(n)最小的节点的原则,矛盾。 

在满足H(n) >= h(n)的情况下,h(n)越大,也就说明估值函数越精确,需要回溯的机会就越小,效率就越高。

 

相较于朴素的搜索算法,A*算法无法寻找出所有最优解。


A*算法例子,小人从左边一点到右边一点,要越过障碍物,估值函数中的h(n)是当前点到右边那一点的欧几里得距离,算法大概的路线是如下图所示,红色,蓝色是走岔的路径,黄色是最终修正得到的路径。



分支限界法

 

分支限界法

A*算法有相通之处,都有估值函数,而且形式也是相同的:

f(n) = h(n) + g(n)

F_upper(n) = up_h(n) + g(n)

假如我们要找图中最短的从原点到终点的路径,那么我们就需要维护一个最短路径的上界(最短路径最长可能是多少),估值函数是最优解的下界,一般这个上界初始化是由贪心算法得到的。

 

 

分支限界算法基本流程(要求输出所有线路,从深度优先搜索中改编)

 

Wait_arr[]数组存放待扩展的节点。

用贪心算法算出最优解的上界upper

初始化:把初始节点root加入到wait_arr[]数组中

While( wait_arr[]数组不为空 ){

    从wait_arr[]数组中取出左边第一个节点n

    If( 是目标节点 ){ 

        输出路径

        Iff(n)<upper)修改upper

    } else{

        If( 当前节点f(n)>upper ){

            剪枝

        } else{

            If( F_upper(n) < upper ){ 更新upper }

            把取出节点扩展出可扩展的邻接子节点

            For( 遍历筛选每个邻接子节点 ){

                If( 子节点没有出现在path中 ){

                    留下这个子节点,然后记录这个子节点的父亲是节点n

                    计算该子节点的f(n)F_upper(n)

                    子节点加入到wait_arr[]左边。

                }              

            }

        }

    }

}



0 0
原创粉丝点击