最短路问题
弗洛伊德算法
通过中间节点 k 来实现 i 和 j 的距离缩短,空间和时间复杂度都为 n3,一般数据在 400 以内可以使用。
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
if (ai < inf && ak < inf)
{
if (ai + ak < ai)
{
ai = ai + ak;
}
}
迪杰斯特拉算法
指定一个点到其余各个顶点的最短路径,也叫“单源最短路径”。
矩阵存储边代码:
int e10, dis[10], book[10], n, m, t1, t2, t3, u, v, min;
int inf = 1 << 30;
//读入n和m,n表示顶点个数,m表示边的条数
scanf("%d%d", &n, &m);
//初始化
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
if (i == j)
ei = 0;
else
ei = inf;
}
//读入边
for (int i = 0; i < m; i++)
{
scanf("%d%d%d", &t1, &t2, &t3);
et1 = t3;
}
//初始化dis数组,这是1号顶点到其余各个顶点的初始化路程
for (int i = 1; i <= n; i++)
dis[i] = e1;
//book数组初始化
for (int i = 1; i <= n; i++)
book[i] = 0;
book[1] = 1;
//核心算法
for (int i = 1; i <= n - 1; i++)
{
//找到离1号点最近的顶点
min = inf;
for (int j = 1; j <= n; j++)
if (book[j] == 0 && dis[j] < min)
{
min = dis[j];
u = j;
}
book[u] = 1;
for (int v = 1; v <= n; v++)
if (eu < min)
if (dis[v] > dis[u] + eu)
dis[v] = dis[u] + eu;
}
//输出结果
for (int i = 1; i <= n; i++)
printf("%d ", dis[i]);
链式前向星存储边:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
struct EDGE
{
int next; //下一条边的存储下标
int to; //这条边的终点
int w; //权值
};
EDGE edge[100];
int n, m, cnt;
int head[10]; //head[i]表示以i为起点的第一条边
void Add(int u, int v, int w)
{ //起点u, 终点v, 权值w
edge[++cnt].next = head[u];
edge[cnt].w = w;
edge[cnt].to = v;
head[u] = cnt; //第一条边为当前边
}
int main()
{
int dis[10], book[10], t1, t2, t3, u, v, min;
int inf = 1 << 30;
//读入n和m,n表示顶点个数,m表示边的条数
scanf("%d%d", &n, &m);
//读入边
for (int i = 0; i < m; i++)
{
scanf("%d%d%d", &t1, &t2, &t3);
Add(t1, t2, t3);
}
//初始化dis数组,这是1号顶点到其余各个顶点的初始化路程
for (int i = 1; i <= n; i++)
dis[i] = inf;
dis[1] = 0;
for (int i = head[1]; i != 0; i = edge[i].next)
dis[edge[i].to] = edge[i].w;
//book数组初始化
for (int i = 1; i <= n; i++)
book[i] = 0;
book[1] = 1;
//核心算法
for (int i = 1; i <= n - 1; i++)
{
//找到离1号点最近的顶点
min = inf;
for (int j = 1; j <= n; j++)
if (book[j] == 0 && dis[j] < min)
{
min = dis[j];
u = j;
}
book[u] = 1;
for (int v = head[u]; v != 0; v = edge[v].next)
if (dis[edge[v].to] > dis[u] + edge[v].w)
dis[edge[v].to] = dis[u] + edge[v].w;
}
//输出结果
for (int i = 1; i <= n; i++)
printf("%d ", dis[i]);
return 0;
}
/*
输入
6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
输出
0 1 8 4 13 17
*/
使用优先队列对算法进行优化:
#include <cstdio>
#include <cstring>
#include <queue>
#include <ctime>
using namespace std;
const int MAX_N = 30000, MAX_L = 30000; //边的最大数量
bool isPrint = 0;
int n, m;
int dis[MAX_N], book[MAX_N], nodeExist[MAX_N];
// 使用优先队列存结点,降低松弛操作复杂度
struct NODE
{
int aimNode, distance;
NODE(int aimNode, int distance) : aimNode(aimNode), distance(distance) {}
bool operator<(const NODE &rhs) const
{
return distance > rhs.distance;
}
};
priority_queue<NODE> Q;
// 链式前向星存边
struct EDGE
{
int next; //下一条边的存储下标
int to; //这条边的终点
int w; //权值
};
EDGE edge[MAX_L];
int cnt;
int head[MAX_N]; //head[i]表示以i为起点的第一条边
void Add(int u, int v, int w)
{
//起点u, 终点v, 权值w
edge[++cnt].next = head[u];
edge[cnt].w = 1;
edge[cnt].to = v;
head[u] = cnt; //第一条边为当前边
}
void Dijikstra(int startNode)
{
memset(dis, 0x7F, sizeof(dis));
memset(book, 0, sizeof(book));
dis[startNode] = 0;
book[startNode] = 1;
//核心算法
Q.push(NODE(startNode, 0));
while (!Q.empty())
{
NODE q = Q.top();
Q.pop();
// book[q.aimNode] = 1;
for (int v = head[q.aimNode]; v != 0; v = edge[v].next)
{
if (dis[edge[v].to] > dis[q.aimNode] + edge[v].w)
{
dis[edge[v].to] = dis[q.aimNode] + edge[v].w;
Q.push(NODE(edge[v].to, dis[edge[v].to]));
}
}
}
//输出结果
if (isPrint)
{
printf("---- Now Start Node IS %d ----\n", startNode);
for (auto i = 0; i < MAX_N; i++)
if (nodeExist[i])
{
if (dis[i] == 2139062143)
printf("%d->%d: inf\n", startNode, i);
else
printf("%d->%d: %d\n", startNode, i, dis[i]);
}
}
}
int main()
{
clock_t start, end;
start = clock();
FILE *fr, *fw;
fr = freopen("small.txt", "r", stdin);
if (isPrint)
fw = freopen("out_small.txt", "w", stdout);
//读入n和m,n表示顶点个数,m表示边的条数
scanf("%d%d", &n, &m);
//读入边
for (int i = 0; i < m; i++)
{
int t1, t2;
scanf("%d%d", &t1, &t2);
nodeExist[t1] = nodeExist[t2] = 1;
Add(t1, t2, 1);
}
// 求所有点为起点的单源最短路
int p = 0;
for (int i = 0; i < MAX_N; i++)
{
if (!nodeExist[i])
continue;
p++;
if (!isPrint)
printf("%d/%d\n", p, n);
Dijikstra(i);
}
fclose(fr);
if (isPrint)
fclose(fw);
end = clock();
if (!isPrint)
printf("%fs", double(end - start) / CLOCKS_PER_SEC);
return 0;
}
bellman-ford 算法
#include <iostream>
#include <cstdio>
#include <cstring>
#include <ctime>
using namespace std;
const int MAX_L = 30000, MAX_N = 30000;
int dis[MAX_N], nodeExist[MAX_N], u[MAX_L], v[MAX_L], w[MAX_L];
int n, m;
bool isPrint = 0;
void bellmanFord(int origin)
{
memset(dis, 0x7F, sizeof(dis));
dis[origin] = 0;
for (int i = 0; i < MAX_L; i++)
if (u[i] == origin)
dis[v[i]] = 1;
for (int k = 1; k <= n; k++)
for (int i = 1; i <= m; i++)
if (dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i];
if (isPrint)
{
printf("---- Now Start Node IS %d ----\n", origin);
for (auto i = 0; i < MAX_N; i++)
if (nodeExist[i])
{
if (dis[i] == 2139062143)
printf("%d->%d: inf\n", origin, i);
else
printf("%d->%d: %d\n", origin, i, dis[i]);
}
}
}
int main()
{
clock_t start, end;
start = clock();
FILE *fr, *fw;
fr = freopen("small.txt", "r", stdin);
if (isPrint)
fw = freopen("out_small.txt", "w", stdout);
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
scanf("%d%d",&u[i],&v[i]);
nodeExist[v[i]] = nodeExist[u[i]] = w[i] = 1;
}
// 求所有点为起点的单源最短路
int p = 0;
for (int i = 0; i < MAX_N; i++)
if (nodeExist[i])
{
p++;
bellmanFord(i);
if (!isPrint)
printf("%d/%d\n", p, n);
}
fclose(fr);
if (isPrint)
fclose(fw);
end = clock();
if (!isPrint)
printf("%fs", double(end - start) / CLOCKS_PER_SEC);
return 0;
}
dis 数组作用与迪杰斯特拉算法相同。u、v、w 三个数组用来记录边的信息。
第 i 条边存储在 u[i] v[i] w[i]中,表示从顶点 u[i]到到顶点 v[i]的边权值为 w[i].
SPFA 算法
设立一个先进先出的队列 q 用来保存待优化的结点,优化时每次取出队首结点 u,并且用 u 点当前的最短路径估计值对离开 u 点所指向的结点 v 进行松弛操作,如果 v 点的最短路径估计值有所调整,且 v 点不在当前的队列中,就将 v 点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
dis[i] > dis[v] + a[v][i]
“>“改为”<“后可求最长路
代码示例:
void spfa(int s)
{
for (int i = 0; i <= n; i++)
dis[i] = 99999999; //初始化每点i到s的距离
dis[s] = 0;
vis[s] = 1;
q.push(s); //队列初始化,s为起点,dis数组作用与bellman-ford算法相同
int i, v;
while (!q.empty())
{ //队列非空
v = q.front();//取队首元素
vis[v] = 0;//释放队首结点,因为这节点可能下次用来松弛其它节点,重新入队
for (i = 0; i <= n; i++) //对所有顶点
if (a[v][i] > 0 && dis[i] > dis[v] + a[v][i])
{ //节点s到节点i通过节点v松弛
dis[i] = dis[v] + a[v][i]; //修改最短路
if (vis[i] == 0)
{ //如果扩展结点i不在队列中,入队
q.push(i);
vis[i] = 1;
}
}
}
}