一、搜索的bfs,宽度优先搜索,一般用于求最短的得到到目的地的距离,有个起始点,先把这个起始点入队列,不要忘记将这个起始点标记为已经利用,不然会走回来的,然后是与这个起始点的周围的点的监测,若可以行的通,我们一一检测这些扩展出来的点,若是目的地就结束了,若不是目的地,将改点标记为已经利用,并将该点入队列。
二、搜索的双向bfs,双向的bfs一般来说可以用单向解决,但是单向的效率上还是差了点,适合双向bfs的题目有一些共同特点:给出起始和最终状态,让求出一条从初始到末状态的最短路。
双向bfs的实现过程: 从初始结点开始扩展,每扩展一层,在从目标节点按照产生系统相反的办法来扩展结点,直到从两端扩展出的某个结点相遇,那么中间的这条路径一定就是从初始到终点最短的路径。
注意: 双向广搜必须按层扩展,才能保证结果的正确性
无解状态就和普通bfs无异, 所以判断无解函数不可省略.
路径的保存:有时候除了要找到一个目标状态,还需要保存从初始结点到它的路径。路径可以随状态保存,也可以用链表穿起来。如果保存的所有结点或者路径长度不定,推荐使用链表。
判重:除非隐式图是无环的,或者有很强的结点扩展方式来避免生产重复,广度优先搜索是需要判重的。但要注意判重函数的费用!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
int
DBFS()
{
Queue Q[2];
Map M[2];
int
now=0;
Q[0].push(start);
Q[1].push(end);
M[0][start]=M[1][end]=
true
;
While( !Q[0].empty() || !Q[1].empty)
{
int
nowg=now&1,count=Q[nowg].size();
while
(count--)
{
curstate=Q[nowg].front();
Q[nowg].pop();
if
( M[nowg^1].find(curstate) != M.end() )
return
now;
for
every extstate=extend(curstate)
if
(M[nowg].find(extstate) == M.end())
{
Q[nowg].push(extstate);
M[nowg][extstate] =
true
;
}
}
++now;
}
return
-1;
}
|
深度优先搜索dfs
一、深度优先搜索一般是给定搜索的深度,或者没有深度有结束条件的,可以用结束条件代替深度,都可以用dfs
二、dfs回溯用来解决搜索所有解的问题,但是纯粹的dfs回溯往往会超时,一般与剪枝联系在一起
三、剪枝一般分为可行性剪枝和最优化剪枝
(1)可行性剪枝, 剪去不能到达终点的路径
(2)最优化剪枝, 剪去不能到达终点和不可能是第一个到达终点的路径
迭代加深搜索
一、用于不知道最短转移次数,为了弥补广搜的空间不足的问题而产生的搜索方法
形式为:for( depth=init(); dfs() ; depth++ )
使用是要注意:
(1)由于不知道上界, 如果问题无解的情况下就不能用这个了
(2)对深度较浅的层数会重复搜索,并不推荐使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
Procedure ID-dfs(dep:integer);
Var
J: integer;
Begin
If dep>深度的限界 then
exit
;
// 如果搜索的深度大于限界,则返回上一层
For j:=1 to n
do
// 按照规则生成子结点
If 子结点安全 then
Begin
入栈;
If 子结点是目标结点 then 对目标结点进行处理,退出程序
Else id-dfs(dep+1);
退栈;
End;
End;
For i:=1 to depmax
do
// 枚举深度的限界
Begin
Id-dfs(i);
If 运行超时 then
break
;
End;
|
从上述迭代加深搜索算法的实现过程和框架,我们可以看出,迭代加深搜索算法就是仿广度优先搜索的深度优先搜索。
既能满足深度优先搜索的线性存储要求,又能保证发现一个最小深度的目标结点。
A*算法
A*类似贪心的BFS
BFS一般扩展最小消耗的点,A*算法在另一方面 扩展最有希望的点(估价函数返回值最优)
状态被保存在一个优先级队列中,按照cost*价值排列。对一个可容许的估价函数,第一个找到的状态保证是最优的。
一、A*算法属于启发式搜索,扩展结点的顺序是基于广度优先搜索,但是不同之处在于每扩展一个子结点需要计算估价函数f(s),以估算起始结点,经过该结点到达目标结点的最佳路径代价,每当扩展结点时,总是希望在所有待扩展结点中选择具有最小
f(s)值的结点作为扩展对象,以便使搜索尽量沿着最有希望的方向进行。
假设s表示一个状态,
f(s):启发式函数,表示从初始状态开始,经过状态s的路径上最少需要多少代价才可以找到终点之一。
g(s):从初始状态到当前状态s的最小代价
h(s):估价函数,从当前状态s到达终点之一最少需要多少代价
有关系式f(s)=g(s)+h(s)
一种具有f(n)=g(n)+h(n)策略的启发式算法能成为A*算法的充分条件是:
A*必须满足两点:
h(s)<=h*(s) 和 f(s)必须是递增的
1) 搜索树上存在着从起始点到终了点的最优路径。
2) 问题域是有限的。
3)所有结点的子结点的搜索代价值>0。
4)h(n)=<h*(n) (h*(n)为实际问题的代价值)。
当此四个条件都满足时,一个具有f(n)=g(n)+h(n)策略的启发式算法能成为A*算法,并一定能找到最优解。
对于一个搜索问题,显然,条件1,2,3都是很容易满足的,而
条件4): h(n)<=h*(n)是需要精心设计的,由于h*(n)显然是无法知道的。
所以,一个满足条件4)的启发策略h(n)就来的难能可贵了。不过,对于图的最优路径搜索和八数码问题,有些相关策略h(n)不仅很好理解,而且已经在理论上证明是满足条件4)的,从而为这个算法的推广起到了决定性的作用。不过h(n)距离h*(n)的程度不能过大,否则h(n)就没有过强的区分能力,算法效率并不会很高。对一个好的h(n)的评价是:h(n)在h*(n)的下界之下,并且尽量接近h*(n).
当然,估值函数的设计也就就仅仅是f(n)=g(n)+h(n)一种,另外的估值函数“变种”如:f(n)=w*g(n)+(1-w)*h(n) ,f(n)=g(n)+h(n)+h(n-1)针对不同的具体问题亦会有不同的效果。
A*算法最为核心的过程,就在每次选择下一个当前搜索点时,是从所有已探知的但未搜索过点中(可能是不同层,亦可不在同一条支路上),选取f值最小的结点进行展开。而所有“已探知的但未搜索过点”可以通过一个按f值升序的队列(即优先队列)进行排列。这样,在整体的搜索过程中,只要按照类似广度优先的算法框架,从优先队列中弹出队首元素(f值),对其可能子结点计算g、h和f值,直到优先队列为空(无解)或找到终止点为止。
A*算法与广度优先和深度优先的联系就在于,当g(n)=0时,该算法类似于DFS,当h(n)=0时,该算法类似于BFS , 这一点,可以通过上面的A*搜索树的具体过程中将h(n)设为0或将g(n)设为0而得到。
路径最优问题,简单来说,就是在两个结点之间找一条最短路径。有的朋友不禁要问,这个问题不是已经有Dijkstra算法可以解决了吗?此话不假,但是不要忘了Dijkstra算法的复杂度是O(n^2),一旦结点很多并且需要实时计算的话,Dijkstra就无法满足要求了。而A*来处理这类有需要实时要求的问题则显得游刃有余。
在路径最优问题中,用来作为启发函数关键部分的h(n)其实很容易选,那便是当前结点至最终结点的距离,这个距离既可以是曼哈顿距离(|x1-x2|+|y1-y2|),亦可以是Euclid(欧几里得或者欧式距离) 距离(直线距离)。都可以在较快的速度下达到问题的最优解。
伪代码
主要搜索过程伪代码如下:
创建两个表,OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点。
算起点的估价值;
将起点放入OPEN表;
while(OPEN!=NULL)
{
从OPEN表中取估价值f最小的节点n;
if(n节点==目标节点){
break;
}
for(当前节点n 的每个子节点X)
{
算X的估价值;
if(X in OPEN)
{
if( X的估价值小于OPEN表的估价值 ){
把n设置为X的父亲;
更新OPEN表中的估价值; //取最小路径的估价值
}
}
if(X inCLOSE) {
if( X的估价值小于CLOSE表的估价值 ){
把n设置为X的父亲;
更新CLOSE表中的估价值;
把X节点放入OPEN //取最小路径的估价值
}
}
if(X not inboth){
把n设置为X的父亲;
求X的估价值;
并将X插入OPEN表中; //还没有排序
}
}//end for
将n节点插入CLOSE表中;
按照估价值将OPEN表中的节点排序; //实际上是比较OPEN表内节点f的大小,从最小路径的节点向下进行。
}//end while(OPEN!=NULL)
保存路径,即 从终点开始,每个节点沿着父节点移动直至起点,这就是你的路径