目录
单源有向无环无负权最短路
采用拓扑+贪心求解,按拓扑把入度为0的节点删去,更新与其相连的节点距离源点s的最短距离,因为无负,因此与其直接相连最短的点的一定比绕一个中转点的距离短,三角形两边和大于第三边
(注意,更新的是距离那个点最短的点,而不是任意点!!!)
时间复杂度N+M,最优秀了
//单源有向无环最短路 拓扑 思路:按拓扑把入度为0的节点删去,更新与其相连的节点距离源点s的最短距离
#include<cstdio>
#include<queue>
const int MAX=1e4+10,INF=0x3f3f3f3f;
using namespace std;
int in[MAX];//入度
int ans[MAX];
int n,m,x;
struct node { //链式前向星
int to,next,w;//u表示当前点到to的距离
} edge[MAX*MAX];
queue <int> q;
int head[MAX];
int dis[MAX],pre[MAX];//dis表示i到这个s的最短路径 ,pre记录前驱结点
void shortest(int s) { //s为源点
for(int i=0; i<=n; i++) {
dis[i]=INF;
pre[i]=i;
}
dis[s]=0;//s是出发点 入度为0
q.push(s);
while(!q.empty()) {//时间复杂度N+M,while是N的节点,for循环总共M条边
int u=q.front();
q.pop();
ans[x++]=u; ///记录答案
for(int i = head[u]; ~i; i=edge[i].next) { ///链式向前星存的图
int to=edge[i].to;
in[to]--; ///入度减1
if(dis[to]>dis[u]+edge[i].w)//更新最短路径 松弛
dis[to]=dis[u]+edge[i].w,pre[to]=u;//s->to 比s->u->to还远,就直接把s->to距离更新成s->u->to,然后记录前驱是u,记录前驱可以还原最短路
if(in[to]==0) {
q.push(to); ///入度为0压入队列
}
}
}
}
int main() {
scanf(" %d%d",&n,&m);
return 0;
}
单源无负权最短路
Dijkstra
模板
时间复杂度logn(n+m)
#include<cstdio>//优化后外层循环为n,内层循环1为优先队列push的logn,2一共为边数m乘以push的logn ,时间复杂度由n^2降为logn(n+m)
#include<iostream>//数组从1开始而不是0
#include<cstring>
#include<queue>
using namespace std;
const int INF=0x3f3f3f3f,N=1e5+10;
struct node {
int to,next,w;
friend bool operator < (const node &f1, const node &f2) {
return f1.w > f2.w;
}
} edge[N];
int n,m,cnt,s,t;
int head[N],dis[N];
bool vis[N];
priority_queue<node> Q;
inline void addedge(int from, int to, int w) {
edge[cnt].to = to;
edge[cnt].next = head[from];
edge[cnt].w = w;
head[from] = cnt++;
}
inline void init() {
cnt = 0;
memset(head, -1, sizeof(head));
memset(edge, 0, sizeof(edge));
memset(dis, 0x3f, sizeof(dis));
}
void dijkstra(int s,int t) { //s为源点 t为终点
node z;
z.to=s,z.next=0,z.w=0;//我们真正需要的是他的to当起点,这里把源点赋予to扔队列里启动算法
Q.push(z);
dis[s] = 0;
while (Q.size()) {
z = Q.top();
Q.pop();
// if(vis[z.to]) continue;//无向图的话请注释掉 因为这是为了剪枝,而无向图不能这么剪枝
// vis[z.to]=true;//无向图的话请注释掉
int start = z.to;
for (int i = head[start]; ~i; i = edge[i].next) {
int end = edge[i].to;
if (dis[end] > dis[start] + edge[i].w) {
dis[end] = dis[start] + edge[i].w;
Q.push(edge[i]);
}
}
}
printf("%d\n", dis[t]);
}
int main() {
while(~scanf(" %d%d%d%d",&n,&m,&s,&t)) {
init();
for (int i = 0; i < m; i++) {
int from, to, w ;
scanf(" %d%d%d", &from, &to, &w);
addedge(from, to, w);
addedge(to, from, w);//无向图的话请加上
}
dijkstra(s,t);
}
return 0;
}
单源有负权最短路+判断负环
采用SPFA求解
时间复杂度KM,K为节点平均入队次数
模板
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
const int N=1e5+10,M=5e5+10,INF=0x3f3f3f3f;
int n,m,s;//n个节点,m条边,s为源点
bool vis[N];//判断是否在队列中,防止一个队列出现重复点
int dis[N];//点i到源点最短距离
struct node {//链式前向星
int to,next,w;
} edge[M];
int cnt,head[N];
int cir[N];//如果cir[i]>=n,存在负环
void init() {
cnt=0;
memset(head,-1,sizeof(head));
for(int i=1; i<=n; i++) {
vis[i]=0;
dis[i]=INF;
cir[i]=0;
}
}
void addEdge(int from,int to,int w) { // 起点,终点,边长
edge[cnt].to=to; // 该边的终点
edge[cnt].w=w; // 权值
edge[cnt].next=head[from]; // (指向head[from]后,head[from]又指向了自己)
head[from]=cnt++; // 以from结点为起点的边在edge数组中存储的下标
}
bool SPFA(int s) {//s为源点
init();//初始化涉及参数n,所以必须放在输入n之后!!!!
queue<int>Q;
Q.push(s);// 源点进队
cir[s]++;
vis[s]=1;
dis[s]=0;
while(!Q.empty()) {
int start=Q.front();
Q.pop();
vis[start]=0; // 在SPFA中这儿需要改为0,因为每个节点需要重复进队
for(int i=head[start]; ~i; i=edge[i].next) { //取出start结点在edge中的起始存储下标(当i=0,即edge[i].next为0,说明以start节点为起始点的边全部访问完)
int w=edge[i].w;
int end=edge[i].to;
if(dis[end]>dis[start]+w) { // 松弛操作
dis[end]=dis[start]+w;
if(!vis[end]) { // 末端点不在队列
Q.push(end); //加入队列
vis[end]=1;
cir[end]++;
if(cir[end]>=n) return true;//存在负环
}
}
}
}
return false;
}
int main() {
int F;
scanf("%d",&F);
while(F--) {
int q;
scanf(" %d%d",&n,&m);
for(int i=1; i<=m; i++) {
int from,to,w;
scanf(" %d%d%d",&from,&to,&w);
addEdge(from,to,w);
}
bool t=SPFA(1);
if(t) printf("YES\n");
else printf("NO\n");
}
return 0;
}
多源有负权最短路+判断负环
采用floyd求解
时间复杂度N^3
模板
bool floyd() {
for(int k=1; k<=n; k++) //k必须放前面,否则比如说k=3、4,i=1,j=2,那就只有1->3->2,1->4->2,而1->3->4->2丢了
//f[i][k](k-1)+f[k][j](k-1)<=f[i][j](k),括号表示中转点你要丢掉k-1这一维度并保证无后效性,必须把k-1下所有情况都考虑到,所以k必须放到最外层
for(int i=1; i<=n; i++) //先丢掉哪一维度,哪一维度套外面
for(int j=1; j<=n; j++)
if(dis[i][k]!=INF && dis[k][j]!=INF && dis[i][k]+dis[k][j]<dis[i][j]) {
dis[i][j]=dis[i][k]+dis[k][j];//dp找到更短路径
}
for(int i=1;i<=n;i++) if(dis[i][i]<0) return 1;//有负环
return 0;//无负环
}