目录
前言
之前我们写过的搜索算法都是一种"盲目"的搜索算法 ,就是说我并不知道我所走的这条路是否可以到达终点,但只要我搜索范围足够大,那么就一定可以到达终点 ,这样的盲目式搜索虽然思路简单但是也造成了很多不必要的搜索,所以我们之后引入了剪枝技术,但这任然改变不了盲目搜索算法的本质,于是A*算法就产生了,它相当于给搜索算法增加了一个感知模块,从而使得搜索算法变得智能。
在开始算法讲解前,先说说本题需要用到的其他搜索技术吧
dijkstra算法
这个算法相当于在传统的bfs 算法的基础上加上**优先队列,**是一个可以搜索起点到每个节点的最短路的最短路径的算法,由于使用了优先队列,使得它可以处理边权不同的图问题
A*算法
这才是这个blog的重点,前面说到传统bfs算法是盲目的,但如果我们可以事先知道,走哪条路径是最优的,那么每次都取最优的方向开始搜索,这不就给算法赋予了方向感了吗。
大家可能已经注意到了,每次去最优的方向,这不就是贪心算法的思路吗,但肯定也有人听过,贪心算法得出的答案不一定是最优的,如果是在没有任何障碍的方格图中,贪心算法是最优的,但如果是其他情况,那就不一定了,我们已知dijkstra算法得出的答案一定是最优的,那么是否可以将两种算法结合一下,那么既可以保证算法的正确性又提升了算法的执行效率,答案是肯定的,这也就得出了A*算法。
A*算法=dijkstra算法+贪心算法。
贪心算法每次优先入队的是距离终点最近的节点,dijkstra算法每次入队的是距离起点最近的节点,那么A*算法就可以每次优先入队距离起点距离+距离终点距离最小的节点,假设起点为s终点为t,起点到达i节点后优先入队s->i+i->t最小的节点。
现在我们来说明A*算法的正确性,如果到达终点时入队的时s->i+i->t最小的节点,由于i->t为0那么入队的就是s->t最小的节点,这个就是答案
k短路径问题
问题描述
给出一个图,由n个点和m条边组成,给定起点s和终点t,求s到t的第k短路径,图中每个点包括起点和终点都可以多次经过。
输入
第一行输入两个整数n,m其中1<=n<=1000, 1<=m<=100000;点从1~n开始编号,之后的m行中每行输入三个整数a,b,w,代表a到b之间有一条单向变,长度为w,最后一行输入三个整数s,t
,k。
输出
输出s到t的第k短路径的长度。
问题分析
如何求第k短路径
我们先解决一个基础问题,如何求第k短路径,之前都是求最短路径,如果bfs算法中第一次搜索到终点时那么就是最短路径,那么同理,如果bfs算法中第k次搜索到终点 时那么这个就是它的第k短路径,可以用一个计数变量实现这一算法
如何实现A*算法
实现A*算法的关键就是设计f(i)函数,其中g(i)函数是到起点的最短路径,h(i)是到终点的理论最短路径,那么h(i)就可以事先用dijkstra算法求出终点到图中各个点的最短路径,从而实现A*算法
这个算法涉及很多个变量,编码时一定要小心
代码
cpp
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1005, M = 100005;
struct edge { //存储单向边,to代表该单向边的目的地,w代表该单向边的长度
int to, w;
edge(int a, int b) {
to = a;
w = b;
}
};
vector<edge>G[M], G2[M]; //G存储正图,G2存储反图
struct node { //id表示该节点的编号,dis表示该节点到终点的距离(从终点反向搜索)
int id, dis;
node(int a, int b) {
id = a, dis = b;
}
//优先队列默认是取出最大的元素,所以需要重新定义
bool operator <(const node& u)const {
return dis > u.dis;
}
}; //这个node节点用于从终点搜索的优先队列中
int dist[N]; //存储节点到终点的距离(存储h函数的值)
void dijkstra(int s) {
for (int i = 0; i < N; i++) {
dist[i] = INF;
}
dist[s] = 0;
priority_queue<node>q;
q.push(node(s, dist[s]));
while (!q.empty()) {
node u = q.top();
q.pop();
for (int i = 0; i < G2[u.id].size(); i++) {
edge y = G2[u.id][i];
if (dist[y.to] > u.dis + y.w) {
dist[y.to] = u.dis + y.w;
q.push(node(y.to, dist[y.to]));
}
}
}
}
struct point {
int v, g, h;
point(int a, int b, int c) {
v = a, g = b, h = c;
}
//A_star算法,根据f=g+h判断那个节点先入队
bool operator <(const point& d)const {
return g + h > d.g + d.h;
}
};
int times[N]; //存储节点访问次数
int A_star(int s,int t,int k) {
memset(times, 0, sizeof(times));
priority_queue<point>q;
q.push(point(s, 0, 0));
while (!q.empty()) {
point p = q.top();
q.pop();
//每次出队算一条路径
times[p.v]++;
if (times[p.v] == k && p.v == t) {
return p.g + p.h;
}
for (int i = 0; i < G[p.v].size(); i++) {
edge y = G[p.v][i];
q.push(point(y.to, p.g + y.w, dist[y.to]));
}
}
//没有答案,输出-1
return -1;
}
int main() {
int n,m;
scanf("%d%d", &n, &m);
while (m--) {
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
G[a].push_back(edge(b, w));
G2[b].push_back(edge(a, w));
}
int s, t, k;
scanf("%d%d%d", &s, &t, &k);
if (s == t) k++; //注意原来就相等时走0步的情况不算一条路径
dijkstra(t);
printf("%d\n", A_star(s, t, k));
return 0;
}
有关问题之外
本题的h(i)函数是需要事先处理的,但也不乏有题目时不需要处理可以直接得出的,例如方格图中的曼哈顿距离,国际象棋棋盘中的对角线距离,不限制方向图中的欧式距离等