A*搜索算法

A*算法 = 贪心 + BFS + 优先队列

A*算法是根据评估函数f(x)=g(x)+f(x)来判断每一步的搜索的,g(x)是实际代价,h(x)是评估代价。因为g(x)是已经经历了的路程,h(x)是对接下来的路程评估,故A*算法的实用价值取决于h(x)函数。

函数h的常见设计

在二维平面上,一般考虑三种方式:
(1)曼哈顿距离:h(i)=abs(i.x-t.x)+abs(i.y-t.y) ,适用于四个方向走(上、下、左、右)
(2)对角线距离:h(i)=max{abs(i.x-t.x),abs(i.y-t.y)},适用于八个方向走,例如马走日
(3)欧几里得距离:h(i)=fabs((i.x-t.x)^2+(i.y-t.y)^2),适用于可以随意走

设计h函数需要注意的事项:

  • g和h应该是同样计算方法,否则f = g + h则会变得很模糊
  • h(i)函数必须等于或优于最佳路径,否则可能走向更坏的路径

利用入门的BFS题作为练习:https://vjudge.net/problem/POJ-2243
h(i)=max{abs(i.x-t.x),abs(i.y-t.y)}

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
//f=g+h g为实际距离,h为估计距离
//设计h函数为row+col
const int dir[8][2] = { {-2,1},{-2,-1},{2,1},{2,-1},{-1,2},{-1,-2},{1,2},{1,-2} };
int sx, sy, tx, ty, vis[9][9], tag;
inline int h(int x, int y) { return max(abs(x - tx), abs(y - ty)); }
struct node {
	int x, y, cnt;
	node(int x, int y, int cnt = 0) :x(x), y(y), cnt(cnt) {}
	bool operator < (const node& rhs)const {
		return h(x, y) > h(rhs.x, rhs.y);
	}
};
int main(void) {
	char s[5];
	while (~scanf("%s",s)){
		tag++;
		sy = s[0] - 'a';
		sx = s[1] - '1';
		scanf("%s", s);
		ty = s[0] - 'a';
		tx = s[1] - '1';
		queue<node>q;
		q.push(node(sx, sy));
		vis[sx][sy] = tag;
		int ans = 0;
		while (!q.empty()){
			node u = q.front(); q.pop();
			if (u.x == tx && u.y == ty) {
				ans = u.cnt; break;
			}
			for (int i = 0; i < 8; i++) {
				int row = u.x + dir[i][0];
				int col = u.y + dir[i][1];
				if (row < 0 || row>7 || col < 0 || col>7 || vis[row][col] == tag)continue;
				vis[row][col] = tag;
				q.push(node(row, col, u.cnt + 1));
			}
		}
		printf("To get from %c%c to %c%c takes %d knight moves.\n", sy + 'a', sx + '1', ty + 'a', tx + '1', ans);
	}
	return 0;
}

两个重要的例题:

Remmarguts’ Date POJ - 2449
题意:给出N个点M条有向边的图,求从s点到t点的第k长路的距离(可成环)

如果用简单的BFS+优先队列,则节点t第k次出队就表示从s到t的第k长的路径。

分析A*算法在此题的使用:

如果思考估计函数h(x),很自然地会想到要就近地到t点,那么可以考虑h(x)就表示x与t的最短距离。显然这样设计的h函数符合设计要求。因此先反向建图求出h(x)。

g(x)是实际代价,仍然是点s到x点的实际距离。f(x)=g(x)+h(x)为估价函数,每次找的下一个点是 已有实际距离+后续理想距离的最小点。

注意本题的一个小坑:如果起始点s和终止点t为同一点,因为这样s和t之间没有边,但是在操作的时候会多数一条,因此要k=k+1。

#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
const int maxn = 1000 + 5;
const int inf = 0x3f3f3f3f;
struct edge {
	int to, w;
	edge(int to, int w) :to(to), w(w) {}
};
struct node {
	int id, dis;
	node(int id, int dis) :id(id), dis(dis) {}
	bool operator <(const node& rhs)const {
		return dis > rhs.dis;
	}
};
int N, M, s, t, k, h[maxn], vis[maxn], cnt[maxn];
struct node2 {
	int id, g;
	node2(int id, int dis) :id(id), g(dis) {}
	bool operator <(const node2& rhs) const {
		return g + h[id] > rhs.g + h[rhs.id];
	}
};
vector<edge>e[maxn], re[maxn];
int main(void) {
	scanf("%d %d", &N, &M);
	for (int i = 1; i <= M; i++) {
		int u, v, w;
		scanf("%d %d %d", &u, &v, &w);
		e[u].push_back(edge(v, w));
		re[v].push_back(edge(u, w));
	}
	memset(h, inf, sizeof(h));
	scanf("%d %d %d", &s, &t, &k);
	if (s == t)k++;
	h[t] = 0;
	priority_queue<node>pq;
	pq.push(node(t, 0));
	while (!pq.empty()) {
		node u = pq.top(); pq.pop();
		if (vis[u.id])continue;
		vis[u.id] = 1;
		for (int i = 0; i < re[u.id].size(); i++) {
			int to = re[u.id][i].to, w = re[u.id][i].w;
			if (vis[to])continue;
			if (h[to] > h[u.id] + w) {
				h[to] = h[u.id] + w;
				pq.push(node(to, h[to]));
			}
		}
	}
	int ans = -1;
	priority_queue<node2>pq2;
	pq2.push(node2(s, 0));
	while (!pq2.empty()) {
		node2 u = pq2.top(); pq2.pop();
		cnt[u.id]++;
		if (cnt[t] == k) { ans = u.g; break; }
		for (int i = 0; i < e[u.id].size(); i++) {
			int v = e[u.id][i].to, w = e[u.id][i].w;
			pq2.push(node2(v, u.g + w));
		}
	}
	printf("%d\n", ans);
	return 0;
}

