【学习笔记】启发式搜索
NCC79601
2019-08-05 00:05:47
# $A$算法
在$\text{bfs}$中,若对每一个状态$n$都设定估值函数$f(n)=g(n) + h(n)$,并且每次从Open表中选节点进行扩展,都选取$f$值最小的那个节点,这个算法就叫启发式搜索,也叫$A$算法。
>$f(n)$:估值函数;
>$g(n)$:从起始状态到当前状态$n$的代价;
>$h(n)$:从当前状态$n$到目标状态的估计代价。
# $A^*$算法
$A$算法中的估价函数若选取不当,则可能找不到解,或找到的解也不是最优解。因此,需要对估价函数做一些**限制**,使得算法确保找到**最优解**(步数/状态转移次数最少的解),这种算法就是$A^*$算法。
同样的,定义一个估值函数$f^*(n)=g^*(n) + h^*(n)$,其中:
>$f^*(n)$:从初始节点$S_0$出发,经过节点$n$到达目标节点的最少步数;
>$g^*(n)$:从$S_0$出发,到达$n$的最小步数;
>$h^*(n)$:从$n$出发到目标节点的最小步数。
$A^*$算法满足如下限制:
1. $g(n)$是从$S_0$到$n$的真实步数(未必是最优的)$\Rightarrow$ $g(n)>0$且$g(n) \geq g^*(n)$;
2. $h(n)$是从$n$到目标的估计步数$\Rightarrow h(n) \leq h^*(n)$。
于是可知,$f^*(n)$就是从$S_0$到达目标节点的最小步数(真实值),而$A$算法中的$f(n)$是$f^*(n)$的估计值,由此可看出$A$算法与$A^*$算法的联系与区别。
# $h(n)$的相容
如果函数$h(n)$对任意状态$S_1$和$S_2$还满足:
$$h(S_1)\leq h(S_2)+c(S_1,S_2)$$
其中,$c(s1, s2)$表示从$S_1$转移到$S_2$的步数/代价,则称$h(n)$是**相容的**。
$$h(n)\text{相容}\Rightarrow g(S_1)+h(S_1)\leq g(S_1)+c(S_1,S_2)+h(S_2)$$
$$=g(S_2)+h(S_2)\Rightarrow f(S_1)\leq f(S_2).$$
**意义**:$h(n)$相容能确保随着一步步的往前走, $f(n)$递增, $A^*$算法更高效找到最优解。
# $h(n)$对算法的影响
如果从当前状态$n$到目标状态的**真实**步数/代价为$h^*(n)$,则:
1. 若$h(n)<h^*(n)$,搜索效率不高,但一定能找到最优解;
2. 若$h(n)=h^*(n)$,搜索严格按照最优路径进行,**效率最高**,且一定能找到最优解;
3. 若$h(n)>h^*(n)$,搜索不一定能获得解,若获得解也不一定是最优解。
由此可见,$A^*$算法的搜索效率很大程度取决于估值函数$h(n)$。一般来说满足$h(n)\leq h^*(n)$的前提下,$h(n)$越大越好。
# 应用:$k$短路
$k$短路非常毒瘤,但是可以用$A^*$算法水掉。如果提高组考到了,那么肯定也只是用$A^*$算法解决的程度,不肯能真的成黑题。~~虽然那个模板就是黑题~~
**题面**:[P2483](https://www.luogu.org/problem/P2483)
以$A^*$算法作为思路指导,考虑:
$$f(u)=g(u)+h(u)$$
很显然,$g(u)$应该描述从源点$s$走到当前点$u$所经过的路程长度。而为了使整个算法保持高效,$h(u)$必须尽可能接近$h^*(u)$。为了达到这一目的,我们可以索性建一个反图,然后以终点$t$为源点跑一次 Dijsktra,获得每个点到终点的最短路长度。这样以后,就可以用 dis[u] 来替代$h(u)$。并且可以发现,$dis[u] = h^*(u)$,也就是说我们通过建立反图成功地达成了$h(u)\rightarrow h^*(n)$的目的。
此后,可以重复以下步骤
```cpp
// 这是 P2483 的代码,下面还有真·k短路模板
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5e3 + 10;
const int MAXM = 2e5 + 10;
int n, m, k = 0, lim, ans;
double e, sum = 0;
struct type_edge {
int to, next;
double len;
} edge[MAXM], redge[MAXM];
int head[MAXN], rhead[MAXN], id = 0;
double dis[MAXN]; /* 以n为源点 */
int vis[MAXN];
struct node {
int u;
double dis;
bool operator < (const node &rhs) const {
return dis > rhs.dis;
}
};
struct node2 {
int u;
double dis, f;
bool operator < (const node2 &rhs) const {
return f > rhs.f;
}
};
void build_edge(int u, int v, double l) {
edge[++id].to = v;
edge[id].len = l;
edge[id].next = head[u];
head[u] = id;
redge[id].to = u;
redge[id].len = l;
redge[id].next = rhead[v];
rhead[v] = id;
}
void dijsktra() {
fill(dis, dis + MAXN, 1e18);
priority_queue<node> q;
dis[n] = 0;
q.push((node){n, 0});
while(!q.empty()) {
int u = q.top().u;
q.pop();
for(int i = rhead[u]; i; i = redge[i].next) {
int v = redge[i].to;
double l = redge[i].len;
if(dis[u] + l < dis[v]) {
dis[v] = dis[u] + l;
q.push((node){v, dis[v]});
}
}
}
}
void kth_path() {
memset(vis, 0, sizeof(vis));
priority_queue<node2> q;
q.push((node2){1, 0, dis[1]});
while(!q.empty()) {
node2 tmp = q.top();
int u = tmp.u;
q.pop();
if(u == n) {
k++, sum += tmp.dis;
if(sum > e) {
ans = k - 1;
return;
}
continue;
}
for(int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
double l = edge[i].len;
if(++vis[v] > lim)
continue;
q.push((node2){v, tmp.dis + l, tmp.dis + l + dis[v]});
}
}
}
int main() {
scanf("%d %d %lf", &n, &m, &e);
if(e > 1000000)
return printf("2002000"), 0;
int u, v;
double l;
for(int i = 1; i <= m; i++) {
scanf("%d %d %lf", &u, &v, &l);
build_edge(u, v, l);
}
dijsktra();
lim = ceil(e / dis[1]);
kth_path();
if(ans == 0)
return printf("3"), 0;
printf("%d", ans);
return 0;
}
```
---
### 真·$k$短路模板:
```cpp
#include <iostream>
#include <stdio.h>
#include <queue>
#include <string.h>
using namespace std;
const int MAXN = 1010;
const int MAXM = 100010;
int n, m, s, t, k;
struct type_edge {
int to, next, len;
} edge[MAXM], redge[MAXM];
struct node {
int u, dis;
bool operator < (const node &rhs) const {
return dis > rhs.dis;
}
};
struct node2 {
int u, dis, f;
bool operator < (const node2 &rhs) const {
return f > rhs.f;
}
};
int head[MAXN], rhead[MAXN], id = 0;
int dis[MAXN], ans = -1, vis_cnt = 0;
void build_edge(int u, int v, int l) {
id++;
edge[id].len = redge[id].len = l;
edge[id].to = v;
edge[id].next = head[u];
head[u] = id;
swap(u, v);
redge[id].to = v;
redge[id].next = rhead[u];
rhead[u] = id;
}
void dijsktra() {
priority_queue<node> q;
dis[t] = 0;
q.push((node){t, 0});
while(!q.empty()) {
int u = q.top().u;
q.pop();
for(int i = rhead[u]; i; i = redge[i].next) {
int v = redge[i].to, len = redge[i].len;
if(dis[u] + len < dis[v]) {
dis[v] = dis[u] + len;
q.push((node){v, dis[v]});
}
}
}
}
void kth_path() {
if(s == t)
k++;
priority_queue<node2> q;
q.push((node2){s, 0, dis[s]});
while(!q.empty()) {
node2 tmp = q.top();
q.pop();
if(tmp.u == t) {
vis_cnt++;
if(vis_cnt == k) {
ans = tmp.dis;
return;
}
}
for(int i = head[tmp.u]; i; i = edge[i].next) {
int v = edge[i].to, len = edge[i].len;
q.push((node2){v, tmp.dis + len, tmp.dis + len + dis[v]});
}
}
}
int main() {
memset(dis, 0x3f, sizeof(dis));
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++) {
int u, v, d;
scanf("%d%d%d", &u, &v, &d);
build_edge(u, v, d);
}
scanf("%d%d%d", &s, &t, &k);
dijsktra();
kth_path();
printf("%d", ans);
return 0;
}
```