模板彙總2.1_圖論1

1A.最小生成樹之Kruskal算法

①Kruskal算法能幹啥及它的原理和時/空間複雜度node

Kruskal算法用於構造最小生成樹,它是一個基於貪心思想的算法ios

咱們創建一個$O(n)$的並查集和一個$O(m)$的結構體,每一個結構體存儲一對點和點之間的邊權,而後按邊權從小到大排序,順次取每一對點。若是兩點未被併入一個集合裏,就把它們並起來,而後就作完了。c++

順便說一句,(因而可知,)Kruskal算法較適用於稀疏圖。算法

時間複雜度:$O(mlog$ $m)$ide

②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算法**。spa

因而可知Prim算法**比較適合於稠密圖**code

時間複雜度:採用鄰接表存儲,堆優化後能夠達到$O(mlog$ $n)$blog

②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
相關文章
相關標籤/搜索