Power Hungry Cows POJ - 1945
题意:给一个数p,你有两个初始值a=1,b=0,可以随意进行操作a*2,b*2,a+b,abs(a-b),每一步选这4个操作的一个替换掉原来的a或b,求最少步数得出p。

显然一直对a,b中较大的数进行a*2,可以最快地接近p,因此可以把这个作为h(x)函数。
直接用A*算法还是会TLE,需要在使用优先队列的时候加一些剪枝条件。

显然可以去掉的情况有:
1.a>p且b=0;2.p%gcd(a,b)!=0;3.x==y,这种情况必然不能构成最优解。
此外还可以对求的范围加以限制,经过翻阅一些题解发现p+100即可为上限。

这个题卡时间又卡空间,如果求的数不够多可能答案会偏大。

对于判重,用map判重花费的时间比较多,用bool vis[][]数组判重空间又不够,必须使用hash来判重。出现未出现的a,b或者同样的a,b但步数更少时,要更新哈希表,大概是因为搜索的范围不够大,不能直接BFS到最小值。

#include<cstdio>
#include<algorithm>
#include<queue>
#include<cmath>
#include<map>
using namespace std;
const int maxl = 20105;
const int inf = 0x3f3f3f3f;
int p, maxp, dp[maxl];
int gcd(int x, int y) {
	return y ? gcd(y, x % y) : x;
}
struct node {
	int a, b, cnt;
	node(int a = 1, int b = 0, int cnt = 0) :a(a), b(b), cnt(cnt) {}
	bool operator < (const node& rhs) const {
		return cnt + dp[a] > rhs.cnt + dp[rhs.a];
	}
};
const int maxn = 1000005;
const int mod = 999983;
struct Hash {
	int x, y, d, next;
} h[maxn];
int n, head[maxn], tot;
inline int insert(int x, int y, int d) {
	int k = (1ll * x * y + x + y) % mod;
	for (int p = head[k]; p; p = h[p].next)
		if (h[p].x == x && h[p].y == y) {
			if (h[p].d > d) {
				h[p].d = d;
				return 1;
			}
			else return 0;
		}
	h[++tot].x = x, h[tot].y = y, h[tot].d = d;
	h[tot].next = head[k], head[k] = tot;
	return 1;
}
priority_queue<node>pq;
void push(int x, int y, int newCnt) {
	if (x < y)swap(x, y);
	if (x > maxp)return;//范围太大
	if (x > p && !y)return;//无解
	if (x == y)return;//显然非最优
	if (p % gcd(x, y))return;//无解
	if (!insert(x, y, newCnt))return;
	pq.push(node(x, y, newCnt));
}
int main(void) {
	dp[0] = inf;
	scanf("%d", &p);
	maxp = p + 100;// +100;
	for (int i = 1; i < p; i++) {
		int x = i, pos = 0;
		while (x < p) pos++, x <<= 1;
		dp[i] = pos;
	}
	push(1, 0, 0);
	int ans = 0;
	while (!pq.empty()) {
		node u = pq.top(); pq.pop();
		if (u.a == p || u.b == p) {
			ans = u.cnt; break;
		}
		int newCnt = u.cnt + 1;
		int a2 = u.a * 2, b2 = u.b * 2;
		int ab = u.a + u.b, a_b = abs(u.a - u.b);
		push(a2, u.b, newCnt);
		push(a2, u.a, newCnt);
		push(b2, u.a, newCnt);
		push(b2, u.b, newCnt);
		push(ab, u.b, newCnt);
		push(ab, u.a, newCnt);
		push(a_b, u.b, newCnt);
		push(a_b, u.a, newCnt);
	}
	printf("%d\n", ans);
	return 0;
}

参考博文:A*搜索 --算法竞赛专题解析(9)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JILIN.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值