2017清北學堂(提升組精英班)集訓筆記——圖論

咱們進入一個新的模塊——圖論!html

emmmmm這個專題更出來可能有點慢別介意,緣由是要劃的圖和要給代碼加的註釋比較多,更重要的就是。。。這幾個晚上我在追劇!!咱們的少年時代超級超級超級好看,劇情很燃啊!!咳咳,好吧下面迴歸正題。node

1、圖的存儲:c++

一、鄰接矩陣:算法

假設有n個節點,創建一個n×n的矩陣,第i號節點能到達第j號節點就將[i][j]標記爲1(有權值標記爲權值),數組

樣例以下圖:函數

 1 /*無向圖,無權值*/
 2 int a[MAXN][MAXN];//鄰接矩陣 
 3 int x,y;//兩座城市 
 4 for(int i=1;i<=n;i++)
 5 {
 6     for(int j=1;j<=n;j++)
 7     {
 8         scanf("%d%d",&x,&y);//能到達,互相標記爲1 
 9         a[x][y]=1;
10         a[y][x]=1;
11     }
12 }
13 /*無向圖,有權值*/
14 int a[MAXN][MAXN];//鄰接矩陣 
15 int x,y,w;//兩座城市,路徑長度 
16 for(int i=1;i<=n;i++)
17 {
18     for(int j=1;j<=n;j++)
19     {
20         scanf("%d%d%d",&x,&y,&w);//能到達,互相標記爲權值w 
21         a[x][y]=w;
22         a[y][x]=w;
23     }
24 }
25 /*有向圖,無權值*/
26 int a[MAXN][MAXN];//鄰接矩陣 
27 int x,y;//兩座城市 
28 for(int i=1;i<=n;i++)
29 {
30     for(int j=1;j<=n;j++)
31     {
32         scanf("%d%d",&x,&y);//能到達,僅僅是x到y標記爲1 
33         a[x][y]=1;
34     }
35 }
36 /*有向圖,有權值*/
37 int a[MAXN][MAXN];//鄰接矩陣 
38 int x,y,w;//兩座城市,路徑長度 
39 for(int i=1;i<=n;i++)
40 {
41     for(int j=1;j<=n;j++)
42     {
43         scanf("%d%d%d",&x,&y,&w);//能到達,僅僅是x到y標記爲權值w 
44         a[x][y]=w;
45     }
46 }

鄰接矩陣很方便,可是在n過大或者爲稀疏圖時,就會很損耗時空,不建議使用!優化

2.鄰接表:網站

鄰接表是一個二維容器,第一維描述某個點,第二維描述這個點所對應的邊集們。spa

鄰接表由表頭point,鏈點構成,以下圖是一個簡單無向圖構成的鄰接表:.net

咱們能夠用指針來建立鏈表,固然,這是很複雜也很麻煩的事情,下面來介紹一種用數組模擬鏈表的方法:

 1 //有向圖鄰接表存儲 
 2 const int N=1005;
 3 const int M=10050;
 4 int point[N]={0};//i節點所對應鏈表起始位置(表頭) 
 5 int to[M]={0};
 6 int next[M]={0};//i節點下一個所指的節點 
 7 int cc=0;//計數器(表示第幾條邊) 
 8 void AddEdge(int x,int y)//節點x到y
 9 {
10     cc++;
11     to[cc]=y;
12     next[cc]=point[x];
13     point[x]=cc;
14 }
15 void find(int x)
16 {
17     int now=point[x];
18     while(now)
19     {
20         printf("%d\n",to[now]);
21         now=next[now];
22     }
23 }
24 int main()
25 {
26     
27 }

具體的過程我也不是很懂怎麼描述,反正若是要增強記憶的話能夠用我所給的例子模擬一下point[],to[],next[],而後再調用函數find(x)來輸出x這個節點能到的點,大概就能YY到數組是怎麼存儲鄰接表的了。

