模板汇总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 }
View Code

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 }
View Code 

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 }
View Code

⑤一些奇怪的东西— —所谓的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 }
View Code
 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 }
View Code

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 }
View Code

 

posted @ 2018-09-18 12:02  Speranza_Leaf  阅读(174)  评论(0)    收藏  举报