关于Search和Uninformed Search

图中的算法多的要命,大多数都是搜索算法吧,因为其应用实在太广了。就比如说机器人眼中的世界其实就是一张graph,graph的大小的有限的(这跟分配给它的memory有关),机器人就根据现有的graph信息边计算边决策,然后边走边获取新graph信息,再重新计算再决策再走……这是一个迭代过程,直到到达目的地为止,汽车导航仪也是类似的。我觉得这过程有点像你用一个英雄去打敌方老窝,在地图还不是完全可见的情况下,你得亲自控制着他,看好路,根据情况选择对应的战斗策略。……哈哈扯远了,这个其实算Informed Search了,也就是带有决策的启发式搜索了(我们将在下一篇中介绍)。我们先来介绍简单的Uninformed Search。

 

什么叫Search?Search就是在所有Search space(状态空间——往往能用树或图来表示出)中找到你想要的那个状态。

Uninformed Search,顾名思义就是消息不灵通的search,被蒙蔽的search,也可以叫做Blind Search(盲目搜索)或者Brute-force Search(也就是传说中的暴力解法)。

 

我们先来看两个搜索的例子。

 

 

如图,我们从A处进入这个空间,我们想要到达J处。当然我们人眼是肯定一眼就看穿路线了,但让机器应该怎么做?

计算机势必得先把这张地图信息存起来,格式只能是计算机能实现的数据结构。

 

 

 

在这棵树(也可以叫图)上,我们把每一个节点视作一个搜索状态,每一条边视作一次搜索动作,只有能从初始节点到达目标节点就算搜索成功了。经典的算法莫过于深度优先搜索和广度优先搜索了(我们将待会儿介绍)。

 

我们再来看一个例子。

有一种两人游戏叫Nim Game,规则是:在一根柱子上放N个大小相同的环,两个人轮流从上面取环下来,规定每人每次最多只能取连续的m个环,不能不取,谁取到最后一个环就算输。

假定现在N=6, m=3,两人游戏过程如图。想想看先取者和后取者谁的胜率较高?进一步再想想看先取者和后取者有没有最有把握胜的策略。

 

 

 

我们将这个问题中所有的状态空间用一棵树来表示出。

 

所有的节点中的数字表示剩下的环的个数,剩下1则表示接下来取的那个人肯定输了,图中的边则表示取下的环的个数。

可见,总共有13种不同的游戏过程,先取者赢的概率为7/13,后取者赢的概率为6/13。更进一步,如果要制定最有把握胜的策略的话,这件事只要交给计算机去做好了。先把所有状态空间都存起来,每当对手取完后,就找到取完那个状态的节点,然后遍历那个节点的子树以计算出胜率最大的下一步操作。

这里已经略微涉及到人机博弈的概念了(这个我还没有研究过),我想基础的东西肯定是大同小异的,只是复杂度提高了。大名鼎鼎的“深蓝”挑战国际象棋冠军,它里面存有了200万张不同的棋局,配有200多个独立的计算器(应该也是ALU吧),不管对手怎么动,它都能在它的库存中找到对应的最佳解法。

 

下面我们开始介绍真正的Uninformed Search方法吧。

 

1.  Depth-First Search(DFS)深度优先

 

经典算法啊,想必大家都懂的,这里我只想提一下它的非递归实现方法。

还是要使用一个Stack

(1)把根节点入栈

(2)Pop一下,访问该节点,然后把它的右子女和左子女(不为NULL)依次Push进去

(3)重复(2),直到找到目标节点或栈为空(没有找到)。

为了避免环的出现而使算法陷入死循环,可以引入一个visited数组。

显然,DFS算法虽然一定能找到出现在图中的节点,但效率很低。若要找F,DFS就相当于把整个图都遍历了一遍。

 

2.  Depth-Limited Search(DLS)

DLS其实是DFS的变体,它限制住了访问的深度,在DFS算法的第(2)步中,若该节点当前深度大于限制深度,那就不再把它的子女Push进去了。

为了实现此算法,又多开辟了一个深度Stack,它与节点Stack同步进行操作,Push节点的时候也把当前的深度Push进去,Pop同理。

显然,DLS未必能找到目标节点。

 

3.  Iterative Deepening Search(IDS)

IDS其实又是DLS的变体了,它通过depth的增量(初始值和增量大小可视情况而定),迭代进行DLS算法,直到找到目标节点。

 

显然当目标节点很深的情况下,IDS算法的效率肯定不如直接DFS的。但当深度很大,而且标节点处于中间位置或较浅位置时,或者深度未知的情况下,就能体现出IDS算法的价值了。

 

4.  Breadth-First Search(BFS)广度优先

也是经典啊,通过队列实现,不多说了。只说下,BFS也可以像DFS一样引入一个visited数组,不是为了避免死循环,而是可以避免有环的情况下重复访问相同的节点,以加速搜索。

 

5.  Uniform-Cost Search(UCS)

这个算法适用与带权图中,为了找出最短路径。

如图,找出从A到E的最短路径。

 

我们需要一个优先队列(也就是堆)来实现,里面存放节点和当前到达该节点总共的Cost,按Cost来把堆调整为最小堆。

(1)初始节点插入空堆

(2)出堆,访问该节点,把它所有的子女(不为NULL)依次入堆(Cost=当前节点的Cost + 与子女边的权)

(3)重复(2),直到出堆的节点即为目标节点,或堆为空了(没有找到)。

注意,因为是堆,所以每次入堆出堆操作堆内都会进行调整。

 

这里我们再来讨论一种backtracking技术,因为问题是通过此算法,我们只能找到最短路径的长度是多少,而不知道这条路径经过了哪些节点。其实最简单的方法就是只要让每个节点记住它的parent就可以了。

我们需要的数据结构如下

struct Node

{

int node_id;

int current_total_cost;  // current_total_cost = 父节点current_total_cost + 边上的权

Node* parent;

}

每次取当前节点的子女时,将parent指针指向当前节点就OK了。只要将赋值好的Node插入堆中就行了。

当算法结束时,取出的最后一个节点为目标节点,然后通过它的parent指针以及parent的parent指针(迭代)一路可以找到初始节点(初始节点的parent当然为NULL),然后按正向输入节点id就OK了。

 

6.  Bidirectional Search 双向搜索

顾名思义,就是从两端同时搜索。但它有个前提条件,必须知道目标节点在哪(有指针指向它)。

 

它的基本思想是两端都采用BFS搜索,直到两条路径碰头。但现实中,若都采用BFS搜索,往往是不会碰头的,即使碰头的话,效率也非常低。

现实中的图往往复杂到这样

 

所以一般两端都采用一种Informed Search叫A* Search(我们将在下篇中介绍)

 

Summary

Algorithm

Time Complexity

Space Complexity

Derivative

DFS

O(bm)

O(bm)

 

DLS

O(bl)

O(bl)

DFS

IDS

O(bd)

O(bd)

DLS

BFS

O(bd)

O(bd)

 

UCS

O(bd)

O(bd)

BFS

BIDIBidirectional 

O(bd/2)

O(bd/2)

BFS(两端采用BFS)

b, branching factor

d, tree depth of the solution

m, tree depth

l, search depth limit

 

综上这些算法,除了UCS(通过优先队列来选择当前Cost最小的节点优先访问)带有一点策略性,(BIDI当两端采用A* Search时也具有策略性),其他算法都是不具备决策能力的盲目式搜索,当数据量很大时,显然是低效的。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章