仍是不理解的話,推一個blog,這裏面說的和我這裏給出的思路很類似:http://developer.51cto.com/art/201404/435072.htm

2、樹的遍歷:

1.BFS:運用隊列,一開始隊列中有一個點, 將一個點出隊,將它的子結點全都入隊。

算法會在遍歷完一棵樹中每一層的每一個結點以後,纔會轉到下一層繼續,在這一基礎上,隊列將會對算法起到很大的幫助:

 1 //廣度優先搜索
 2 void BreadthFirstSearch(BitNode *root)
 3 {
 4     queue<BitNode*> nodeQueue;
 5     nodeQueue.push(root);//將根節點壓入隊列 
 6     while (!nodeQueue.empty())//隊列不爲空,繼續壓入隊列 
 7     {
 8         BitNode *node = nodeQueue.front();
 9         nodeQueue.pop();//彈出根節點 
10         if (node->left)//左兒子不爲空 
11         {
12             nodeQueue.push(node->left);//壓入隊列 
13         }
14         if (node->right)//右兒子不爲空 
15         {
16             nodeQueue.push(node->right);//壓入隊列 
17         }
18     }
19 }

2.DFS:運用棧,遞歸到一個點時,依次遞歸它的子結點。

還能夠利用堆棧的先進後出的特色,現將右子樹壓棧,再將左子樹壓棧,這樣左子樹就位於棧頂,能夠保證結點的左子樹先與右子樹被遍歷:

 1 //深度優先搜索
 2 //利用棧,現將右子樹壓棧再將左子樹壓棧
 3 void DepthFirstSearch(BitNode *root)
 4 {
 5     stack<BitNode*> nodeStack;
 6     nodeStack.push(root);//將根節點壓棧 
 7     while (!nodeStack.empty())//棧不爲空,繼續壓棧 
 8     {
 9         BitNode *node = nodeStack.top();//引用棧頂 
10         cout << node->data << ' ';
11         nodeStack.pop();//彈出根節點 
12         if (node->right)//優先遍歷右子樹 
13         {
14             nodeStack.push(node->right);
15         }
16         if (node->left)
17         {
18             nodeStack.push(node->left);
19         }
20     }
21 }

3、無根樹變成有根樹:

  選擇一個點做爲根結點, 開始遍歷。

  遍歷到一個點時, 枚舉每一條鏈接它和另外一個點的邊。 若另外一個點不是它的父結點, 那就是它的子結點。 遞歸到子結點。

  咱們能夠更加形象的比喻爲:抓住一個點,把它拎起來構成一棵新的樹。

4、並查集:

這是我學OI這麼久以來以爲性價比最高的算法(簡單又實用啊!!),用來處理不相交合並和查詢問題。

給你們推個超超超超級易懂的blog,保證一看就懂,這裏我就再也不詳解了:http://blog.csdn.net/dellaserss/article/details/7724401

5、最小生成樹:

1.Prim算法(適用於稠密圖):

算法描述

1).輸入:一個加權連通圖,其中頂點集合爲V,邊集合爲E;
2).初始化:Vnew = {x},其中x爲集合V中的任一節點(起始點),Enew = {},爲空;
3).重複下列操做,直到Vnew = V:
a.在集合E中選取權值最小的邊<u, v>,其中u爲集合Vnew中的元素,而v不在Vnew集合當中,而且v∈V(若是存在有多條知足前述條件即具備相同權值的邊,則可任意選取其中之一);
b.將v加入集合Vnew中,將<u, v>邊加入集合Enew中;
4).輸出:使用集合Vnew和Enew來描述所獲得的最小生成樹。
 1 #include<stdio.h>//普里姆算法
 2 const int N=1050;
 3 const int M=10050;
 4 struct Edge//定義圖類型結構體,a到b權值爲c
 5 {
 6     int a,b,c;
 7 }edge[M];
 8 int n,m;//n個點,m條邊 
 9 bool black[N];//染黑這個點,表示這個點已經被選過了 
