A*启发式搜索 其实是两种搜索方法的合成( A*搜索算法 + 启发式搜索),但要真正理解A*搜索算法,还是得先从启发式搜索算法谈起。

何为启发式搜索

启发式搜索算法有点像广度优先搜索,不同的是,它会优先顺着有启发性和具有特定信息的节点搜索下去,这些节点可能是到达目标的最好路径。我们称这个过程为最优(best-first)或启发式搜索。

简单来说,启发式搜索就是对取和不取都做分析,从中选取更优解(或删去无效解)

由于概念过于抽象,我们使用例题讲解。

例题【NOIP2005 普及组】 采药

题目大意:有$ N $ 种物品和一个容量为 \(W\)的背包,每种物品有重量\(wi\)和价值\(ui\) 两种属性,要求选若干个物品(每种物品只能选一次)放入背包使背包中物品的总价值最大且背包中物品的总重量不超过背包的容量。

很明显这是一个01背包问题,很容易让我们想到使用动态规划和贪心算法。

但在启发式算法的思路是:

我们写一个估价函数 f,可以剪掉所有无效的 0 枝条(就是剪去大量无用不选枝条)。

估价函数 f 的运行过程如下:

我们在取的时候判断一下是不是超过了规定体积(可行性剪枝)。

在不取的时候判断一下不取这个时,剩下的药所有的价值 + 现有的价值是否大于目前找到的最优解(最优性剪枝)。

例题代码

#include <algorithm>
#include <cctype>//isdigit
#include <cstdio>
int n, tot, ans;
struct node
{
    int c, v;//体积与价值
    double cost;
};
using namespace std;
const int N = 10000;
node a[N + 5];
int read()/*快读大法吼啊!比cin scanf都快*/
{
    int x = 0; short w = 0; char ch = 0;
    while (!isdigit(ch)) { w |= ch == '-'; ch = getchar(); }
    while (isdigit(ch)) { x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar(); }
    return w ? -x : x;
}
//所有的启发式搜索都会有一个估价函数。下面是这一题的估价函数。
inline int f(int t, int v) {
    int tot = 0;
    for (int i = 1; t + i <= n; i++)
    {
        if (v >= a[t + i].c)
        {
            v -= a[t + i].c;
            tot += a[t + i].v;
        }
        else
            return (int)(tot + v * a[t + i].cost);
    }
    return tot;
}
bool operator<(const node &a, const node &b){
    return a.cost > b.cost;
} /*等价于
bool cmp(node a,node b){
    return a.cost>b.cost;
}
*/
void DFS(int now, int cv, int cp){
    ans = max(ans, cp);
    if (now > n)
    {
        return;
    }
    if (f(now, cv) + cp > ans)
        DFS(now + 1, cv, cp);
    if (cv - a[now].c >= 0)
        DFS(now + 1, cv - a[now].c, cp + a[now].v);
}
int main()
{
    tot = read(), n = read();
    for (int i = 1; i <= n; i++)
    {
        a[i].c = read(), a[i].v = read();
        a[i].cost = 1.0 * a[i].v / a[i].c;
    }
    sort(a + 1, a + 1 + n);//由于我们重载了<,所以就不需要cmp函数
    DFS(0, tot, 0);
    printf("%d", ans);
    return 0;
}

A*搜寻算法

​ A*搜寻算法,俗称A星算法,作为启发式搜索算法中的一种,这是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。常用于游戏中的NPC的移动计算,或线上游戏的BOT的移动计算上。该算法像Dijkstra算法一样,可以找到一条最短路径;也像BFS一样,进行启发式的搜索。

A*算法最为核心的部分,就在于它的一个估值函数的设计上:

\[f(n)=g(n)+h(n)
\]

定义起点\(s\) ,终点 \(t\)

从起点(初始状态)开始的距离函数 \(g(x)\)

到终点(最终状态)的距离函数 \(h(x),h*(x)\)

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

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

如果 \(h <= h*\),则 A*算法能找到最优解。

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

其实……$h = 0 $ 时就是 DFS 算法, 并且边权为 时就是 BFS

例题 八数码

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

123
804
765

)

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

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

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

代码实现:

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
#include <set>
using namespace std;
const int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
int fx, fy;
char ch;
struct matrix {
  int a[5][5];
  bool operator<(matrix x) const {
    for (int i = 1; i <= 3; i++)
      for (int j = 1; j <= 3; j++)
        if (a[i][j] != x.a[i][j]) return a[i][j] < x.a[i][j];
    return false;
  }
} f, st;
int h(matrix a) {
  int ret = 0;
  for (int i = 1; i <= 3; i++)
    for (int j = 1; j <= 3; j++)
      if (a.a[i][j] != st.a[i][j]) ret++;
  return ret;
}
struct node {
  matrix a;
  int t;
  bool operator<(node x) const { return t + h(a) > x.t + h(x.a); }
} x;
priority_queue<node> q;
set<matrix> s;
int main() {
  st.a[1][1] = 1;
  st.a[1][2] = 2;
  st.a[1][3] = 3;
  st.a[2][1] = 8;
  st.a[2][2] = 0;
  st.a[2][3] = 4;
  st.a[3][1] = 7;
  st.a[3][2] = 6;
  st.a[3][3] = 5;
  for (int i = 1; i <= 3; i++)
    for (int j = 1; j <= 3; j++) {
      scanf(" %c", &ch);
      f.a[i][j] = ch - '0';
    }
  q.push({f, 0});
  while (!q.empty()) {
    x = q.top();
    q.pop();
    if (!h(x.a)) {
      printf("%d\n", x.t);
      return 0;
    }
    for (int i = 1; i <= 3; i++)
      for (int j = 1; j <= 3; j++)
        if (!x.a.a[i][j]) fx = i, fy = j;
    for (int i = 0; i < 4; i++) {
      int xx = fx + dx[i], yy = fy + dy[i];
      if (1 <= xx && xx <= 3 && 1 <= yy && yy <= 3) {
        swap(x.a.a[fx][fy], x.a.a[xx][yy]);
        if (!s.count(x.a)) s.insert(x.a), q.push({x.a, x.t + 1});
        swap(x.a.a[fx][fy], x.a.a[xx][yy]);
      }
    }
  }
  return 0;
}

另外例题 K短路 同样可以用A*算法可以思考下如何AC

参考文献

版权声明:本文为RioTian原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/RioTian/p/12870751.html