图论 —— 图的连通性 —— Tarjan 求双连通分量
【概念】
1.雙連通分量:對(duì)于一個(gè)無(wú)向圖,其邊/點(diǎn)連通度大于1,滿足任意兩點(diǎn)之間,能通過(guò)兩條或兩條以上沒(méi)有任何重復(fù)邊的路到達(dá)的圖,即刪掉任意邊/點(diǎn)后,圖仍是連通的
2.分類:
? ? 1)點(diǎn)雙連通圖:點(diǎn)連通度大于 1 的圖
? ? 2)邊雙連通圖:邊連通度大于 1 的圖
【原理】
1.求點(diǎn)雙連通分量
求點(diǎn)雙連通分量可以在求割點(diǎn)的同時(shí)用棧維護(hù)。
在搜索圖時(shí),每找到一條樹(shù)枝邊或后向邊(非橫叉邊),就把這條邊加入棧中。如果遇到滿足 dfn(u)<=low(v),說(shuō)明 u 是一個(gè)割點(diǎn),同時(shí)把邊從棧頂一個(gè)個(gè)取出,直到遇到了邊 (u,v),取出的這些邊與其關(guān)聯(lián)的點(diǎn),就組成一個(gè)點(diǎn)雙連通分支。
割點(diǎn)可以屬于多個(gè)點(diǎn)雙連通分支,其余點(diǎn)和每條邊只屬于且屬于一個(gè)點(diǎn)雙連通分支。
2.求邊雙連通分量
求邊雙連通分量時(shí),需要先求出橋,然后把橋全部去掉,原圖變成多個(gè)連通塊,此時(shí)每個(gè)連通塊就是一個(gè)邊雙連通分量。
橋不屬于任何一個(gè)邊雙連通分量,其余的邊和每個(gè)頂點(diǎn)都屬于且只屬于一個(gè)邊雙連通分量
【過(guò)程】
1.求點(diǎn)雙連通分量
1)Tarjan 求割點(diǎn)
2)每找到一個(gè)割點(diǎn),將它上面的所有點(diǎn)彈出棧,所得到的點(diǎn)集就是點(diǎn)雙連通分量
2.求邊雙連通分量
1)Tarjan 找橋
2)刪除橋
3)剩余各部分即為邊雙連通分量
【實(shí)現(xiàn)】
1.求點(diǎn)雙連通分量
int n,m; vector<int> G[N]; vector<int> bcc[N];//包含i號(hào)點(diǎn)雙連通分量的所有結(jié)點(diǎn) int dfn[N]; bool iscut[N];//記錄i結(jié)點(diǎn)是否是割點(diǎn) int bccno[N];//記錄第i個(gè)點(diǎn)屬于第幾號(hào)點(diǎn)雙連通分量 int block_cnt;//時(shí)間戳 int bcc_cnt;//記錄點(diǎn)雙連通分量個(gè)數(shù) struct Edge {int x;int y; }; stack<Edge> S; int Tarjan(int x,int father) {int lowx=dfn[x]=++block_cnt;int child=0;//子節(jié)點(diǎn)數(shù)目for(int i=0; i<G[x].size(); i++) {int y=G[x][i];//當(dāng)前結(jié)點(diǎn)的下一結(jié)點(diǎn)Edge e;e.x=x;e.y=y;if(dfn[y]==0) { //若未被訪問(wèn)過(guò)S.push(e);child++;//未訪問(wèn)過(guò)的節(jié)點(diǎn)才能算是x的孩子int lowy=Tarjan(y,x);lowx=min(lowx,lowy);if(lowy>=dfn[x]) {iscut[x]=true;//x點(diǎn)是割點(diǎn)bcc_cnt++;bcc[bcc_cnt].clear();while(true) {Edge temp=S.top();S.pop();if(bccno[temp.x]!=bcc_cnt) {bcc[bcc_cnt].push_back(temp.x);bccno[temp.x]=bcc_cnt;}if(bccno[temp.y]!=bcc_cnt) {bcc[bcc_cnt].push_back(temp.y);bccno[temp.y]=bcc_cnt;}if(temp.x==x && temp.y==y)break;}}} else if(dfn[y]<dfn[x] && y!=father) { //y!=father確保了(x,y)是從x到y(tǒng)的反向邊S.push(e);lowx=min(lowx,dfn[y]);}}if(father<0 && child==1 )//x若是根且孩子數(shù)<=1,那x就不是割點(diǎn)iscut[x]=false;return lowx; } int main() {scanf("%d%d",&n,&m);for(int i=0; i<n; i++)G[i].clear();while(m--) {int x,y;scanf("%d%d",&x,&y);G[x].push_back(y);G[y].push_back(x);}bcc_cnt=0;block_cnt=0;memset(dfn,0,sizeof(dfn));memset(iscut,0,sizeof(iscut));memset(bccno,0,sizeof(bccno));for(int i=0; i<n; i++)if(!dfn[i])Tarjan(i,-1);printf("點(diǎn)-雙連通分量一共%d個(gè)\n",bcc_cnt);for(int i=1; i<=bcc_cnt; i++){printf("第%d個(gè)點(diǎn)-雙連通分量包含以下點(diǎn):\n",i);sort(&bcc[i][0],&bcc[i][0]+bcc[i].size()); //對(duì)vector排序,使輸出的點(diǎn)從小到大for(int j=0; j<bcc[i].size(); j++)printf("%d ",bcc[i][j]);printf("\n");}return 0; }2.求邊雙連通分量
struct Edge {int x;int yl } edge[N]; int n,m; int dfn[N],low[N]; int bccno[N]; vector<int> G[N],bcc[N]; int sig,block_cnt; bool g[N][N],isbridge[N]; void tarjan(int x,int father) {dfn[x]=low[x]=++block_cnt;for(int i=0; i<G[x].size(); i++) {int y=edge[G[x][i]].y;if(!dfn[y]) {tarjan(y,x);low[x]=min(low[x],low[y]);if(low[y]>dfn[x]) {isbridge[G[x][i]]=1;isbridge[G[x][i]^1]=1;}} else if(dfn[y]<dfn[x] && y!=father)low[x]=min(low[x], dfn[y]);} } void dfs(int x) {dfn[x]=1;bccno[x]=sig;for(int i=0; i<G[x].size(); i++) {int y=G[x][i];if(isbridge[y])continue;if(!dfn[edge[y].y])dfs(edge[y].y);} } int main() {scanf("%d%d",&n,&m);for(int i=0; i<n; i++)G[i].clear();while(m--) {int x,y;scanf("%d%d",&x,&y);G[x].push_back(y);}sig=0;block_cnt=0;memset(dfn,0,sizeof(dfn));memset(low,0,sizeof(low));memset(isbridge,0,sizeof(isbridge));memset(bccno,0,sizeof(bccno));memset(bcc,0,sizeof(bcc));for(int i=1; i<=n; i++) //先DFS找橋if(!dfn[i])tarjan(i, -1);memset(dfn,0,sizeof(dfn));for(int i=1; i<=n; i++){ //再DFS找邊雙連通分量if(!dfn[i]) {sig++;dfs(i);}}printf("%d\n",sig);//邊雙連通分量個(gè)數(shù)return 0; }?
總結(jié)
以上是生活随笔為你收集整理的图论 —— 图的连通性 —— Tarjan 求双连通分量的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 训练日志 2019.1.26
- 下一篇: 树形结构 —— 树与二叉树