10 int ans=0;//最小生成樹權值和
11 int main()
12 {
13     int i,j,k;
14     scanf("%d%d",&n,&m);
15     for(i=1;i<=m;i++)
16     scanf("%d%d%d",&edge[i].a,&edge[i].b,&edge[i].c);
17     black[1]=1;//把第一個點染黑(從第一個點找起)
18     for(k=1;k<n;k++)
19     {
20         int mind,minz=123456789;
21         for(i=1;i<=m;i++)//開始! 
22         {
23             if(black[edge[i].a]!=black[edge[i].b]&&edge[i].c<minz)//若是這個點未被找過而且權值比當前最優值還要小,更新之 
24             {
25                 mind=i;//記錄當前最優勢 
26                 minz=edge[i].c;//記錄當前最小邊權 
27             }
28         }
29         /*******************************************///將這最優勢納入 
30         ans+=minz;//答案加上 
31         black[edge[mind].a]=1;//染黑兩個節點 
32         black[edge[mind].b]=1;
33         /*******************************************/
34     }
35     printf("%d\n",ans);//輸出答案 
36     return 0;
37 }

2.kruskal算法(適用於稀疏圖):

算法描述

克魯斯卡爾算法從另外一途徑求網的最小生成樹。

假設連通網N=(V,{E}),則令最小生成樹的初始狀態爲只有n個頂點而無邊的非連通圖T=(V,{∮}),圖中每一個頂點自成一個連通份量。

在E中選擇代價最小的邊,若該邊依附的頂點落在T中不一樣的連通份量上,則將此邊加入到T中,不然捨去此邊而選擇下一條代價最小的邊。

依次類推,直至T中全部頂點都在同一連通份量上爲止。

 

 1 #include<cstdio>//克魯斯卡爾算法
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int N=1050;
 6 const int M=10050;
 7 struct Edge//定義圖類型結構體
 8 {
 9     int a,b,c;//a到b的權值爲c
10 }edge[M];
11 int fa[N];//父親數組
12 int n,m;//n個節點,m條邊
13 int ans=0;//最小生成樹權值和
14 bool cmp(Edge x,Edge y)//比較權值大小 
15 {
16     return (x.c<y.c);
17 }
18 int getf(int x)//尋找x的最原始祖先(並查集) 
19 {
20     if(fa[x]!=x)
21     fa[x]=getf(fa[x]);
22     return fa[x];//返回最原始祖先 
23 }
24 int main()
25 {
26     int i,j;
27     scanf("%d%d",&n,&m);
28     for(i=1;i<=m;i++)
29     scanf("%d%d%d",&edge[i].a,&edge[i].b,&edge[i].c);
30     sort(edge+1,edge+m+1,cmp);//從小到大排序邊數組 
31     for(i=1;i<=n;i++)
32     fa[i]=i;//初始值,每一個節點的父親就是本身 
33     for(i=1;i<=m;i++)
34     {
35         int a=edge[i].a;
36         int b=edge[i].b;
37         a=getf(a);//尋找a的最原始祖先 
38         b=getf(b);//尋找b的最原始祖先 
39         if(a!=b)//若是兩個的最終祖先不相同(不會構成迴路) 
40         {
41             ans+=edge[i].c;//加入 
42             fa[a]=b;//加入當前父親的兒子們中(合併並查集)
43         }
44     }
45     printf("%d\n",ans);
46     return 0; 
47 }

經典例題:繁忙的都市(Luogu 2330)

城市C是一個很是繁忙的大都市,城市中的道路十分的擁擠,因而市長決定對其中的道路進行改造。城市C的道路是這樣分佈的:城市中有n個交叉路口,有些交叉路口之間有道路相連,兩個交叉路口之間最多有一條道路相鏈接。這些道路是雙向的,且把全部的交叉路口直接或間接的鏈接起來了。每條道路都有一個分值,分值越小表示這個道路越繁忙,越須要進行改造。可是市政府的資金有限,市長但願進行改造的道路越少越好,因而他提出下面的要求:

