模板汇总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 struct a{int n1,n2,val;}mst[m];
 5 int n,m,ans,t1,t2;
 6 int aset[n];
 7 bool cmp(a x,a y)//按边权从小到大排序
 8 {
 9     return x.val<y.val;
10 }
11 int find(int x)
12 {
13     return (aset[x]==x)?x:aset[x]=find(aset[x]);
14 }
15 int main()
16 {
17     cin>>n>>m;
18     for(int i=1;i<=m;i++)
19         cin>>mst[i].n1>>mst[i].n2>>mst[i].val;   
20     for(int i=1;i<=n;i++) aset[i]=i;
21     sort(mst+1,mst+1+m,cmp);//排序
22     for(int i=1;i<=m;i++)
23         {
24             int t1=find(mst[i].n1),t2=find(mst[i].n2);
25             if(t1!=t2) aset[t1]=t2,ans+=mst[i].val;//合并 
26         }
27     cout<<ans;
28     return 0;
29 }
View Code

1B.最小生成树之Prim算法(+堆优化)


①Prim算法能干啥及它的原理和时/空间复杂度

Prim当然也是用来构造最小生成树啦,它是一个基于搜索思想的算法。Prim算法先**将随意的一个点作为起始点,然后以类似BFS的方式进行扩展,以新扫到的边更新已选边**。我们需要用**边权建立一个小根堆进行Prim算法**。

由此可见Prim算法**比较适合于稠密图**

时间复杂度:采用邻接表存储,堆优化后可以达到$O(mlog$ $n)$

(正在以Prim算法构造最小生成树↓)


