学习笔记 #2:启发式搜索

分为两种:A* 和 IDA*

A* :BFS,极其类似Dijkstra堆优化

IDA* :迭代加深DFS,比A*好写,适用于限制深度的题

需要估价函数 \(h(x)\),作为判断是否先行搜索的基准。

A*

一个好的估价函数的选择会影响程序的最终效率。任意时刻下估价函数的值小于当前结点到目标结点的实际距离,否则可能使算法失效而无法得出“最优路径”。好的估价函数会在不断搜索中逐渐变得“精确”(搜索到后面时 \(h[x]\) 的影响逐渐变大,最终占据主导,若该函数值越接近真实值,计算出的价值也越接近实际),越精确程序效率越高

在bfs的过程中,计算每个点的 \(g[x]+h[x]\) 并加入优先队列中,每次选取价值最高的点扩充路径并记录父节点(便于记录路径)即可

例题:P1379 八数码难题

#include<bits/stdc++.h>
using namespace std;
int x, y;
int goal[4][4] = {
	{-1, -1, -1, -1}, 
	{-1, 1, 2, 3}, 
	{-1, 8, 0, 4}, 
	{-1, 7, 6, 5}
};
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
struct sit {
	int f[4][4];
	bool operator < (const sit &nl) const {
		for(int i = 1; i <= 3; i++) {
			for(int j = 1; j <= 3; j++) {
				if(f[i][j] != nl.f[i][j]) return f[i][j] < nl.f[i][j];
			}
		}
		return 0;
	}
};
int g(sit k) {
	int res = 0;
	for(int i = 1; i <= 3; i++) {
		for(int j = 1; j <= 3; j++) {
			if(k.f[i][j] != goal[i][j]) res++;
		}
	}
	return res;
}
struct node {
	int d;
	sit s;
	bool operator < (const node &nl) const {
		return d + g(s) > nl.d + g(nl.s);
	}
} a;
priority_queue <node> Q;
set <sit> s;
void A_Star() {
	Q.push(a);
	while(!Q.empty()) {
		node tp = Q.top(); Q.pop();
		if(!g(tp.s)) {
			cout << tp.d;
			exit(0);
		}
		for(int i = 1; i <= 3; i++) for(int j = 1; j <= 3; j++) if(tp.s.f[i][j] == 0) x = i, y = j;
		for(int i = 0; i < 4; i++) {
			int xt = x + dx[i], yt = y + dy[i];
			if(xt < 1 or xt > 3 or yt < 1 or yt > 3) continue;
			swap(tp.s.f[x][y], tp.s.f[xt][yt]);
			if(!s.count(tp.s)) {
				s.insert(tp.s);
				Q.push((node){tp.d + 1, tp.s});
			}
			swap(tp.s.f[x][y], tp.s.f[xt][yt]);
		}
	}
}
int main() {
	string str; cin >> str;
	for(int i = 1; i <= 3; i++) for(int j = 1; j <= 3; j++) a.s.f[i][j] = str[(i - 1) * 3 + j - 1] - '0';
	a.d = 0;
	A_Star();
	return 0;
}

IDA*

每次搜索时限制搜索层数,加快效率,加记忆化可以使效率提高,不过会麻烦。

适用于层数较少的搜索。

例题:P2324 [SCOI2005] 骑士精神

#include<bits/stdc++.h>
using namespace std;
int TT;
int x, y, st_x, st_y;
int a[6][6];
int ans, success;
int goal[6][6] = {
	{-1, -1, -1, -1, -1}, 
	{-1, 1, 1, 1, 1, 1}, 
	{-1, 0, 1, 1, 1, 1}, 
	{-1, 0, 0, 2, 1, 1}, 
	{-1, 0, 0, 0, 0, 1}, 
	{-1, 0, 0, 0, 0, 0}, 
};
int dx[8] = {-2, -2, -1, -1, 1, 1, 2, 2}, dy[8] = {-1, 1, -2, 2, -2, 2, -1, 1};
int g() {
	int res = 0;
	for(int i = 1; i <= 5; i++) {
		for(int j = 1; j <= 5; j++) {
			if(a[i][j] != goal[i][j]) res++;
		}
	}
	return res;
}
void IDA_Star(int dep, int x, int y, int maxdep) {
	if(dep == maxdep) {
		if(!g()) success = 1;
		return;
	}
	for(int i = 0; i < 8; i++) {
		int xt = x + dx[i], yt = y + dy[i];
		if(xt < 1 or xt > 5 or yt < 1 or yt > 5) continue;
		swap(a[x][y], a[xt][yt]);
		if(dep + g() <= maxdep) IDA_Star(dep + 1, xt, yt, maxdep);
		swap(a[x][y], a[xt][yt]);
	}
}
int main() {
	scanf("%d", &TT);
	while(TT--) {
		for(int i = 1; i <= 5; i++) for(int j = 1; j <= 5; j++) {
			char c; scanf(" %c", &c); if(c == '*') a[i][j] = 2, st_x = i, st_y = j; else a[i][j] = c - '0';
		}
		ans = -1; success = 0;
		for(int i = 1; i <= 15; i++) {
			IDA_Star(0, st_x, st_y, i);
			if(success) {
				ans = i; break;
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}

代码很容易理解,写出来有点令人断片,多打打题就好了,难点就在于估价函数。

posted @ 2024-02-01 07:40  水晶矩阵锭  阅读(21)  评论(0编辑  收藏  举报