A\* 算法是 [BFS](/search/bfs) 的一种改进。

定义起点 $s$，终点 $t$。

从起点（初始状态）开始的距离函数 $g(x)$。

到终点（最终状态）的距离函数 $h(x), h*(x)$。

定义每个点的估价函数 $f(x)=g(x)+h(x)$。

A\* 算法每次从 **优先队列** 中取出一个 $f$ 最小的，然后更新相邻的状态。

如果 $h\leq h*$，则 A\* 算法能找到最优解。

上述条件下，如果 $h$ **满足三角形不等式，则 A\* 算法不会将重复结点加入队列**。

其实…… $h=0$ 时就是 [DFS](/search/dfs) 算法，$h=0$ 并且边权为 $1$ 时就是 [BFS](/search/BFS)。

## 例题 [八数码](https://www.luogu.org/problemnew/show/P1379)

题目大意：在 $3\times 3$ 的棋盘上，摆有八个棋子，每个棋子上标有 1 至 8 的某一数字。棋盘中留有一个空格，空格用 0 来表示。空格周围的棋子可以移到空格中，这样原来的位置就会变成空格。给出一种初始布局和目标布局（为了使题目简单, 设目标状态为

    123
    804
    765

），找到一种从初始布局到目标布局最少步骤的移动方法。

$h$ 函数可以定义为，不在应该在的位置的数字个数。

容易发现 $h$ 满足以上两个性质，此题可以使用 A\* 算法求解。

代码实现：

```cpp
#include<cstdio>
#include<algorithm>
using namespace std;
int fx[9]={0,1,1,1,2,3,3,3,2},fy[9]={0,1,2,3,3,3,2,1,1};
int xx[4]={1,-1,0,0},yy[4]={0,0,-1,1},a[4][4]={},n,m,x,y,z,X,Y,ans;
int h()
{
    int ans=0;
    for(int i=1;i<=3;i++)for(int j=1;j<=3;j++)
	if(a[i][j])ans+=abs(i-fx[a[i][j]])+abs(j-fy[a[i][j]]);
    return ans;
}
void dfs(int tf,int X,int Y,int g)
{
    int H=h();
    if(!H){ans=g;return;}
    if(g==tf||ans||H+g>tf)return;
    for(int i=0;i<4;i++)
	{
        int x=X+xx[i],y=Y+yy[i];
        if(x&&y&&x<=3&&y<=3)
		{
            swap(a[X][Y],a[x][y]);
            dfs(tf,x,y,g+1);
            swap(a[X][Y],a[x][y]);
        }
    }
}
int main()
{
    for(int i=1;i<=3;i++)for(int j=1;j<=3;j++)
	{
        char c;scanf(" %c",&c);a[i][j]=c-'0';
        if(!a[i][j])X=i,Y=j;
    }
    for(int i=0;;i++)
	{
        dfs(i,X,Y,0);  
        if(ans){printf("%d\n",ans);return 0;};
    }
}
```

## 例题 [k 短路](https://www.luogu.org/problemnew/show/P2483)

题目大意：按顺序求一个有向图上从结点 $s$ 到结点 $t$ 的所有路径最小的前任意多（不妨设为 $k$ ）个。

很容易发现，这个问题很容易转化成用 A\* 算法解决问题的标准程式。

初始状态为处于结点 $s$，最终状态为处于结点 $t$，距离函数为从 $s$ 到当前结点已经走过的距离，估价函数为从当前结点到结点 $t$ 至少要走过的距离，也就是当前结点到结点 $t$ 的最短路。

就这样，我们在预处理的时候反向建图，计算出结点 $t$ 到所有点的最短路，然后将初始状态塞入优先队列，每次取出 $f(x)=g(x)+h(x)$ 最小的一项，计算出其所连结点的信息并将其也塞入队列。当你第 $k$ 次走到结点 $t$ 时，也就算出了结点 $s$ 到结点 $t$ 的 $k$ 短路。

由于设计的距离函数和估价函数，每个状态需要存储两个参数，当前结点 $x$ 和已经走过的距离 $v$。

我们可以在此基础上加一点小优化：由于只需要求出第 $k$ 短路，所以当我们第 $k+1$ 次或以上走到该结点时，直接跳过该状态。因为前面的 $k$ 次走到这个点的时候肯定能因此构造出 $k$ 条路径，所以之后在加边更无必要。

代码实现：

```cpp
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=5010;
const int maxm=400010;
const double inf=2e9;
int n,m,k,u,v,cur,h[maxn],nxt[maxm],p[maxm],cnt[maxn],ans;
int cur1,h1[maxn],nxt1[maxm],p1[maxm];
double e,ww,w[maxm],f[maxn];
double w1[maxm];
bool tf[maxn];
void add_edge(int x,int y,double z)
{
    cur++;
    nxt[cur]=h[x];
    h[x]=cur;
    p[cur]=y;
    w[cur]=z;
}
void add_edge1(int x,int y,double z)
{
    cur1++;
    nxt1[cur1]=h1[x];
    h1[x]=cur1;
    p1[cur1]=y;
    w1[cur1]=z; 
} 
struct node
{
    int x;
    double v;
    bool operator<(node a)const{return v+f[x]>a.v+f[a.x];}
};
priority_queue<node>q;
struct node2
{
    int x;
    double v;
    bool operator<(node2 a)const{return v>a.v;}
}x;
priority_queue<node2>Q;
int main()
{
    scanf("%d%d%lf",&n,&m,&e);
    while(m--)
    {
        scanf("%d%d%lf",&u,&v,&ww);
        add_edge(u,v,ww);
        add_edge1(v,u,ww);
    }
    for(int i=1;i<n;i++)f[i]=inf;
    Q.push({n,0});
    while(!Q.empty())
    {
        x=Q.top();Q.pop();
        if(tf[x.x])continue;
        tf[x.x]=true;f[x.x]=x.v;
        for(int j=h1[x.x];j;j=nxt1[j])Q.push({p1[j],x.v+w1[j]});
    }
    k=(int)e/f[1];
    q.push({1,0});
    while(!q.empty())
    {
        node x=q.top();q.pop();
        cnt[x.x]++;
        if(x.x==n)
        {
            e-=x.v;
            if(e<0){printf("%d\n",ans);return 0;}
            ans++;
        }
        for(int j=h[x.x];j;j=nxt[j])if(cnt[p[j]]<=k&&x.v+w[j]<=e)q.push({p[j],x.v+w[j]});
    } 
    printf("%d\n",ans);
    return 0;
}
```