1.改造的那些道路可以把全部的交叉路口直接或間接的連通起來。

2.在知足要求1的狀況下,改造的道路儘可能少。

3.在知足要求一、2的狀況下,改造的那些道路中分值最大的道路分值儘可能小。

任務:做爲市規劃局的你,應看成出最佳的決策,選擇那些道路應當被修建。

這題是經典的最小瓶頸生成樹問題只用邊權小於等於x的邊,看看能不能構成最小生成樹

在kruskal算法中,咱們已經對邊從小到大排過序了,因此只要用≤x的前若干條邊便可

3.最小生成樹計數問題:

題目:如今給出了一個簡單無向加權圖。你不知足於求出這個圖的最小生成樹,而但願知道這個圖中有多少個不一樣的最小生成樹(若是兩顆最小生成樹中至少有一條邊不一樣,則這兩個最小生成樹就是不一樣的)

解法:按邊權排序,先選小的,相同邊權的暴力求出有幾種方案,將邊按照權值大小排序,將權值相同的邊分到一組,統計下每組分別用了多少條邊。而後對於每一組進行dfs,判斷是否可以用這一組中的其餘邊達到相同的效果。最後把每一組的方案數相乘就是答案。 

換句話說:就是不一樣的最小生成樹方案,每種權值的邊的數量是肯定的,每種權值的邊的做用是肯定的, 排序之後先作一遍最小生成樹,得出每種權值的邊使用的數量x而後對於每一種權值的邊搜索,得出每一種權值的邊選擇方案。

 1 #include<cstdio>
 2 #include<algorithm>
 3 #define N 105
 4 #define M 1005
 5 #define MOD 31011
 6 using namespace std;
 7 struct node//定義圖類型結構體 
 8 {
 9     int a,b;//節點a,b 
10     int zhi;//a到b的權值 
11 }xu[M];
12 int n,m;
13 int fa[N];
14 int lian[N];
15 int ans=1;
16 int cmp(struct node x,struct node y)//從小到大排序函數 
17 {
18     return (x.zhi<y.zhi);
19 }
20 int getf(int x)
21 {
22     if(fa[x]!=x)
23         fa[x]=getf(fa[x]);
24     return(fa[x]);
25 }
26 int getlian(int x)
27 {
28     if(lian[x]==x)
29         return x;
30     return ( getlian(lian[x]) );
31 }
32 int dfs(int now,int end,int last)
33 {
34     if(now==end)
35     {
36         if(last==0)
37             return 1;
38         return 0;
39     }
40     int res=dfs(now+1,end,last);
41     int s=getlian(xu[now].a);
42     int t=getlian(xu[now].b);
43     if(s!=t)
44     {
45         lian[s]=t;
46         res+=dfs(now+1,end,last-1);
47         lian[s]=s;
48     }
49     return res;
50 }
51 int main()
52 {
53     int i,j,k;
54     int s,t;
55     int now;
56     int sum=0;
57     scanf("%d%d",&n,&m);
58     for(i=1;i<=n;i++)//初始化,每一個節點的父親就是本身 
59         fa[i]=i;
60     for(i=1;i<=m;i++)
61         scanf("%d%d%d",&xu[i].a,&xu[i].b,&xu[i].zhi);
62     sort(xu+1,xu+m+1,cmp);//從小到大排序邊數組 
63     for(i=1;i<=m;)
64     {
65         for(j=1;j<=n;j++)
66             lian[j]=j;
67         k=i;
68         while(i<=m&&xu[i].zhi==xu[k].zhi)
69         {
70             xu[i].a=getf(xu[i].a);
71             xu[i].b=getf(xu[i].b);
72             i++;
73         }
74         now=sum;
75         for(j=k;j<i;j++)
76         {
77             s=getf(xu[j].a);
78             t=getf(xu[j].b);
79             if(s!=t)
80             {
81                 sum++;
82                 fa[s]=t;
83             }
84         }
85         ans*=dfs(k,i,sum-now);
86         ans%=MOD;//防止溢出 
87     }
88     if(sum!=n-1)
89         ans=0;
90     printf("%d\n",ans);
91     return 0;
92 }

