总结:
单源最短路
仅有正权边
dujkstra算法:【贪心】
代码实现:
一、邻接矩阵版本:用于边数较多的稠密图
代码实现:
int n, m,s; //n个点,m个边,源点为s
int g[N][N]; //临界矩阵记录距离
int vis[N];//用于判断当前点是否属于s中,已经算过最短距离的点
int dis[N];//每个点到源点的距离
int p[N];//记录到第i个点的最短路径
void dijsktra(int u)
{
for (int i = 1; i <= n; i++)
{
vis[i] = false;
dis[i] = inf;
p[i] = -1;
}
//初始化,有用的值的初始化
dis[u] = 0;
//源点初始化距离为0
//有n个点,每一次选择当前最短的最优解,所以要遍历n-1次
for (int i = 2; i <= n; i++)
{
//查找属于s-v的最短路径的点
int t = -1, Min = inf;
for (int j = 1; j <= n; j++)
{
if (!vis[j] && (t == -1 || Min > dis[j]))
{
t = j;
Min = dis[t];
}
}
vis[t] = true;
//标记点属于s里面了
//遍历所有点,松弛操作,更新
for(int j=1;j<=n;j++)
{
if (!vis[j] && dis[j] > Min + g[t][j])
{
dis[j] = Min + g[t][j];
p[j] = t;
}
}
}
for (int i = n; i <= n; i++)
{
//若该点没有连接
if (dis[i] >= inf)
cout << - 1 << " ";
else
cout << dis[i] << " ";
}
//回溯输出路径
int t = n;
while (p[t] != -1)
{
cout << p[t] << " ";
t = p[t];
}
}
void solve()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
g[i][j] = inf;
while (m--)
{
int a, b, c;
cin >> a >> b >> c;
g[a][b] = min(g[a][b], c);
}
dijsktra(1);
}
二、堆优化【稀疏图,用最小优先队列使每次更新的小值存入数组中】
代码实现:
struct Edge
{
int to, ne, w;
}edge[N * 2 + 5];//特别注意,边的数量是多少啊!!!
int h[N];
int idx = 0;
void init()
{
memset(h, -1, sizeof h);
idx = 0;
}
void add(int u, int v, int w)
{
edge[idx].to = v; edge[idx].w = w; edge[idx].ne = h[u]; h[u] = idx++;
}
int n, m, s; //n个点,m个边,源点为s
int vis[N];//用于判断当前点是否属于s中,已经算过最短距离的点
int dis[N];//每个点到源点的距离
int p[N];//记录到第i个点的最短路径
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
void dijsktra(int u)
{
q.empty();
for (int i = 1; i <= n; i++)
{
vis[i] = false;
dis[i] = inf;
p[i] = -1;
}
dis[u] = 0;
q.push({ 0, u });
while (q.size())
{
pair<int, int> now = q.top();
q.pop();
if (vis[now.second] == true)
continue;
int t = now.second, Min = now.first;
vis[t] = true;
//t为当前最短路径的点,Min为最短路径的长度
//遍历所有这个点连接到的点,并且把点的距离进行疏松
for (int i = h[t]; i != -1; i = edge[i].ne)
{
//i枚举的是连接点的边的编号
int v = edge[i].to, w = edge[i].w;
//v记录的是连接的点,对这个点进行疏松,如果值变小了,就可以,加入q里面
if (dis[v] > Min + w)
{
dis[v] = Min + w;
q.push({ dis[v],v });
p[v] = t;
}
}
}
for (int i = 1; i <= n; i++)
{
if (dis[i] >= inf)
cout <<(1<<31-1) << " ";
else
cout << dis[i] << " ";
}
}
void solve()
{
init();
cin >> n >> m>>s;
while (m--)
{
int a, b, v;
cin >> a >> b >> v;
add(a, b, v);
}
dijsktra(s);
}
收穫++:
1.堆優化版本:小耿堆存儲<路徑長度,對應的終點>,每次有更新路徑較小的值且從來沒有入過堆,較小路徑和對應的點存入堆中,對中的點最多n個。堆中【st[i]=true】存的是s,堆外最短路徑可以改變的,比較的是[st[i]=false】存的是s-v。
2.dijsktra函數要先預處理,路徑長度inf化,st/vis false化,路徑p-1化
存在负权边
bellman-ford算法
【动态规划,当前点的状态由上一点转移】
代码实现:
struct EEdeg
{
int a, b, w;
}edges[M];
int n, m, k;
int dis[N];
int back[N];
void bellman_ford(int k)
{
for (int i = 1; i <= n; i++)
dis[i] = inf;
//初始化
dis[1] = 0;
for (int t = 0; t < k; t++)
{//限制有k条边
memcpy(back, dis, sizeof(dis));
//备份操作,eg.滚动数组
for (int i = 1; i <= m; i++)
{//遍历所有的边,球min值
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
dis[b] = min(dis[b], back[a] + w);
}
}
//
if (dis[n] >=inf / 2)
cout <<"impossible" << endl;
else
cout << dis[n] << endl;
}
void solve()
{
cin >> n >> m >> k;
for (int i = 1; i <= m; i++)
{
cin >> edges[i].a >> edges[i].b >> edges[i].w;
}
bellman_ford(k);
}
spfa算法
【一般可以用dijsktra算法都可以用spfa算法,如果被卡了,再回去;负权值一定可以用spfa算法】
2.是由bellman_ford算法的堆优化版本,值只有改变了才会对后面相连的线有影响
代码实现:
struct Edge
{
int to, ne, w;
}edge[M];
int h[1000001];
int vis[1000001];
int idx = 0;
void init()
{
memset(h, -1, sizeof h);
idx = 0;
}
void add(int u, int v, int w)
{
edge[idx].to = v; edge[idx].w = w; edge[idx].ne = h[u]; h[u] = idx++;
}
int n, m, k,s;
int dis[N];
int back[N];
int times[N];
void sofa(int s)
{
queue<int>q;
for (int i = 1; i <= n; i++)
{
dis[i] = inf;
vis[i] = false;
times[i]=0;
}
dis[s] = 0;
q.push(s);
vis[s] = true;
//当前的值改变,需要放入队列,对其后面的值进行改变
while (q.size())
{
int u = q.front();
q.pop();
vis[u] = false;
//表示当前的点已经不在队列了,也就是如果值改变还得入堆
for (int i = h[u]; i != -1; i = edge[i].ne)
{
//遍历以当前的点为起点的终边,对于上一个点路径的改变是否会使路径变短
int v = edge[i].to,w=edge[i].w;
if (dis[v] > (dis[u] + w))
{
dis[v]=dis[u] + w;
if (vis[v]==false)
{
//若使路径变短了,且不在存储路径有改变的点堆里面,就存入
q.push(v);
vis[v] = true;
times[v]++;
if(times[v]>=n+1)
{
cout<<"YES"<<endl;//存在负环
return;
}
}
}
}
}
for(int i=1;i<=n;i++)
cout<<dis[i]<<" ";//不存在负环输出最短路径。
}
void solve()
{
init();
cin >> n >> m>>s ;
for (int i = 1; i <= m; i++)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
sofa(s);
}
收获++:
1.核心,只有当前的值改变了,路径变短才会对它的子树枝产生影响,有可能使路径变短
2.队列记录路径改变的点,也就是后面的子枝条要改变的点,vis【=false=非天使组织,不影响】记录当点对后面的已经没有影响,vis【=true=天使组织,大发善心让后面的路径变短】说明正在队列中等待影响后面的人,所以初始false非天使都没有影响,存入队列后true天使,天使开始大法慈心(VS dijsktra的堆记录的是最短路径,vis用于判断路径已经确定,不再受后面的队列的影响)
多源最短路
Floyd算法【动态规划】
#include <iostream>
using namespace std;
const int N = 210, M = 2e+10, INF = 1e9;
int n, m, k, x, y, z;
int d[N][N];
void floyd() {
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
int main() {
cin >> n >> m >> k;
//初始值最大化
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(i == j) d[i][j] = 0;
else d[i][j] = INF;
//建边
while(m--) {
cin >> x >> y >> z;
d[x][y] = min(d[x][y], z);
//注意保存最小的边
}
//算最短路径
floyd();
//最短路径后的操作
while(k--) {
cin >> x >> y;
if(d[x][y] > INF/2) puts("impossible");
//由于有负权边存在所以约大过INF/2也很合理
else cout << d[x][y] << endl;
}
return 0;
}
收获++:
对于多源汇最短路来说,很简单和bellman-ford算法有点异曲同工之处,每个点每条边每次都进行一次疏松!