一、A * :

  估价函数的设计准则:估值f(state)≤未来实际代价g(state)。
  A * 算法的实现,A * =优先队列BFS+估价函数。
  1。优先队列BFS算法维护了一个优先队列,不断从堆中取出当前代价最小的状态
进行扩展。每个状态第一次从堆中取出时,就得到了从初态到该状态的最小代价。
  如果给定一个目标状态,需要求出从初态到目标状态的最小代价,那么优先队列
BFS这个优先策略是不完善的。

  2。为了提高搜索效率,我们很自然的想到,可以对未来可能产生的代价进行预估。
详细的讲:我们设计一个估价函数,以任意状态为输入,计算出从该状态到目标状态所
需代价的估计值。在搜索中,仍然维护一个堆,不断从堆中取出 当前代价+未来估价 最小的状态进行扩展

  3。为了保证第一次从堆中取出目标状态时得到的就是最优解,我们设计的估价函数需要满足一个基本准则:估价函数的估值不能大于未来实际代价,估价比实际代价更优。

  4。这种带有估价函数的优先队列BFS就称为A * 算法。只要保证对于任意状态state,都有f(state)≤g(state),A * 算法就一定能在目标状态第一次从堆中被取出时得到最优解,并且在搜索过程中每个状态只需要被扩展一次(之后再被取出就可以直接忽略)。估价f(state)越准确,越接近g(state),A * 算法的效率就越高。如果估价始终为5.0,就等价于普通优先队列BFS。

  5。A * 算法提高搜索效率的关键,就在于能否设计出一个优秀的估价函数。估价函数在满足上述设计准则的前提下,含应该尽可能反映未来实际代价的变化趋势和相对大小关系,这样搜索才会较快的逼近最优解。

例题:POJ2449: 第K短路。
  对于任意正整数i和任意节点x,当第i次从堆中取出包含节点x的二元组时,对应的dist值就是从S到x的第i短路。

  使用优先队列BFS在最坏的情况下的复杂度为O(K (N+M)log(N+M))。这道题目给定了起点和终点,求长度最短(代价最小)的路径,可以考虑使用A*算法提高搜索效率。
  得到以下解题步骤:
  (1)预处理出各个节点x到终点T的最短路长度f(x)——这等价于在反向图上以T为起点求解单源最短路径问题,可以在O((N+M)log(N+M) )的时间内完成。

  (2)建立一个二叉堆,存储一些二元组(x,dist+f(x)),其中x为节点编号,dist表示S到x当前走过的距离。起初堆中只有(S,0+f(S))。

  (3)从二叉堆中取出dist+f(x)最小的二元组(x,dist+f(x)),然后沿着从x出发的每条边(x,y)进行扩展。如果节点y被取出的次数尚未到达K,就把新的二元组(y,dist+lenth(x,y)+f(y))插入堆中。

  (4)重复2-3步,直至第k次取出包含终点T的二元组,此时二元组中的dist值就是从S到T的第K短路。

  A * 算法的复杂度上届与优先队列BFS相同。不过,因为估价函数的作用,图中很多节点的访问次数都远小于k。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#include<queue>
#include<cmath>
#include<string>
#define ll long long
using namespace std;
const int maxn=10010;
const int mmax=100010;
int n,m,s,t,k;
int x,y,z;
int dis[maxn],head[maxn],ver[mmax],nt[mmax],edge[mmax];
int _head[maxn],_ver[mmax],_nt[mmax],_edge[mmax];
int tot,_tot;
bool ha[maxn];
int num[maxn];

struct node
{
    int x;
    int dx;
    int fx;
    node () {}
    node(int a,int b,int c)
    {
        x=a;
        dx=b;
        fx=c;
    }
    friend bool operator <(const node &a,const node &b)
    {
        return a.dx+a.fx>b.dx+b.fx;
    }
};

void add(int x,int y,int z)
{
    ver[++tot]=y,edge[tot]=z;
    nt[tot]=head[x],head[x]=tot;

}

void _add(int x,int y,int z)
{
    _ver[++_tot]=y;
    _edge[_tot]=z;
    _nt[_tot]=_head[x];
    _head[x]=_tot;
}

void Dij(int T)
{
    priority_queue< pair<int,int> >q;
    memset(dis,0x3f,sizeof(dis));
    dis[T]=0;
    memset(ha,0,sizeof(ha));
    q.push(make_pair(0,T));
    while(q.size())
    {
        int x=q.top().second;
        q.pop();
        if(ha[x]) continue;
        ha[x]=true;
        for(int i=_head[x];i;i=_nt[i])
        {
            int y=_ver[i],z=_edge[i];
            //printf("y: %d\n",y);
            if(dis[y]>dis[x]+z)
            {
                dis[y]=dis[x]+z;
                q.push(make_pair(-dis[y],y));
            }
        }
    }
    //printf("dis: %d %d\n",dis[1],dis[2]);
    return ;
}