6、最短路徑:

1.Floyd算法(插點法):

經過一個圖的權值矩陣求出它的每兩點間的最短路徑(多源最短路)。

算法描述

一個十分暴力又經典的DP,假設i到j的路徑有兩種狀態:

①i和j直接有路徑相連:

②i和j間接聯通,中間有k號節點聯通:

假設dis[i][j]表示從i到j的最短路徑,對於存在的每一個節點k,咱們檢查一遍dis[i][k]+dis[k][j]。

 1 //Floyd算法,時間複雜度:O(n^3) 
 2 int dis[MAXN][MAXN];
 3 for(k=1;k<=n;k++)//枚舉 
 4 {
 5     for(i=1;i<=n;i++)
 6     {
 7         for(j=1;j<=n;j++)
 8         {
 9             dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);//DP 10         }
11     }
12 }

2.Dijkstra算法(無向圖,無負權邊):

算法描述

多源最短路!

a.初始時,S只包含源點,即S={v},v的距離爲0。U包含除v外的其餘頂點,即:U={其他頂點},若v與U中頂點u有邊,則<u,v>正常有權值,若u不是v的出邊鄰接點,則<u,v>權值爲∞。

b.從U中選取一個距離v最小的頂點k,把k,加入S中(該選定的距離就是v到k的最短路徑長度)。

c.以k爲新考慮的中間點,修改U中各頂點的距離;若從源點v到頂點u的距離(通過頂點k)比原來距離(不通過頂點k)短,則修改頂點u的距離值,修改後的距離值的頂點k的距離加上邊上的權。

d.重複步驟b和c直到全部頂點都包含在S中。

啊~上面的的亂七八糟的概念太難懂了,仍是舉個例子吧!以下圖!

咱們假設1號節點爲原點。

第一輪,咱們能夠算出2,3,4,5,6號節點到原點1的距離爲[7,9,∞,∞,14],∞表示無窮大(節點間沒法直接連通),取其中最小的7,就肯定了1->1的最短路徑爲0,1->2的最短路徑爲7,同時取最短路徑最小的2節點爲下一輪的前驅節點。

第二輪,取2節點爲前驅節點,按照 前驅節點到原點的最短距離 + 新節點到前驅節點的距離 來計算新的最短距離,能夠獲得3,4,5,6號節點到原點1的距離爲[17,22,∞,∞](新節點必須通過2號節點回到原點),這時候須要將新結果和上一輪計算的結果比較,3號節點:17>9,最短路徑仍然爲9;4號節點:22<∞,更新4號節點的最短路徑爲22,;5號節點:仍然不變爲∞;6號節點:14<∞,更新6號節點的最短路徑爲14。獲得本輪的最短距離爲[9,22,∞,14]1->3的最短路徑爲9,同時取最短路徑最小的3節點爲下一輪的前驅節點。

第三輪:同理上,以3號節點爲前驅節點,能夠獲得4,5,6號節點到原點1的距離爲[20,∞,11],根據最短路徑原則,和上一輪最短距離比較,刷新爲[20,∞,11]1->3->6的最短路徑爲11,同時取最短路徑最小的6節點爲下一輪的前驅節點。