②Prim的具体实现

 1 #include<queue>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 struct a{int nxt,val;};
 6 const int N=10005,M=100005;
 7 int p[N],noww[2*M],goal[2*M],val[2*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.单源最短路径之SPFA


①SPFA能干啥

SPFA是一个由Bellman-Ford算法优化而来的最短路算法,用于求由单点出发,到达其他各点的最短路径长度,适用范围较广,以其为基础还可以判断一张图是否存在负环。

![](https://cdn.luogu.org/upload/pic/16680.png )

从度娘那里抱来的图↑~~(这图好像和SPFA关系不大,只是展示了最短路)~~

②SPFA的原理

通常SPFA以邻接表存图,以一个队列存储等待进行松弛的各个点,队列中起初只有一个起点,利用三角形不等式进行松弛操作,直到队列为空。在这个过程中一个节点可能会入/出队多次,即使存在负权边,也能正常求解。

初始化:$dis[1]=0$,$dis[oth]=INF$,$judge[1]=true$,$judge[oth]=false$,点1入队

③SPFA的时间复杂度

时间复杂度:$O(???)$,准确说来是$O(km)$,k是由边权关系确定的一个常数,与图本身与起点有关,所以说SPFA的时间复杂度为$O(???)$。它也(为什么要说也)**较为适用于稀疏图**,在稠密图和特殊构造的图上,SPFA最惨的话会被卡成$O(nm)$。

既然说起了SPFA的时间复杂度,那就再扯几句。SPFA其实是Shortest Path Fast Algorithm的缩写,但是听说因为玄学的复杂度和玄学复杂度的证明的%^&* ,国际上一般不承认SPFA算法,而是称它为“队列优化的Bellman-Ford算法”(23333)。

④SPFA的具体实现

```cpp
SPFA求单源最短路径
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dis[n],p[n],noww[m],goal[m],val[m];
int N,M,S,cnt,t1,t2,t3;
bool inq[n];
queue<int> qs;
void link(int f,int t,int v)
{
noww[++cnt]=p[f],p[f]=cnt;
goal[cnt]=t,val[cnt]=v;
}
void setit(int s)
{
memset(dis,0x3f,sizeof dis);
qs.push(S);dis[s]=0;inq[s]=true;
}
void SPFA()
{
while(!qs.empty())
{
int tn=qs.front();
qs.pop(),inq[tn]=false;
for(int i=p[tn];i;i=noww[i])
if(dis[goal[i]]>dis[tn]+val[i])
{
dis[goal[i]]=dis[tn]+val[i];
if(!inq[goal[i]])
inq[goal[i]]=true,qs.push(goal[i]);
}

}
}
int main()
{
scanf("%d%d%d",&N,&M,&S);
for(int i=1;i<=M;i++)
scanf("%d%d%d",&t1,&t2,&t3),link(t1,t2,t3);
setit(S);SPFA();
for(int i=1;i<=N;i++)
printf("%d ",dis[i]);
return 0;
}
```
⑤一些奇怪的东西— —SPFA的“优化”

SPFA有一个相对常见优化,叫做SLF(Small Label First),它基于双端队列(可使用<queue>里的std::deque或者手写)。在每次松弛后,将$dis[temp]$与队首节点的$dis$作比较,若$dis[temp]$较小,将其从队头入队,否则从队尾入队。

还有个叫做LLL(Large Label Last)的优化,原理差不多,看字面意思就能懂吧= =

代码即是将原代码中的
```cpp
if(!inq[goal[i]])
{
inq[goal[i]]=true;
qs.push(goal[i]);
}
```
改为
```cpp
if(!inq[goal[i]])
{
if(!q.empty()&dis[goal[i]]<dis[q.front()])
q.push_front(goal[i]);
else
q.push_back(goal[i]);
inq[goal[i]]=true;
}
```

可是这么明显的优化为什么很少听人说呢?因为这是个假的优化,本来SPFA在构造数据下能被卡成$O(nm)$,用了上述两种优化或许能让你过掉卡SPFA的数据,但是......

这个优化也能被卡,而且会被丧心病狂地卡成$O(2^n)$......

所以没有负权边时老老实实写堆优化下的迪杰斯特拉算法,不要老是想什么骚操作=。=,毕竟

![](https://cdn.luogu.org/upload/pic/26221.png )
(NOI 2018讲解现场,不要在意二重存在的luogu水印=。=)

哦你问什么是堆优化下的迪杰斯特拉算法?往下看就知道了quq

2A*.基于SPFA判负环 以及 基于深度优先思想判负环
--

①原理

$_1$基于SPFA判负环的原理

根据SPFA的原理,显然在图中存在负环时,SPFA会将这个负环上的节点不断循环入队。而一张图若存在最短路则每个点至多入队$n$次(显),所以每个点记录一个入队次数,超过$n$次即存在负环(据lyd学长说记录边的次数会更快),最差情况下复杂度为$O(n^2)$
![](https://cdn.luogu.org/upload/pic/16708.png )

自己画了张~~奇丑无比~~的图,负环用绿色的边标出↑

$_2$基于深度优先思想判负环的原理

基本与DFS相同,我们可以想象出递归时的那个栈。判断时加上一个初始为假的判断标志$flag$,在进行松弛时,如果这个点已经入栈或者$flag$为真,标记$flag$为真,回溯即可。虽然是指数复杂度,但是随机数据下跑的飞快,一般没人卡这个(不过luogu新数据卡了,可真是有心。。。)

另:个人测试下,如果你跑深度优先的话,$dis$初值为$0$每个点跑一次比加超级源点再跑超级源点要快。。。

②具体实现
```cpp
//深度优先
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int p[n],noww[m],goal[m],val[m],dis[n];
int n,m,typ,t1,t2,t3,cnt,T;
bool ins[n];
bool found;
void link(int f,int t,int v)
{
noww[++cnt]=p[f],p[f]=cnt;
goal[cnt]=t,val[cnt]=v;
}
void gtmd()
{
memset(ins,false,sizeof ins);
memset(dis,0,sizeof dis);
memset(p,0,sizeof p);
cnt=0;found=false;
}
void DF_SPFA(int nde)
{
ins[nde]=true;
for(int i=p[nde];i;i=noww[i])
if(dis[goal[i]]>dis[nde]+val[i])
{
if(ins[goal[i]]||found)
{
found=true;
return ;
}
dis[goal[i]]=dis[nde]+val[i];
DF_SPFA(goal[i]);
}
ins[nde]=false;
return ;
}
int main ()
{
scanf("%d",&T);
while(T--)
{
gtmd();
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&t1,&t2,&t3);
link(t1,t2,t3);if(t3>=0) link(t2,t1,t3);
}
for(int i=1;i<=n;i++)
if(!found)
DF_SPFA(i);
else
break;
printf(found?"Ye5\n":"N0\n");
}
return 0;
}
```

```cpp
//SPFA改造
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dis[n],p[n],noww[n],goal[n],val[m],xnt[n];
int n,m,t1,t2,t3,cnt,T;
bool inq[n];
queue<int> qs;
void link(int f,int t,int v)
{
noww[++cnt]=p[f],p[f]=cnt;
goal[cnt]=t,val[cnt]=v;
}
void gtmd()
{
memset(p,0,sizeof p);
memset(xnt,0,sizeof xnt);
memset(dis,0x3f,sizeof dis);
memset(inq,false,sizeof inq);
while(!qs.empty()) qs.pop();
qs.push(1);cnt=0;
inq[1]=true,dis[1]=0;
}
bool SPFA()
{
while(!qs.empty())
{
int tn=qs.front();inq[tn]=false;qs.pop();
for(int i=p[tn];i;i=noww[i])
{
int tp=goal[i],tv=val[i];
if(dis[tp]>dis[tn]+tv)
{
dis[tp]=dis[tn]+tv;
if(!inq[tp])
{
if(++xnt[tp]>n) return true;
inq[tp]=true,qs.push(tp);
}
}
}

}
return false;
}
int main ()
{
scanf("%d",&T);
while(T--)
{
gtmd();
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&t1,&t2,&t3);
link(t1,t2,t3);if(t3>=0) link(t2,t1,t3);
}
printf(SPFA()?"YE5\n":"N0\n");
}
return 0;
}
```

2B.Dijkstra(heap)
--

①Dijkstra能干啥

Dijkstra也是一种求最短路的算法,缺点是不能处理负权边,但是其复杂度稳定,卡不掉

②Dijkstra的原理

![](https://cdn.luogu.org/upload/pic/21235.png )

↑手绘Dijkstra,红点->终点,绿点->起点,蓝点->正在决策的点,灰点->已经决策完的点,白点->尚未决策的点

Dijkstra朴素做法是枚举每个点及和这个点相连的点进行进行扩展,而扩展过的点就不会再扩展,因而无法处理负权边,复杂度为$O(n^2)$,比较差。通常会用一个堆进行优化:每次扩展将目标点和边权加入一个小根堆,在扩展时取堆顶的那个点进行新一轮扩展,复杂度为$(mlog n)$。

③Dijkstra的时间复杂度

时间复杂度:朴素$O(n^2)$ 堆优化下$O(mlogn)$

④Dijkstra的具体实现
```
#include<queue>
#include<cstdio>
using namespace std;
struct SP{int value,node;};
bool operator <(const SP &a,const SP &b) {return a.value>b.value;}//堆优化
int p[n],noww[m],goal[m],val[m],dis[n];
bool inh[n];
priority_queue<SP> qs;
int N,M,S,cnt,t1,t2,t3;
void link(int f,int t,int v)
{
noww[++cnt]=p[f],p[f]=cnt;
goal[cnt]=t;val[cnt]=v;
}
void Dijkstra()
{
while(!qs.empty())
{
SP tmp=qs.top();qs.pop();
int tn=tmp.node; //取点
if(!inh[tn])
{
inh[tn]=true;
for(int i=p[tn];i;i=noww[i])//扩展
{
int tp=goal[i],tv=val[i];
if(dis[tp]>dis[tn]+tv)
{
dis[tp]=dis[tn]+tv;
qs.push((SP){dis[tp],tp});
}
}
}
}
}
int main ()
{
scanf("%d%d%d",&N,&M,&S);
for(int i=1;i<=M;i++)
scanf("%d%d%d",&t1,&t2,&t3),link(t1,t2,t3);
memset(dis,0x3f,sizeof dis);
dis[S]=0;qs.push((SP){0,S});
Dijkstra();
for(int i=1;i<=N;i++)
printf("%d ",dis[i]);
return 0;
}
```

猜你喜欢

转载自www.cnblogs.com/ydnhaha/p/9668141.html