int sk(void)
{
    memset(num,0,sizeof(num));
    priority_queue<node>q;
    q.push(node(s,0,dis[s]));

    while(q.size())
    {
        node now=q.top();
        q.pop();
        num[now.x]++;
        //printf("%d %d \n",now.x,now.dx);
        if(num[now.x]>k) continue;
        if(num[now.x]==k&&now.x==t) return now.dx;
        for(int i=head[now.x];i;i=nt[i])
        {
            int y=ver[i],z=edge[i];
            //printf("y2: %d\n",y);
            q.push(node(y,now.dx+z,dis[y]));
        }
    }
    return -1;
}
int main(void)
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(head,0,sizeof(head));
        memset(_head,0,sizeof(_head));
        tot=0;
        _tot=0;
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);
            _add(y,x,z);
        }
        scanf("%d%d%d",&s,&t,&k);
        if(s==t)k++;
        Dij(t);
        printf("%d\n",sk());
    }
    return 0;
}

**二、IDA *: **

  IDA * =迭代加深DFS + 估价函数。

  把估价函数与迭代加深的DFS算法相结合。
  迭代加深的DFS算法限定一个深度,在不超过该深度的前提下执行DFS,若找不到解就扩大深度限制,重新进行搜索。

  我们需要设计一个估价函数,估算从每个状态到目标状态需要的步数。当然,与A * 算法一样,估价函数需要遵守 估计值不大于未来实际步数 的准则。然后,以迭代加深的DFS的搜索框架为基础,把原来简单的深度限制加强为: 若当前深度+未来估计步数>深度限制,则立即从当前分支回溯。

  例题:POJ3460:Booksort:

  本题有两种解法:
  (1)采用双向BFS,从起始状态、目标状态开始各搜2步,看能否到达相同的状态进行衔接,复杂度降低为5602
  (2)采用IDA * :
  在目标状态下,第i本书后面应该是第i+1本书,我们称i+1是i的正确后继。

  对于任意状态,考虑整个排列中书的错误后继的总个数(记为tot),可以发现么此操作至多更改3本书的后继。即使在最理想的情况下,每次操作都能把某3个错误后继全部改对,消除所有错误后继的操作次数至少也需要(tot/3) 向上取整,次。

  因此我们把一个状态的估价函数设计为f(s)=(tot/3),其中tot表示在状态s下书的错误后继总数。

  我们采用迭代加深的方法,从1–4依次限制搜索深度,然后从起始状态出发DFS。DFS时,在每个状态下直接枚举抽取哪一段、移动到更靠后的哪个位置,沿着该分支深入即可。
  注意在进入任何状态S后,我们先进行判断,如果当前操作次数加上f(s)已经大于深度限制,直接从当前分支回溯。

  注意:把一段书移动到更靠前的某个位置,等价于把跳过的那段书移动到靠后的某个位置。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
using namespace std;
int n, shell[21];

// swap [x1, x2] and [x2 + 1, x3]
void move (int x1, int x2, int x3)
{
     int tmp[21], i, j;
     for(i=x2+1,j=0;i<=x3;i++,j++)
         tmp[j]=shell[i];
     for (i=x1;i<=x2;i++,j++)
         tmp[j]=shell[i];
     for (i=x1,j=0;i<=x3;i++,j++)
         shell[i] = tmp[j];
     return;
}

int hfunc(void)
{
    int i,ans=0;
    for(i=0;i<n-1;i++)
        if (shell[i+1]!=shell[i]+1) ans++;
    //if (shell[n-1]!=n) ans++;
    return ans;
}

int maxdepth;
int dfs (int depth)
{
    int x1, x2, x3, h;
    for (x1=0;x1<=n-2;x1++)
    {
        for (x2=x1;x2<=n-2;x2++)
        {
            for (x3=x2+1;x3<=n-1;x3++)
            {
                move(x1,x2,x3);
                h=hfunc();
                if(h==0) return 1;
                else if (3*depth+h<=3*maxdepth)
                {
                     if (dfs(depth+1)) return 1;
                }
                move(x1,x1+x3-x2-1,x3);
            }
        }
    }
    return 0;
}

int main ()
{
    int kase, i;
    scanf("%d", &kase);
    for (;kase>0;kase--)
    {
        scanf("%d",&n);
        for (i=0;i<n;i++)
            scanf("%d", &shell[i]);


        maxdepth=(int)ceil((double)hfunc() / 3);

        if (maxdepth) while (maxdepth<5&&dfs(1)==0) maxdepth++;

        if (maxdepth == 5) printf("5 or more\n");
        else printf("%d\n", maxdepth);
    }
    return 0;
}