第四輪:同理,獲得4,5號節點最短距離爲[20,20],這兩個值相等,運算結束,到達這兩個點的最短距離都是20,若是這兩個值不相等,還要進行第五輪運算!

 1 #include<cstdio>
 2 #include<cstring>
 3 const int N=100500;
 4 const int M=200500;
 5 int point[N]={0},to[M]={0},next[M]={0},len[M]={0},cc=0;
 6 int dis[N];//最短路長度,dis[i]表示第i號點到源點(1號點)的最短距離
 7 bool ever[N];//當前節點最短路有沒有肯定 
 8 int n,m; 
 9 void AddEdge(int x,int y,int z)//添加新的邊和節點:x到y邊長z
10 {
11     cc++;
12     next[cc]=point[x];
13     point[x]=cc;
14     to[cc]=y;
15     len[cc]=z;//len記錄x到y的邊長 
16 }
17 int main()
18 {
19     int i,j,k;
20     scanf("%d%d",&n,&m);
21     for(i=1;i<=m;i++)
22     {
23         int a,b,c;
24         scanf("%d%d%d",&a,&b,&c);
25         AddEdge(a,b,c);//無向圖,要加兩遍 
26         AddEdge(b,a,c);
27     }
28     memset(dis,0x3f,sizeof dis);//用極大值來初始化 
29     dis[1]=0;//1號節點到本身最短距離爲0 
30     for(k=1;k<=n;k++)
31     {
32         int minp,minz=123456789;
33         for(i=1;i<=n;i++)
34         {
35             if(!ever[i])
36             {
37                 if(dis[i]<minz)
38                 {
39                     minz=dis[i];
40                     minp=i;
41                 }
42             }
43         }
44         ever[minp]=1;
45         int now=point[minp];
46         while(now)
47         {
48             int tox=to[now];
49             if(dis[tox]>dis[minp]+len[now])
50             dis[tox]=dis[minp]+len[now];
51             now=next[now];
52         }
53     }
54     for(i=1;i<=n;i++)
55         printf("%d\n",dis[i]);
56     return 0;
57 }

3.SPFA算法(有負權邊,無負圈,能檢測負圈但不能輸出):

多源最短路!

SPFA和Dijkstra極爲類似,只是加了個隊列優化來檢測負圈和負權邊。

算法描述

創建一個隊列,初始時隊列裏只有起始點,再創建一個表格記錄起始點到全部點的最短路徑(該表格的初始值要賦爲極大值,該點到他自己的路徑賦爲0)。而後執行鬆弛操做,用隊列裏有的點做爲起始點去刷新到全部點的最短路,若是刷新成功且被刷新點不在隊列中則把該點加入到隊列最後。重複執行直到隊列爲空。

判斷有無負環:
若是某個點進入隊列的次數超過N次則存在負環(SPFA沒法處理帶負環的圖)

 1 #include<cstdio>
 2 #include<cstring>
 3 const int N=100500;
 4 const int M=200500;
 5 int point[N]={0},to[M]={0},next[M]={0},len[M]={0},cc=0;
 6 int dis[N];//最短路長度
 7 int queue[N],top,tail;//雙向隊列queue,隊頭,隊尾 
 8 bool in[N];//記錄這個點在不在隊列中,1表示在,0表示不在 
 9 int n,m; //n個節點,m條邊 
10 void AddEdge(int x,int y,int z)//x到y邊長爲z 
11 {
12     cc++;
13     next[cc]=point[x];
14     point[x]=cc;
15     to[cc]=y;
16     len[cc]=z;
17 }
18 int main()
19 {
20     int i,j;
21     scanf("%d%d",&n,&m);
22     for(i=1;i<=m;i++)
23     {
24         int a,b,c;
25         scanf("%d%d%d",&a,&b,&c);
26         AddEdge(a,b,c);//由於是雙向隊列,左邊加一次,右邊加一次
27         AddEdge(b,a,c);
28     }
29     memset(dis,0x3f,sizeof dis);//用極大值來初始化
30     dis[1]=0;//1號節點到本身最短距離爲0 
31     top=0;tail=1;queue[1]=1;in[1]=1;//初始化,只有原點加入 
32     while(top!=tail)
33     {
34         top++;
35         top%=N;
36         int now=queue[top];
37         in[now]=0;
38         int ed=point[now];
39         while(ed)
40         {
41             int tox=to[ed];
42             if(dis[tox]>dis[now]+len[ed])
43             {
44                 dis[tox]=dis[now]+len[ed];
45                 if(!in[tox])
46                 {
47                     tail++;
48                     tail%=N;
49                     queue[tail]=tox;
50                     in[tox]=1;
51                 }
52             }
53             ed=next[ed];
54         }
55     }
56     for(i=1;i<=n;i++)
57         printf("%d\n",dis[i]);
58     return 0; 
59 }

