模板汇总2.1_图论1
1A.最小生成树之Kruskal算法
①Kruskal算法能干啥及它的原理和时/空间复杂度
Kruskal算法用于构造最小生成树,它是一个基于贪心思想的算法
我们建立一个$O(n)$的并查集和一个$O(m)$的结构体,每个结构体存储一对点和点之间的边权,然后按边权从小到大排序,顺次取每一对点。如果两点未被并入一个集合里,就把它们并起来,然后就做完了。
顺便说一句,(由此可见,)Kruskal算法较适用于稀疏图。
时间复杂度:$O(mlog$ $m)$
②Kruskal算法求最小生成树的具体实现
1 #include<iostream> 2 #include<algorithm> 3 using namespace std; 4 const int N=10005,M=100005; 5 struct a{int n1,n2,val;}mst[M]; 6 int n,m,ans,t1,t2; 7 int aset[N]; 8 bool cmp(a x,a y)//按边权从小到大排序 9 { 10 return x.val<y.val; 11 } 12 int find(int x) 13 { 14 return (aset[x]==x)?x:aset[x]=find(aset[x]); 15 } 16 int main() 17 { 18 cin>>n>>m; 19 for(int i=1;i<=m;i++) 20 cin>>mst[i].n1>>mst[i].n2>>mst[i].val; 21 for(int i=1;i<=n;i++) aset[i]=i; 22 sort(mst+1,mst+1+m,cmp);//排序 23 for(int i=1;i<=m;i++) 24 { 25 int t1=find(mst[i].n1),t2=find(mst[i].n2); 26 if(t1!=t2) aset[t1]=t2,ans+=mst[i].val;//合并 27 } 28 cout<<ans; 29 return 0; 30 }
1B.最小生成树之Prim算法(+堆优化)
①Prim算法能干啥及它的原理和时/空间复杂度
Prim当然也是用来构造最小生成树啦,它是一个基于搜索思想的算法。Prim算法先**将随意的一个点作为起始点,然后以类似BFS的方式进行扩展,以新扫到的边更新已选边**。我们需要用**边权建立一个小根堆进行Prim算法**。
由此可见Prim算法**比较适合于稠密图**
时间复杂度:采用邻接表存储,堆优化后可以达到$O(mlog$ $n)$
②Prim的具体实现
1 #include<queue> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 const int N=10005,M=100005; 6 struct a{int nxt,val;}; 7 int p[N],noww[M],goal[M],val[M],dis[N]; 8 int n,m,cnt,t1,t2,t3,c,tot; 9 bool inh[N]; 10 bool operator <(a x,a y) 11 { 12 return x.val>y.val; 13 }//边权小根堆 14 priority_queue<a> qs; 15 void link(int f,int t,int v) 16 { 17 noww[++cnt]=p[f],p[f]=cnt; 18 goal[cnt]=t,val[cnt]=v; 19 } 20 int main () 21 { 22 scanf("%d%d",&n,&m); 23 for(int i=1;i<=m;i++) 24 { 25 scanf("%d%d%d",&t1,&t2,&t3); 26 link(t1,t2,t3),link(t2,t1,t3); 27 } 28 memset(dis,0x3f,sizeof dis); 29 int tn=1;dis[tn]=0,inh[tn]=true;//随便找个初始点 30 for(int i=p[tn];i;i=noww[i]) 31 qs.push((a){goal[i],val[i]}),dis[goal[i]]=val[i];//把边一股脑丢进去 32 while(!qs.empty()) 33 { 34 a tmp=qs.top();qs.pop(); 35 if(!judge[tmp.nxt])//没选过 36 { 37 int tp=tmp.nxt,tv=tmp.val; 38 tot+=tv,c++,judge[tp]=true; 39 for(int i=p[tp];i;i=noww[i])//更新 40 if(dis[goal[i]]>val[i]) 41 dis[goal[i]]=val[i],qs.push((a){goal[i],val[i]}); 42 } 43 } 44 printf("%d",tot); 45 return 0; 46 }
2A.单源最短路径之Bellman_Ford(queue)
①Bellman_Ford能干啥
Bellman-Ford是一个单源最短路算法,适用于各种类型的图。不过在国内,我们一般给它加上一个队列优化(并不能降低其理论复杂度上界,但是会让它跑不满,然后在一些图上就跑的很快),在平时我们都是这么写的(指队列优化),然后叫它"SPFA(Shortest Path Fast Algorithm)"。
(至于是不是真的"Fast”就不一定了)
②Bellman_Ford的原理
以一个队列存储等待进行松弛的各个点,队列中起初只有一个起点,利用三角形不等式进行松弛操作,直到队列为空。在这个过程中一个节点可能会入/出队多次,即使存在负权边,也能正常求解。初始化即将起点入队。
③Bellman_Ford的时间复杂度
时间复杂度:$O(nm)$,较为适用于稀疏图(稀疏图上跑得很不满),但是复杂度上界仍然是$O(nm)$,出题人只要想卡就能卡,所以在没有负权边时看好数据范围,考虑使用下文的Dijkstra。
什么?有负权还$O(nm)$跑不过去?那就是出题人的问题了,强行Bellman_Ford吧
(所以国际上一般不承认所谓的SPFA算法,而是称它为“队列优化的Bellman-Ford算法”)。
④Bellman_Ford的具体实现
1 #include<queue> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int N=10005,M=100005; 7 int dis[N],p[N],noww[M],goal[M],val[M]; 8 int N,M,S,cnt,t1,t2,t3; 9 bool inq[N]; 10 queue<int> qs; 11 void link(int f,int t,int v) 12 { 13 noww[++cnt]=p[f],p[f]=cnt; 14 goal[cnt]=t,val[cnt]=v; 15 } 16 void setit(int s) 17 { 18 memset(dis,0x3f,sizeof dis); 19 qs.push(S);dis[s]=0;inq[s]=true; 20 } 21 void SPFA() 22 { 23 while(!qs.empty()) 24 { 25 int tn=qs.front(); 26 qs.pop(),inq[tn]=false; 27 for(int i=p[tn];i;i=noww[i]) 28 if(dis[goal[i]]>dis[tn]+val[i]) 29 { 30 dis[goal[i]]=dis[tn]+val[i]; 31 if(!inq[goal[i]]) 32 inq[goal[i]]=true,qs.push(goal[i]); 33 } 34 35 } 36 } 37 int main() 38 { 39 scanf("%d%d%d",&N,&M,&S); 40 for(int i=1;i<=M;i++) 41 scanf("%d%d%d",&t1,&t2,&t3),link(t1,t2,t3); 42 setit(S);SPFA(); 43 for(int i=1;i<=N;i++) 44 printf("%d ",dis[i]); 45 return 0; 46 }
⑤一些奇怪的东西— —所谓的SPFA的“优化”
SPFA有一个相对常见优化,叫做SLF(Small Label First),它基于双端队列(可使用<queue>里的std::deque或者手写)。在每次松弛后,将$dis[temp]$与队首节点的$dis$作比较,若$dis[temp]$较小,将其从队头入队,否则从队尾入队。
还有个叫做LLL(Large Label Last)的优化,原理差不多,看字面意思就能懂吧= =
SLF的代码即是将原代码中的
1 if(!inq[goal[i]]) 2 { 3 inq[goal[i]]=true; 4 qs.push(goal[i]); 5 }
改为
1 if(!inq[goal[i]]) 2 { 3 if(!q.empty()&dis[goal[i]]<dis[q.front()]) 4 q.push_front(goal[i]); 5 else 6 q.push_back(goal[i]); 7 inq[goal[i]]=true; 8 }
可是这么明显的优化为什么很少听人说呢?因为这是个假的优化,本来SPFA在的复杂度就是$O(nm)$,这两个优化本质上不会降低复杂度(可能能过掉一些弱数据),同时在有负权边的图上会被丧心病狂地卡成$O(2^n)$......
所以没有负权边时老老实实写堆优化下的迪杰斯特拉算法,有负权边就老老实实写正常的SPFA,不要老是想什么骚操作,毕竟

(NOI 2018讲解现场,不要在意二重存在的luogu水印=。=)
哦你问什么是堆优化下的迪杰斯特拉算法?往下看就知道了quq
2A*.基于Bellman_Ford判负环 以及 基于深度优先思想判负环
①原理
$_1$基于Bellman_Ford判负环的原理
根据Bellman_Ford的原理,显然在图中存在负环时,SPFA会将这个负环上的节点不断循环入队。而一张图若存在最短路则每个点至多入队$n$次(显),所以每个点记录一个入队次数,超过$n$次即存在负环(据lyd学长说记录边的次数会更快),最差情况下复杂度为$O(n^2)$
$_2$基于深度优先思想判负环的原理
基本与DFS相同,我们可以想象出递归时的那个栈。判断时加上一个初始为假的判断标志$flag$,在进行松弛时,如果这个点已经入栈或者$flag$为真,标记$flag$为真,回溯即可。虽然是指数复杂度,但是随机数据下跑的飞快,一般没人卡这个(不过luogu新数据卡了,可真是有心。。。)
另:个人测试下,如果你跑深度优先的话,$dis$初值为$0$每个点跑一次比加超级源点再跑超级源点要快。。。
②具体实现
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=1005,M=10005; 6 int p[N],noww[M],goal[M],val[M],dis[N]; 7 int n,m,typ,t1,t2,t3,cnt,T; 8 bool ins[N]; 9 bool found; 10 void link(int f,int t,int v) 11 { 12 noww[++cnt]=p[f],p[f]=cnt; 13 goal[cnt]=t,val[cnt]=v; 14 } 15 void gtmd() 16 { 17 memset(ins,false,sizeof ins); 18 memset(dis,0,sizeof dis); 19 memset(p,0,sizeof p); 20 cnt=0;found=false; 21 } 22 void DF_SPFA(int nde) 23 { 24 ins[nde]=true; 25 for(int i=p[nde];i;i=noww[i]) 26 if(dis[goal[i]]>dis[nde]+val[i]) 27 { 28 if(ins[goal[i]]||found) 29 { 30 found=true; 31 return ; 32 } 33 dis[goal[i]]=dis[nde]+val[i]; 34 DF_SPFA(goal[i]); 35 } 36 ins[nde]=false; 37 return ; 38 } 39 int main () 40 { 41 scanf("%d",&T); 42 while(T--) 43 { 44 gtmd(); 45 scanf("%d%d",&n,&m); 46 for(int i=1;i<=m;i++) 47 { 48 scanf("%d%d%d",&t1,&t2,&t3); 49 link(t1,t2,t3);if(t3>=0) link(t2,t1,t3); 50 } 51 for(int i=1;i<=n;i++) 52 if(!found) 53 DF_SPFA(i); 54 else 55 break; 56 printf(found?"Ye5\n":"N0\n"); 57 } 58 return 0; 59 }
1 //SPFA改造 2 #include<queue> 3 #include<cstdio> 4 #include<cstring> 5 #include<algorithm> 6 using namespace std; 7 const int N=1005,M=10005; 8 int dis[N],p[N],noww[M],goal[M],val[M],xnt[N]; 9 int n,m,t1,t2,t3,cnt,T; 10 bool inq[N]; 11 queue<int> qs; 12 void link(int f,int t,int v) 13 { 14 noww[++cnt]=p[f],p[f]=cnt; 15 goal[cnt]=t,val[cnt]=v; 16 } 17 void gtmd() 18 { 19 memset(p,0,sizeof p); 20 memset(xnt,0,sizeof xnt); 21 memset(dis,0x3f,sizeof dis); 22 memset(inq,false,sizeof inq); 23 while(!qs.empty()) qs.pop(); 24 qs.push(1);cnt=0; 25 inq[1]=true,dis[1]=0; 26 } 27 bool SPFA() 28 { 29 while(!qs.empty()) 30 { 31 int tn=qs.front();inq[tn]=false;qs.pop(); 32 for(int i=p[tn];i;i=noww[i]) 33 { 34 int tp=goal[i],tv=val[i]; 35 if(dis[tp]>dis[tn]+tv) 36 { 37 dis[tp]=dis[tn]+tv; 38 if(!inq[tp]) 39 { 40 if(++xnt[tp]>n) return true; 41 inq[tp]=true,qs.push(tp); 42 } 43 } 44 } 45 46 } 47 return false; 48 } 49 int main () 50 { 51 scanf("%d",&T); 52 while(T--) 53 { 54 gtmd(); 55 scanf("%d%d",&n,&m); 56 for(int i=1;i<=m;i++) 57 { 58 scanf("%d%d%d",&t1,&t2,&t3); 59 link(t1,t2,t3);if(t3>=0) link(t2,t1,t3); 60 } 61 printf(SPFA()?"YE5\n":"N0\n"); 62 } 63 return 0; 64 }
2B.Dijkstra(heap)
①Dijkstra能干啥
Dijkstra也是一种求最短路的算法,缺点是不能处理负权边,但是其复杂度稳定,卡不掉
②Dijkstra的原理
Dijkstra朴素做法是枚举每个点及和这个点相连的点进行进行扩展,而扩展过的点就不会再扩展,因而无法处理负权边,复杂度为$O(n^2)$,比较差。通常会用一个堆进行优化:每次扩展将目标点和边权加入一个小根堆,在扩展时取堆顶的那个点进行新一轮扩展,复杂度为$(mlog n)$。
③Dijkstra的时间复杂度
时间复杂度:朴素$O(n^2)$ 堆优化下$O(mlogn)$
④Dijkstra的具体实现
1 #include<queue> 2 #include<cstdio> 3 using namespace std; 4 struct SP{int value,node;}; 5 bool operator <(const SP &a,const SP &b) {return a.value>b.value;}//堆优化 6 int p[N],noww[M],goal[M],val[M],dis[N]; 7 bool inh[N]; 8 priority_queue<SP> qs; 9 int N,M,S,cnt,t1,t2,t3; 10 void link(int f,int t,int v) 11 { 12 noww[++cnt]=p[f],p[f]=cnt; 13 goal[cnt]=t;val[cnt]=v; 14 } 15 void Dijkstra() 16 { 17 while(!qs.empty()) 18 { 19 SP tmp=qs.top();qs.pop(); 20 int tn=tmp.node; //取点 21 if(!inh[tn]) 22 { 23 inh[tn]=true; 24 for(int i=p[tn];i;i=noww[i])//扩展 25 { 26 int tp=goal[i],tv=val[i]; 27 if(dis[tp]>dis[tn]+tv) 28 { 29 dis[tp]=dis[tn]+tv; 30 qs.push((SP){dis[tp],tp}); 31 } 32 } 33 } 34 } 35 } 36 int main () 37 { 38 scanf("%d%d%d",&N,&M,&S); 39 for(int i=1;i<=M;i++) 40 scanf("%d%d%d",&t1,&t2,&t3),link(t1,t2,t3); 41 memset(dis,0x3f,sizeof dis); 42 dis[S]=0;qs.push((SP){0,S}); 43 Dijkstra(); 44 for(int i=1;i<=N;i++) 45 printf("%d ",dis[i]); 46 return 0; 47 }

浙公网安备 33010602011771号