Floyd算法
算法原理
Floyd算法解决多源最短路径问题。本质用动态规划。如,mindis[1][5] = mindis[1][3] + mindis[3][5]
算法流程
dp五部曲:
- dp数组含义: dp[i][j]表示从i到j的最短距离。
- 递推式:dp[i][j] = min(dp[i][k] + dp[k][j]), k需要遍历
- 初始化:dp[i][i] = 0,如果i到j有边,dp[i][j] = 权值,否则为正无穷
- 遍历顺序:三层循环
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const long long INF = 1e18;
void floyd(vector<vector<long long>>& dist) {
int n = dist.size();
for (int k = 0; k < n; k++) { // 中转点
for (int i = 0; i < n; i++) { // 起点
for (int j = 0; j < n; j++) { // 终点
if (dist[i][k] == INF || dist[k][j] == INF) {
continue;
}
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}
}
}
}
int main() {
int n, m;
cin >> n >> m;
vector<vector<long long>> dist(n, vector<long long>(n, INF));
// 自己到自己距离为 0
for (int i = 0; i < n; i++) {
dist[i][i] = 0;
}
// 输入 m 条边:u v w
// 表示 u 到 v 的边权为 w
for (int i = 0; i < m; i++) {
int u, v;
long long w;
cin >> u >> v >> w;
dist[u][v] = min(dist[u][v], w);
// 如果是无向图,加上这一句
// dist[v][u] = min(dist[v][u], w);
}
floyd(dist);
// 输出最短距离矩阵
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dist[i][j] == INF) {
cout << "INF ";
} else {
cout << dist[i][j] << " ";
}
}
cout << endl;
}
return 0;
}
当需要打印出所有路径时:
优化:开两个数组,一个存距离,一个存下一跳。path[i][j]表示从i到j的路径上,以i为起点的下一跳是什么。还原时不断寻找下一跳为起点的path。如:path[1][4] = 2 -> path[2][4] = 3-> path[3][4] = 4 ,到达终点,停止。
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const long long INF = 1e18;
void floydWithPath(
vector<vector<long long>>& dist,
vector<vector<int>>& path
) {
int n = dist.size();
for (int k = 0; k < n; k++) { // 中转点
for (int i = 0; i < n; i++) { // 起点
for (int j = 0; j < n; j++) { // 终点
if (dist[i][k] == INF || dist[k][j] == INF) {
continue;
}
if (dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
// i 到 j 的下一跳,应该等于 i 到 k 的下一跳
path[i][j] = path[i][k];
}
}
}
}
}
vector<int> getPath(int start, int end, const vector<vector<int>>& path) {
vector<int> result;
if (path[start][end] == -1) {
return result; // 不可达
}
result.push_back(start);
while (start != end) {
start = path[start][end];
result.push_back(start);
}
return result;
}
int main() {
int n, m;
cin >> n >> m;
vector<vector<long long>> dist(n, vector<long long>(n, INF));
vector<vector<int>> path(n, vector<int>(n, -1));
// 初始化
for (int i = 0; i < n; i++) {
dist[i][i] = 0;
path[i][i] = i;
}
// 输入 m 条边:u v w
// 表示 u 到 v 的边权为 w
for (int i = 0; i < m; i++) {
int u, v;
long long w;
cin >> u >> v >> w;
if (w < dist[u][v]) {
dist[u][v] = w;
path[u][v] = v;
}
// 如果是无向图,加上下面这一段
/*
if (w < dist[v][u]) {
dist[v][u] = w;
path[v][u] = u;
}
*/
}
floydWithPath(dist, path);
// 输出最短距离矩阵
cout << "Shortest distance matrix:" << endl;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dist[i][j] == INF) {
cout << "INF ";
} else {
cout << dist[i][j] << " ";
}
}
cout << endl;
}
// 查询一条路径
int start, end;
cout << "Input start and end: ";
cin >> start >> end;
vector<int> shortestPath = getPath(start, end, path);
if (shortestPath.empty()) {
cout << "No path from " << start << " to " << end << endl;
} else {
cout << "Shortest path: ";
for (int i = 0; i < shortestPath.size(); i++) {
if (i > 0) cout << " -> ";
cout << shortestPath[i];
}
cout << endl;
cout << "Shortest distance: " << dist[start][end] << endl;
}
return 0;
}
A* 算法
算法原理
改良版的BFS。区别于穷举,A* 根据启发式函数有选择的进行遍历。给每一个节点一个权值,Key = 起点到当前节点的距离 + 当前节点到终点的距离。维护BFS队列,每次从中取权值最小的节点。
cpp
#include<iostream>
#include<queue>
#include<string.h>
using namespace std;
int moves[1001][1001];
int dir[8][2]={-2,-1,-2,1,-1,2,1,2,2,1,2,-1,1,-2,-1,-2};
int b1, b2;
// F = G + H
// G = 从起点到该节点路径消耗
// H = 该节点到终点的预估消耗
struct Knight{
int x,y;
int g,h,f;
bool operator < (const Knight & k) const{ // 重载运算符, 从小到大排序
return k.f < f;
}
};
priority_queue<Knight> que;
int Heuristic(const Knight& k) { // 欧拉距离
return (k.x - b1) * (k.x - b1) + (k.y - b2) * (k.y - b2); // 统一不开根号,这样可以提高精度
}
void astar(const Knight& k)
{
Knight cur, next;
que.push(k);
while(!que.empty())
{
cur=que.top(); que.pop();
if(cur.x == b1 && cur.y == b2)
break;
for(int i = 0; i < 8; i++)
{
next.x = cur.x + dir[i][0];
next.y = cur.y + dir[i][1];
if(next.x < 1 || next.x > 1000 || next.y < 1 || next.y > 1000)
continue;
if(!moves[next.x][next.y])
{
moves[next.x][next.y] = moves[cur.x][cur.y] + 1;
// 开始计算F
next.g = cur.g + 5; // 统一不开根号,这样可以提高精度,马走日,1 * 1 + 2 * 2 = 5
next.h = Heuristic(next);
next.f = next.g + next.h;
que.push(next);
}
}
}
}
int main()
{
int n, a1, a2;
cin >> n;
while (n--) {
cin >> a1 >> a2 >> b1 >> b2;
memset(moves,0,sizeof(moves));
Knight start;
start.x = a1;
start.y = a2;
start.g = 0;
start.h = Heuristic(start);
start.f = start.g + start.h;
astar(start);
while(!que.empty()) que.pop(); // 队列清空
cout << moves[b1][b2] << endl;
}
return 0;
}
图论总结
图的定义,图的存储,图的遍历,并查集(解决快速判断元素是否属于一个集合的问题,压缩路径),图的连通(最小生成树,prim,kasul算法),拓扑排序(解决线性依赖问题)最短路径(单源有向正权,单源有向负权,多源)
添加链接描述