4.Bellman Ford算法(有負權邊,可能有負圈,能檢測負圈並輸出):

單源最短路!

算法描述

1.初始化:將除源點外的全部頂點的最短距離估計值 d[all]=+∞, d[start]=0;
2.迭代求解:反覆對邊集E中的每條邊進行鬆弛操做,使得頂點集V中的每一個頂點v的最短距離估計值逐步逼近其最短距離;(運行|v|-1次)
3.檢驗負權迴路:判斷邊集E中的每一條邊的兩個端點是否收斂。若是存在未收斂的頂點,則算法返回false,代表問題無解;不然算法返回true,而且從源點可達的頂點v的最短距離保存在 d[v]中。
簡單的說,以下圖所示:
 
鬆弛計算以前,點B的值是8,可是點A的值加上邊上的權重2,獲得5,比點B的值(8)小,因此,點B的值減少爲5。這個過程的意義是,找到了一條通向B點更短的路線,且該路線是先通過點A,而後經過權重爲2的邊,到達點B
若是出現瞭如下狀況:

鬆弛操做後,變爲7,7>6,這樣就不修改(Bellman Frod算法的高妙之處就在這),保留原來的最短路徑就OK,代碼實現起來很是簡單。

 1 int n,m;//n個點,m條邊 
 2 struct Edge//定義圖類型結構體 
 3 {
 4     int a,b,c;//a到b長度爲c 
 5 }edge[];
 6 int dis[];
 7 memset(dis,0x3f,sizeof dis);
 8 dis[1]=0;
 9 for(int i=1;i<n;i++)
10 {
11     for(int j=1;j<=m;j++)
12     {
13         if(dis[edge[j].b]>dis[edge[j].a]+edge[j].c)
14         {
15             dis[edge[j].b]=dis[edge[j].a]+edge[j].c;        
16         }
17     }
18 }

5.A*算法:

這玩意兒我是沒看懂,等之後我看懂了再更吧(無奈臉)~

7、拓撲排序:

對一個有向無環圖(Directed Acyclic Graph簡稱DAG)G進行拓撲排序,是將G中全部頂點排成一個線性序列,使得圖中任意一對頂點u和v,若邊(u,v)∈E(G),則u在線性序列中出如今v以前。一般,這樣的線性序列稱爲知足拓撲次序(Topological Order)的序列,簡稱拓撲序列。

打個比喻:咱們要作好一盤菜名字叫作紅燒茄子,那麼第一步得買茄子和配料,第二步就是要洗茄子,第三步就是要開始倒油進鍋裏啊什麼七七八八的,第四步…,你不可能先洗茄子再買茄子和配料,這樣的一些事件必須是按照順序進行的,這些依次進行的事件就構成了一個拓撲序列。

算法描述

咱們須要一個棧或者隊列,二者均可以無所謂,只是找個容器把入度爲0的元素維護起來而已。

①從有向圖中選擇一個入度爲0(無前驅)的頂點,輸出它。

②從網中刪去該節點,而且刪去從該節點出發的全部有向邊。

③重複以上兩步,直到剩餘的網中再也不存在沒有前驅的節點爲止。

具體操做過程以下:

