状态空间搜索
http://zhan.renren.com/aiiyuu?from=bar
本文为aiiYuu精心整理,转载请注明出处O(∩_∩)O --aiiYuu
状态(ststus)是对问题在某一时刻的进展情况的数学描述;
状态转移(state-transition)是问题从一种状态转移到,另一种(或几种)状态的操作。
搜索的目的:判断智能体(agent)能否从起始状态(start state)到达目标状态(goal state)。
智能体数量 > 1 => 多人博弈
状态空间:搜索的目的实际上是在遍历一个隐式图,他的节点是所有的状态,有向边对应于状态的转移,而一个可行解就是一条从起始节点出发到达目标状态集中任意一个节点的路径。这样的图称为状态空间(state space),这样的搜索称为状态空间搜索(Single-agent Search),得到的遍历树称为解答树。
盲目搜索算法的种类:
纯随机搜索(Random Generation and Random Walk):你在慌乱中找东西的时候,往往都是进行随机搜索‘
深度优先搜索(DFS):沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所有边都被搜索过,将回溯到发现发现v的那条边的起始节点。这一过程一直持续到已发现从原点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点重复以上过程,整个进程反复进行直到所有节点都被访问为止。
DFS的剪枝:当对u的下一节点Vi,当通过条件判断已知Vi不可达或者再对Vi搜索下去已不能得到所要解时,则不对Vi进行搜索。
DFS(G)
for each vertex u∈V[G]
do color[u] <- WHITE
Π[u] <- NIL
time <- 0
for each vertex u∈V[G]
do if color[u] = WHITE
then DFS-VISIT(u)
DFS-VISIT(u)
color[u] = GRAY △whille vertex u has just been discovered
time <- tine + 1
d[u] <- time
for each v ∈ Adj[u] △explore edge(u,v)
do if color[v] = WHITE
then π[v] <- u
DFS-VISIT(v)
color[u] <- BLACK △Blacken u , it’s finished.
f[u] <- time <- time + 1
广度优先搜索(BFS):从任意节点u开始,一次搜索其可以扩展的每一个节点v1,v2,...,当一层节点全部搜索完成后,再依次搜索第一个可扩展节点可以扩展的所有节点,直到所有节点均被访问,则算法中止。
BFS(G , s)
for each vertex u ∈V[[G]-f{s}
do color[u] <- WHITE
d[u] <- ∞
Π[u] <- NIL
color[s] = GRAY
d[s] <- 0;
Q <- φ
ENQUEU(Q,s)
while(Q ≠φ)
do u <- DEQUEUE(Q)
For each v∈Adj[u]
do if color[v] = WHITE
then color[u] <- GRAY
d[v] <- d[u]+1
Π[v] <-u
ENQUEUE(Q,v)
color[u] <- BLACK
重复式搜索:这些搜索对通过树扩展做一些限制,用逐步放宽条件的方式进行重复搜索,这些方法包括:
迭代加深搜索(Iterative Deeping):限制搜索树的最大深度Dmax,然后进行搜索。如果没有解就加大Dmax然后再搜索。虽然这样进行了很多重复工作,但是因为搜索的工作量与搜索的深度呈指数关系,因此上一次(重复的)工作量比起当前的搜索量来说是比较小的。这种方法适合搜索树总的来说又宽又深,但是可行解却不是很深的题目。
迭代加宽搜索(Iterative Broading):限制从一个节点扩展出来的子节点的最大值。
柱形搜索(Beam Search):限制每层搜索树节点总数的最大值Wmax。
双向广度优先搜索:从初始节点和目标节点开始分别作广度优先搜索,每次检测两边是否重合。每次扩展节点后总是选择节点比较少的一边进行下次搜索,不是机械的两边交替。
周界搜索(Porimetet Search):先反向搜索一次,把节点都保存起来再正向搜的方法。
启发式搜索:能使用启发搜索算法的特点是能够定义目标状态的特征,并且利用启发式规则计算出当前状态距离目标状态的差距。通过对每一个搜索状态进行评估,得到最好的状态,再从这个状态进行搜索得到目标。因此,在启发式搜索中,对状态的评估是十分重要的,采用不同的评估方法可能会有不同的效果。
用来评估节点重要性的函数称为评估函数,其一般形式为:f(s)=g(s)+f(s),式中:
s 表示问题的任何一种状态;
g(s) 表示从初始状态到状态s的实际代价;
h(s) 是s的估价函数,表示s到目标状态节点的最有搜索路径的评估代价,体现了问题的启发式信息;
f(s) 是s的启发函数,也就是到达目标的总代价的估计。
启发式搜索有如下分类:
贪心搜索/最好优先搜索(Best-First Search):是搜索的理想状态,每次从最有希望的节点开始,扩展其所有的子节点,然后计算所有还未被搜索到的节点的估价函数,基于此即可从所有节点中选择最优的节点进行下一次搜索。
Best-First-Search()
确定可能的开始状并测量他们和目标状态的距离f=g+h
把这些节点插入表中
while(表L不空)
do 取出距离最小的节点;若有多个节点,则从中任选一个(假设为s)
if 到达目标状态
do 返回s以及从初始节点开始的路径,并结束
else do 从L中删除节点s,把该节点的所有子节点放入L表中,并标记从初始节点开始的路径。
A*搜索:对于每个状态,可以找出其所有的子状态,然后根据f(s)的代价,选取一个代价最低的往下搜索。
A*算法的实现:算法需要按照f函数值递增的顺序扩展节点。由于每次取f函数最小的节点,需要设置一个带扩展节点的节点表一(进行“取最小值”,“插入”和“替换”操作),又因为需要判重,所以还需要保存所有的节点(包括已扩展的和带扩展的)的表二(进行“查找并插入”操作)。
IDA*算法(Korf与1985年提出,Itarative Deeping A*):借用DFS的空间优势,用重复搜索的方式来缓解A*算法空间需求大的问题。
IDA*的优势:①不需要判重,不需要排序,只用到栈,操作简单;②空间需求大大减少,与搜索树大小呈对数关系。
DFSBnB算法:DFS+最优性剪枝。
博弈问题算法(多智能体博弈)
局面估价函数:我们给每个局面(state)规定一个估价函数值f,评价他对于己方的有利程度 。胜利的局面的估价函数是∞,而失败的局面的估价函数值为-∞。
Max局面:假设这个局面轮到己方走,有多种决策可以选择,其中没中决策导致一种子局面(sub-stste),该局面的决策函数值等于子局面f值的最大值。
Min局面:假设这个局面轮到对方走,有多种决策可以选择,给局面的决策函数等于子局面的最小值。
终结局面:如果双方都不能走,显然胜负已分(假设没有和局),f值根据规定取值。
完全极大极小过程:对于一个局面,递归计算他所有的子局面的估价函数值,如果是Max层,转移到其函数值最大的子局面,否则转移到函数值最小的子局面。可以把已经算过的估价函数的局面都记录下来,以免重复工作。
主观估价值:通常规定计算的最大递归深度maxdepth,如果达到了该深度,f值就是按照某种主观规律得到的估价值。
Alphs-Beta剪枝:对于一个Max局面S,已经计算除了他的子局面S1,(为Min局面)的f值为5,而现在正在计算S的第二个子局面S2、(也是Min局面)局面的值,没有计算完。假设我们已知得知了S2的某个子局面的f值为2,那么S2的f值至多才是2,比S1的f值小,因此,选取S1肯定比S2好,应马上停止计算S2,对于Min局面,与此类似。
剪枝的方法:
1.极端法:通过对当前节点进行理想式扩展,通过否定这样的“理想情况”来避免对当前节点的扩展。
2.2.调整法:通过对子树的比较剪掉重复子树和明显不是最有“前途”的子树。
3.3.数学方法:列入图论中借助连通分量解决独立集的着色问题,数论中借助模线性方程解决开关切换一类的问题。
*路径寻找问题
*约束满足问题
搜索的应用
(一)动态规划中的记忆化搜索
例:上山问题
1
2 3
4 5 6
7 8 9 10
11 12 13 14 15
如图所示,有一座高度n = 5的山,小明要从山的底层向左上或忧伤的位置走,一直走到山顶,一路上他获得他所到达过的点上的数字,问:从山底走到山顶能够获得的最大的数字和是多少?
方法一:自底向上的递推
for i <- n to 1
for j <- 1 to i
f(i,j) = max{f(i+1),f(i+1,j+1)}+value(i,j)
方法二:自顶向下的递归
DFS(i,j)
if(i = n) return value(i,j)
if(VISITED(i,j)) return f(i,j)
Return max{DFS(i+1,j),DFS(i+1,j+1)}
方法二对已经计算过的情况进行了备忘(memorize),避免了对重叠子问题的再次计算。
→记忆化搜索
(二)景区覆盖问题和舞蹈链X(Dancing Links X)算法→优化搜索的一种工具
精确覆盖问题(Exact Cover Problem):有一些由整数1~n组成的集合S1,S2,S3,...,Sn,要求选择若干集合Si,使得1~n的每个整数恰好在一个集合中出现。比如,n=7,S1={1,4,7},S2={1,4},S3={4,5,7},S4={3,5,6},S5={2,3,6,7},S6={2,7},则一个精确覆盖为S2,S4,S6,因为{1,4},{3,5,6},{2,7}无重复,无遗漏地包含1~7的所有整数。
我们可以利用r行n列的0-1举证矩阵来表示一个精确覆盖问题,其中第i行第j列元素表示Si是否包含元素j(1表示包含,0表示不包含)。而n=7,S1={1,4,7},S2={1,4},S3={4,5,7},S4={3,5,6},S5={2,3,6,7},S6={2,7},可以表示为一个6行7列的矩阵。
1 2 3 4 5 6 7
S1 1 0 0 1 0 0 1
S2 1 0 0 1 0 0 0
S3 0 0 0 1 1 0 1
S4 0 0 1 0 1 1 0
S5 0 1 1 0 0 1 1
S6 0 1 0 0 0 0 1
算法X(Algorithm X)。和普通的回溯法一样,我们可以编写一个递归过程求解精确覆盖问题。每次选取一个没有被覆盖的元素,然后选一个包含它的集合进行覆盖。对应到矩阵中,如果我们删除所有已经选择的行和已被覆盖的列,则这个过程可以描述为每次选择一个没被删除的列,然后枚举该列为1的所有行,尝试删除该行,递归搜索后恢复该行。删除行时,除了需标记“此行已删除”之外,同时还需要把该行中值为1的列删除,恢复是也一样。
舞蹈链(Dancing Links)。删除列兵不是一个简单的操作,因为还需要把覆盖它的行也一并删除掉(因为没列只能被一行所覆盖)。为了提高效率,我们需要一种能高效支持上述操作的数据结构,他就是Knuth的舞蹈链(Dancing LInks)。使用了舞蹈链的算法X通常称为DLX算法。
舞蹈链是一个4个方向的循环链表结构,每个普通节点对应矩阵中的一个1,另外还有n+1个虚拟节点,其中每列最上方有一个虚拟节点,而所有虚拟节点最前面有一个头结点h。每个节点记录5个指针:L,R,U,D和C。
L和R:表示该节点所在行的左边相邻节点和右边节点(由于是循环链表,每一行的最左边节点的L指针指向这一行的最后一个节点;最右边节点的R至指针指向这一行的第一个节点)。
U和D:表示该节点所在列的上边相邻节点和下边相邻节点(每一列的最上方虚拟节点的U指针指向这一列的最下方节点,最下方节点的D指针指向最上方的那个虚拟节点)。
C:指向该节点所在列虚拟节点。
参考资料:
【1】算法导论
【2】南京理工acm训练指导书
【3】刘汝佳老师《算法竞赛入门经典--训练指南》
本文同时发表在aiiYuu的cnblogs上