咱們進入一個新的模塊——圖論!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 #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算法(有負權邊,可能有負圈,能檢測負圈並輸出):
單源最短路!
算法描述:
鬆弛操做後,變爲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,這實在不能忍(™把關於個人名字什麼的所有刪去只保留文本啥意思。。)!!但願各位轉載引用時請註明出處,謝謝配合噢~