若棧非空,則在棧中彈出一個元素,而後枚舉這個點能到的每個點將它的入度-1(刪去一條邊),若是入度=0,則壓入棧中。 
若是沒有輸出全部的頂點,則有向圖中必定存在環

 1 //拓撲排序,時間複雜度:O(n+m) 
 2 #include<cstdio>
 3 #include<cstring>
 4 const int N=100500;
 5 const int M=200500;
 6 int point[N]={0},to[M]={0},next[M]={0},cc=0;
 7 int xu[N]={0};//棧,初始值爲空,xu[0]表示棧的大小 
 8 int in[N]={0};//入度,a能夠到達b,in[b]++ 
 9 int ans[N]={0};//ans[0]整個拓撲序列的大小 
10 int n,m; 
11 void AddEdge(int x,int y)//鄰接表a到b 
12 {
13     cc++;
14     next[cc]=point[x];
15     point[x]=cc;
16     to[cc]=y;
17 }
18 int main()
19 {
20     int i,j;
21     scanf("%d%d",&n,&m);
22     for(i=1;i<=m;i++)
23     {
24         int a,b;
25         scanf("%d%d",&a,&b);
26         in[b]++;//統計每一個節點的入度 
27         AddEdge(a,b);
28     }
29     for(i=1;i<=n;i++)
30     {
31         if(in[i]==0)//這個節點入度爲0,壓入棧 
32         xu[++xu[0]]=i;
33     }
34     while(xu[0])
35     {
36         int now=xu[xu[0]];//出棧 
37         xu[0]--;
38         ans[++ans[0]]=now;
39         int ed=point[now];
40         while(ed)
41         {
42             int tox=to[ed];
43             in[tox]--;
44             if(!in[tox])
45             xu[++xu[0]]=tox;
46             ed=next[ed];//找下一個相鄰節點 
47         }
48     }
49     if(ans[0]<n)//有向圖中必定存在環,無結果 
50     printf("no solution");
51     else
52     {
53         for(i=1;i<=n;i++)
54         printf("%d ",ans[i]);
55     }
56     return 0;
57 }

8、聯通份量:

強連通:有向圖中,從a能到b而且從b能夠到a,那麼a和b強連通。

強連通圖:有向圖中,任意一對點都知足強連通,則這個圖被稱爲強連通圖。

強聯通份量:有向圖中的極大強連通子圖,就是強連通份量。

通常用Tarjan算法求有向圖強連通份量:

推一個蠻容易理解的blog:http://www.cnblogs.com/uncle-lu/p/5876729.html

9、歐拉路徑與哈密頓路徑:

1.歐拉路徑:從某點出發一筆畫遍歷每一條邊造成的路徑。

歐拉回路:在歐拉路徑的基礎上回到起點的路徑(從起點出發一筆畫遍歷每一條邊)。

歐拉路徑存在

無向圖:當且僅當該圖全部頂點的度數爲偶數 或者 除了兩個度數爲奇數外其他的全是偶數。

有向圖:當且僅當該圖全部頂點 出度=入度 或者 一個頂點 出度=入度+1,另外一個頂點 入度=出度+1,其餘頂點 出度=入度

歐拉回路存在

無向圖:每一個頂點的度數都是偶數,則存在歐拉回路。

有向圖:每一個頂點的入度都等於出度,則存在歐拉回路。

求歐拉路徑/歐拉回路算法經常用Fleury算法

再推一個蠻容易理解的blog:http://www.cnblogs.com/Lyush/archive/2013/04/22/3036659.html

2.哈密頓路徑:每一個點剛好通過一次的路徑是哈密頓路徑

哈密頓迴路:起點與終點之間有邊相連的哈密頓路徑是哈密頓迴路。

最近發現一些網站盜用個人blog,這實在不能忍(™把關於個人名字什麼的所有刪去只保留文本啥意思。。)!!但願各位轉載引用時請註明出處,謝謝配合噢~

原博客惟一地址:http://www.cnblogs.com/geek-007/p/7247953.html

相關文章
相關標籤/搜索