(注:这是一篇学习笔记,其实是写的第2遍.昨晚3点多快要收尾时,电脑啪的一声关机了.当我想起用的编辑器是gedit时,一口鲜血很华丽地喷在屏幕上.只怪我手欠没用vim..因为得重新写一遍,所以很多地方简化了很多.你完全可以挑着想看的看,没有什么影响.)
宽度优先搜索
宽搜与深搜一道,是最简便的图搜索算法之一.这一算法也是其他很多重要的搜索算法的原型.诸如带贪婪的搜索,双向搜索,A*搜索等,其实都可以认为是基于宽度优先搜索的.之所以称之为宽度优先,是因为算法每次扩展的节点都是当前节点的直接后继,也就是说,算法首先搜索和s距离为k的所有节点,然后才搜索和s距离为k+1的所有节点.因此,宽搜找到的必定是最短路径.下面是宽度优先搜索的伪代码:
声明两个空的链表: Open 和 Closed.
将起始节点压入Open 表中.
While Open表非空:
移出Open表首元素X.
If X为目标节点:
跳出While循环,将X压入Closed表中,返回成功信息.
Else 继续.
For X的所有直接后继节点Y.
将Y压入Open表尾.
将X压入Closed表中.
End While
对于宽搜,我们并不关心其复杂度,因为宽搜实际上使用得并不多.当然,如果你有兴趣的话,可以参考<Artificial Intelligence : A Modern Approach>这本书.我们更多的是关心宽搜能解决些什么问题.用宽搜解决迷宫问题是数据结构课上的内容,下面的问题你可以思考下如何解决:
[题目]魔板(via: sicily 1150)由8个大小相同方块组成,分别用涂上不同颜色,用1到8的数字表示。
其初始状态是
1 2 3 4
8 7 6 5
对魔板可进行三种基本操作:
A操作(上下行互换):
8 7 6 5
1 2 3 4
B操作(每次以行循环右移一个):
4 1 2 3
5 8 7 6
C操作(中间四小块顺时针转一格):
1 7 2 4
8 6 3 5
用上述三种基本操作,可将任一种状态装换成另一种状态。
编写一程序,对于给定的目标态,输入到达的最短路径上的所有基本操作序列.不能到达输出-1.
深度优先搜索
和宽度优先搜索不一样的是,深度优先搜索的搜索策略是尽可能深地搜索图.换句话说,深搜其实就是在搜索树的每一层始终先扩展一个子节点,不断地递归该过程,直到不能再牵及,然后才从当前节点返回到上一级节点,沿另一方向继续搜索.需要注意的是,深搜的搜索策略是不完备的,而且,深搜得到的解并不一定就是最优解.下面是深度优先搜索的伪代码(注意它与宽搜的异同):
声明两个空的链表: Open 和 Closed.
将起始节点压入Open 表中.
While Open表非空:
移出Open表首元素X.
If X为目标节点:
跳出While循环,将X压入Closed表中,返回成功信息.
Else 继续.
For X的所有直接后继节点Y.
将Y压入Open表首.
将X压入Closed表中.
End While
我们可以看到,深搜和宽搜的代码基本上是一样的,也许实现上,对于数据结构的选择可以会有略微不同.因为深搜得到的结果可能是局部最优的,因此除非深搜的完备性很清楚,一般用宽搜而不是深搜.
[题目]下面是一个来自<the art of java>的题目,你可以试着使用深搜解决它:
航班问题:一位顾客要预定一张从New York到Los Angeles的航班机票,下面是航班线路,请你为顾客找一种购票方案。
航班 距离
New York到Chicago 900英里
Chicago到Denver 1000英里
New York到Toronto 500英里
New York到Denver 1800英里
Toronto到Calgary 1700英里
Toronto到Los Angeles 2500英里
Toronto到Chicago 500英里
Denver到Urbana 1000英里
Denver到Houston 1000英里
Houston到Los Angeles 1500英里
Denver到Los Angeles 1000英里
带贪心的搜索
带贪心的搜索其实是指在搜索时,算法根据某信息进行贪心搜索.如dijkstra算法.它与A*算法类似,但是却没有使用启发函数.贪心算法和动态规划一样,更多的是一种思想,如何使用则需灵活选择.比如,在马周游问题中,可以每次选择8连节点(马最多可以跳往8个方向)中入度最小的那个节点进行扩展.这里,节点Y的入度指能跳到它的节点X的个数,已跳过的X不算.找出贪心策略需要有比较清晰的理解,最好的办法就是多做些题.
[题目] 马周游问题 (via: sicily 1152)
在一个5 * 6的棋盘中的某个位置有一只马,如果它走29步正好经过除起点外的其他位置各一次,这样一种走法则称马的周游路线,试设计一个算法,从给定的起点出发,找出它的一条周游路线.
为了便于表示一个棋盘,我们按照从上到下,从左到右对棋盘的方格编号,如下所示:

对输入的每一个起点,求一条周游线路.对应地输出一行,有30个整数,从起点开始按顺序给出马每次经过的棋盘方格的编号.
双向搜索
双向搜索是指搜索同时在正反两个方向进行,正反向定义为:
正向搜索:从起始节点向目标节点搜索
反向搜索:从目标节点向起始节点搜索
注意,双向搜索中,正反两个方向是交替进行的,当两个方向上搜索到同一子节点时,程序结束.该子节点我们一般称为"相交点",可以证明,如果存在从起始节点到目标节点的最优路径,那么双向搜索的两个方向必定相交,从起始节点到相交点再到目标节点的路径即是最优路径.双向搜索的伪代码如下:
创建Open队列和Closed队列
将起始节点压入Open队列,将目标节点压入Closed队列
While
移出Open队列首元素X
For X的所有直接后继节点Y
If Y是否已经搜索过
跳出While循环,输出成功信息
Else 将Y压入Open队列中
End For
移出Closed队列首元素X'
For X'的所有直接后继节点Y'
If Y'是否已经搜索过
跳出While循环,输出成功信息
Else 将Y'压入Closed队列中
End For
End While
使用双向搜索可以让程序更快,比如下面的八数码问题,如果双向搜索函数写的好的话,它甚至于比下文要介绍的A*算法还快,在sicily上提交,0.01秒过了,暴爽.
/* source code of submission 273100, Zhongshan University Online Judge System */
#include
#include