2.对于单源最短路存在负权边的图,若题目有求边数 <= k的条件,则只能用BF算法不能用Spfa
3.单源和多源指求一个点或多个点到终点的距离
Dijkstra(朴素)
活动 - AcWing
思路:
1.先初始化dist数组和g数组置为无穷大,且将dist[1]=0(dist[x] 表示x到起点的距离)
2.迭代n次,每次找到集合st外的距离起点最近的点t
3.再用点t来更新其他的点的距离
时间复杂度:O(n2)
代码如下:
#include
using namespace std;
const int N = 510;
int n,m;
int g[N][N],dist[N];
bool st[N];
int Dijkstra()
{
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
for(int i=1;i<=n;i++)
{
int t = -1;
for(int j=1;j<=n;j++)
{
if(!st[j]&&(t==-1||dist[t]>dist[j]))
{
t=j;
}
}
//if(t==n) break;
//将t加入集合
st[t]=true;
//用t来更新其他点的距离
for(int k=1;k<=n;k++)
{
dist[k]=min(dist[k],dist[t]+g[t][k]);
}
}
if(dist[n]==0x3f3f3f3f)return -1;//1-n不连通
return dist[n];
}
int main()
{
memset(g,0x3f,sizeof(g));
cin>>n>>m;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
g[a][b]=min(g[a][b],c);
}
cout<
Dijkstra(堆优化)
活动 - AcWing
代码如下:
#include
#include
#include
#include
using namespace std;
const int N=1e6+10;
typedef pair PII;
int n,m;//输入n,m 节点数量和边数
int h[N],e[N],ne[N],idx,w[N];//邻接表
int d[N];//存放距离数组
bool st[N];//存放相应的状态
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
//首先明确我们的邻接表放的是什么东西
//e[idx]里面放的是b,b是我们的另一个点,而idx更像是地址,
//我们则需要通过地址来获取我们需要的b数据,和c数组权重
}
int dijkstra(){
memset(d,0x3f,sizeof d);//朴素算法的要求初始化
d[1]=0;
priority_queue,greater> q; //优先队列--小根堆
q.push({0,1}); //存放编号和编号对应距离的集合
while(q.size()){
auto t=q.top();//取出第一个元素
q.pop();//弹出第一个元素
int ver=t.second,dis=t.first;//取出编号和距离
if(st[ver]) continue;//这个点确定了,打上烙印了,不走回头路
st[ver]=true;//打上烙印
//h[ver]代表我们要遍历的那一个链表里面的第一个结点的地址,记住!!!
//must remeber it,please!h[ver]是元素地址不是元素,本人老犯的一个病
for(int i=h[ver];i!=-1;i=ne[i]){
int j=e[i];//通过地址取出我们的b点即我们要遍历的那个点
//给自己的忠告:d[ver]和dis的区别:dis是我们这个点ver到1点的距
//d[ver]是ver这个点到1点的距离,所以没有区别,所以用哪个都行~ha
//注意:我们的w是通过idx地址来取的因为w[i]不是w[j],
//再次通过我们的输入顺序a,b,c,存放的时候是e[idx]=b,w[idx]=c,因此
//b------------c可以说是一一对应关系。翻译:b的前面一条路的权重
//h[ver]取的是ver后面的一个元素的首地址,所用取前面的路的权重
//便是取w[i]
if(d[j]>(d[ver]+w[i]))
{
d[j]=d[ver]+w[i];//不满足最小距离更新
q.push({d[j],j});//遍历到下一个点
}
}
}
//判断是否到达最后一点,4个字节所以4个3f
if(d[n]==0x3f3f3f3f) return -1;
return d[n];
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);//初始化我们的邻接表~~~~~
while(m--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
cout<
Bellman-Ford
853. 有边数限制的最短路 - AcWing题库
思路:
1.存储方法:用结构体存储a到b权重为w的边
2.迭代n次,每次用dist数组更新每个点的最短路径(松弛操作)其中每次备份dist数组保证用上次更新过的点来更新下一次,这也是spfa算法队列优化的原理。
注:若有负权回路则不存在最短路径。
对于串联效应的解释:(也就是为什么需要备份数组)
因为有边数限制,每次迭代是往前迈一步进行更新最短距离,每次迈一步需要基于上一次的状态,所以需要备份数组,否则用更新后的状态再更新就会发生错误。
代码如下:
#include
using namespace std;
const int N = 510,M = 10010,INF = 0x3f3f3f3f;
int dist[N],backup[N];
struct Edge{
int a,b,w;
}edges[M];
int n,m,k;
int bellman_ford()
{
memset(dist,0x3f,sizeof(dist));
dist[1]=0;
for(int i=0;iINF/2)return INF;//由于值为0x3f3f3f3f的点可以经过负权边更新为小一点的值,所以可以除以2判断
return dist[n];
}
int main()
{
cin>>n>>m>>k;
for(int i=0;i>a>>b>>w;
edges[i]={a,b,w};
}
int t = bellman_ford();
if(t==INF)cout<<"impossible"<
SPFA
活动 - AcWing
思路:
1.spfa是用队列来优化bf算法.
2.队列里面存在的就是待更新的点,也就是变小过的点才能够使后面的点更小。
3.spfa中的st数组表示这个点是否在队列中,防止重复。
4.类似与多源点的BFS。
为什么和BF算法的判断不一样:
代码如下:
#include
#include
#include
using namespace std;
const int N = 100010;
int h[N], e[N], w[N], ne[N], idx;//邻接表,存储图
int st[N];//标记顶点是不是在队列中
int dist[N];//保存最短路径的值
int q[N], hh, tt = -1;//队列
void add(int a, int b, int c){//图中添加边和边的端点
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void spfa(){
q[++tt] = 1;//从1号顶点开始松弛,1号顶点入队
dist[1] = 0;//1号到1号的距离为 0
st[1] = 1;//1号顶点在队列中
while(tt >= hh){//不断进行松弛
int a = q[hh++];//取对头记作a,进行松弛
st[a] = 0;//取完队头后,a不在队列中了
for(int i = h[a]; i != -1; i = ne[i])//遍历所有和a相连的点
{
int b = e[i], c = w[i];//获得和a相连的点和边
if(dist[b] > dist[a] + c){//如果可以距离变得更短,则更新距离
dist[b] = dist[a] + c;//更新距离
if(!st[b]){//如果没在队列中
q[++tt] = b;//入队
st[b] = 1;//打标记
}
}
}
}
}
int main(){
memset(h, -1, sizeof h);//初始化邻接表
memset(dist, 0x3f, sizeof dist);//初始化距离
int n, m;//保存点的数量和边的数量
cin >> n >> m;
for(int i = 0; i < m; i++){//读入每条边和边的端点
int a, b, w;
cin >> a >> b >> w;
add(a, b, w);//加入到邻接表
}
spfa();
if(dist[n] == 0x3f3f3f3f )//如果到n点的距离是无穷,则不能到达
cout << "impossible";
else cout << dist[n];//否则能到达,输出距离
return 0;
}
SPFA判负环
思路:
代码如下:
#include
#include
#include
using namespace std;
const int N = 100010;
int h[N], e[N], w[N], ne[N], idx;//邻接表,存储图
int st[N];//标记顶点是不是在队列中
int dist[N];//保存最短路径的值
int q[N], hh, tt = -1;//队列
int cnt[N];
int n, m;//保存点的数量和边的数量
void add(int a, int b, int c){//图中添加边和边的端点
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
bool spfa(){
//1有可能不是源点
for(int i=1;i<=n;i++)
{
st[i]=true;
q[++tt]=i;
}
while(tt >= hh){//不断进行松弛
int a = q[hh++];//取对头记作a,进行松弛
st[a] = 0;//取完队头后,a不在队列中了
for(int i = h[a]; i != -1; i = ne[i])//遍历所有和a相连的点
{
int b = e[i], c = w[i];//获得和a相连的点和边
if(dist[b] > dist[a] + c){//如果可以距离变得更短,则更新距离
dist[b] = dist[a] + c;//更新距离
cnt[b] = cnt[a] + 1;
if(cnt[b]>=n)return true;
if(!st[b]){//如果没在队列中
q[++tt] = b;//入队
st[b] = 1;//打标记
}
}
}
}
return false;
}
int main(){
memset(h, -1, sizeof h);//初始化邻接表
cin >> n >> m;
for(int i = 0; i < m; i++){//读入每条边和边的端点
int a, b, w;
cin >> a >> b >> w;
add(a, b, w);//加入到邻接表
}
if(spfa())cout<<"Yes"<
Floyd
活动 - AcWing
思路:
1.邻接矩阵存储图
2.三重循环暴力解决最短路
基于dp
代码如下:
#include
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");
else cout << d[x][y] << endl;
}
return 0;
}
复习小结~~