2011年2月28日 03:18

启发式搜索算法学习之A* & ID_A*

Algorithm Study

文中提及的一些观点和理论,并非全部个人原创,而是自己的总结和心得, 仅供个人学习编程使用! 

启发式搜索算法从听说有这个名字开始到正真学习她,应该间隔了一年多的时间吧!当我接触他之后,就有种相见恨晚的感觉!因为她完美的剪枝思想,不仅弥补了通常盲目搜索算法的不足,提高了程序的运行效率,而且给了我一种处理搜索问题的“优化思想”!

下面就让我简单的说一下个人对两种启发式搜索算法,A*和ID_A*算法的理解。开始之前,先推荐比较好的几份资料。

  • 《A*算法详解》  Sunway;个人认为他的这篇介绍性的文章写的非常到位,由浅入深的带领我们进入A*算法的世界。强烈建议,学习启发搜索先看这篇文章。(推荐yes
  • 《A*算法》(ppt) 尚福华; 从理论层面上分析了A*算法的正确性。很值得我们在学了A*算法一段时间之后,回过来分析和思考的。(推荐♦
  • 《算法艺术与信息学竞赛》(刘如佳,黄亮 著)P168。介绍的比较笼统,适合有一定基础的人来体会。

先给出一个A*算法的模型Sunway。

Best_First_Search() {
    Open = [起始节点S0];  Closed = [NULL];
    while (Open表非空) {
        从Open中取得一个节点X,并从OPEN表中删除。
        if (X是目标节点) {
            求得一组最优路径PATH;返回路径PATH;
        }
        for (每一个X的子节点Y) {
            if (Y不在OPEN表和CLOSE表中) {
                求Y的估价值;并将Y插入OPEN表中;//还没有排序  
            }
            else
                if (Y在OPEN表中) {
                     if (Y的估价值小于OPEN表的估价值)
                         更新OPEN表中的估价值;
                }
                else {//Y在CLOSE表中  
                    if (Y的估价值小于CLOSE表的估价值) {
                        更新CLOSE表中的估价值;
                        从CLOSE表中移出节点,并放入OPEN表中;
                    }
                }
        }//end for  
        将X节点插入CLOSE表中;
        按照估价值f()将OPEN表中的节点排序;        
    }//end while  
}//end func  

谈到A* 算法,就要涉及到三个函数:估价函数f()  =  实际代价函数g()  +   启发函数 h();

对于状态空间中的某个节点n定义:f*(n)为初始节点src, 约束警告过节点n到达目标节点的最小代价值(f(n)是对f(n)的一个估价,因此成为估价函数)。则f*(n)应包含两部分:一是:从目标节点到达节点N时的最小代价,记为g*() (g() 是g*()的一个上界);另外一部分是从节点n到达目标节点的最小代价,记为h*(), (因为h()使我们事先无法知晓的,所以,我们用h()来估计,因此,称为估价函数h())。所以,g()是对g*()的一个估价,h()是对h*()的一个估价。

  对于实际代价函数g(),我们在搜索到当前节点的时候,有可能最小代价的路径还没有找到,所以 有g() >= g*(), 即 g() 是g*()的一个上界。

 对于估价函数g(), 这里要重点介绍一下了,尚福华的ppt里面说的很清晰,这里就引用一下。h(n)的单调限制: 

在A*算法中,每当扩展一个节点n时,都需要检查其子节点是否已在Open表或Closed表中。对于那些已在Open表中的子节点,需要决定是否调整指向其父节点的指针;对于那些已在Closed表中的子节点,除了需要决定是否调整其指向父节点的指针外,还需要决定是否调整其子节点的后继节点的父指针。这就增加了搜索的代价。如果我们能够保证,每当扩展一个节点时就已经找到了通往这个节点的最佳路径,就没有必要再去检查其后继节点是否已在Closed表中,原因是Closed表中的节点都已经找到了通往该节点的最佳路径。为满足这一要求,我们需要启发函数h(n)增加单调性限制。 
对任意节点ni及其任意子节点nj,     都有 h(ni) <= h(nj) + c(ni, nj); 其中c (ni,,nj)是节点ni到其子节点nj的边代价,则称h(n)满足单调限制。满足此性质的启发函数h(),我们称她是相容(consistent)。
 下面是加了H(n)单调性优化的伪代码(黑皮书P170):
Procedure Expand(State: StateType);
Begin
    for i := 1 to OperandCount(State) do
    Begin
         NewState := DoOperand(State, i);
         NewState.h := CalculateH(NewState);
         NewState.g := State.g + 1;
         NewState.f := NewState.g + NewState.h;
         NewState.f := State;
         NewState.LastOp := OperandCount;
         i := FindInsertTableTwo(NewState);  // 在表二中查找,找不到就插入
         If i = 0 then
             InsertToTableOne(NewState);
         ELse if NewState.f < State[i].f then // 新状态优于原状态就替换。
             ReplaceInTableOne(NewState, i);
    End;
End;

procedure Astar(depth: integer);
begin
    Calculate of InitialState;
    TableOne := [InitialState];            // 表一:待扩展节点集
    TableTwo := [initialState];            // 表二:待扩展和已扩展节点集
    ok := false;
    while not ok and not TableOne Empty do // 没有找到解且待扩展节点非空
    begin
         State := ExtractMinInTableOne;    // 找f值最小的待扩展节点
         If IsAnswer(State) then           // 找到解并输出解
             Begin
                 PrintState(state);
                 ok := true;
             End
        Else Expand(State);                //扩展节点
    End;
    If not Ok then NoAnswer;
End;
             
由此, 一种具有f(n)=g(n)+h(n)策略的启发式算法能成为A*算法的充分条件是:
1)搜索树上存在着从起始点到终了点的最优路径。
¤2)问题域是有限的。
¤3)所有结点的子结点的搜索代价值>0。
¤4)h(n)<=h*(n) (h*(n)为实际问题的代价值)。
当此四个条件都满足时,一个具有f(n)=g(n)+h(n)策略的启发式算法能成为A*算法,并一定能找到最优解。简单的来说就两句话:
1. 启发函数是随着深度的增加,是单调不递减的;
2. 估价函数随着深度的增加,也是单调不递减的;
 
迭代加深+启发函数 = ID_A*
Procedure IDAstar(StartState);
Begin 
     PathLimit := H(startState) -1;  //限定每次搜索的最大深度,初始值为初始状态h();
     Success := false;
     Repeat
          inc(PathLimit);
	  StartState.g = 0;
          Push(OpenStack, StartState);
          Repeat
		CurrentState := Pop(OpenStack);
		If Solution(CurrentState) then  // 判定当前状态是否是目的状态
			Success = True;         // 是的话,玲标记为真。
		Else if PathLimit >= CurrentState.g = H(CurrentState) then
			ForEach Child(CurrentState) do // 计算所有子状态的f()并入列。
				Push(OpenState, Child(CurrentState));
          Untill Success or empty of OpenStack
      Untill Success or ResourceLimitsReached;
End;
 运用迭代加深对深度的限制 和 A*算法的启发函数,结合两个算法的优点,便得到了这个算法。他的优势:
不需要判重(深搜的的特点),省去了hash值设定的烦恼;
不需要排序,省去了堆排序的烦恼,只用到栈,实现简单;
 空间的需求较之A*算法,大大减少。因为每次都是规定的最大深度,因此,这个解是最优的。 
 
后记:将在下一节,结合具体的实例,说明这两种算法的好处。并进行一个简单的对比。

Comments(38)

Tags:

Copyright © 2007

Webdesign, tvorba www stránek

Valid XHTML 1.1