Tarjan學習筆記

来源:https://www.cnblogs.com/Vwsrenzk/archive/2018/12/15/10124677.html
-Advertisement-
Play Games

從頭開始學習OI之Tarjan. 今天重新學習了Tarjan演算法..來這裡寫一下學習筆記...但是我太菜了..還是講不好怎麼辦... ...


從頭開始學習OI之Tarjan. 今天重新學習了Tarjan演算法..來這裡寫一下學習筆記...

用處

Tarjan演算法,是一個關於圖的聯通性的神奇演算法.基於DFS(深度優先搜索).是對於有向圖的演算法是.根據樹,棧,打標記等方法來完成剖析一個圖的工作.


我們先來學習一下Tarjan演算法需要知道的定義:
強連通,強連通圖,強連通分量

強連通(Strongly Connected):

在一個有向圖 G 里,有兩個點 a,b ,由a有一條路可以走到b,由b又有一條路可以走到a,我們就叫這兩個頂點 (a,b) 強連通.

強連通圖(Strongly Connected Graph):

如果在一個有向圖 G 中,每兩個點都強連通,我們就叫這個圖 強連通圖

強連通分量(Strongly Connected Components):

在一個有向圖 G 中,有一個子圖,這個子圖每2個點都滿足強連通,我們就叫這個子圖叫做 強連通分量 (如圖中的 1,2,3 組成的分量就叫強連通分量)


Tarjan演算法:

Tarjan所做的事情很簡單...就是找到每一個強連通分量(單獨一個點也算是強連通分量..) 在實現演算法之前...我們先定義幾個數組:
dfn[i] 表示第 i 個節點的時間戳..也就是在DFS這張圖的時候這個點被遍歷到的時刻..
low[i] 表示第 i 個節點所在的強連通分量的根節點(按理說強連通分量無所謂根節點..這裡的根節點是指在那一棵DFS樹中這個強連通分量的根節點)

很明顯如果 dfn[i] == low[i] 那麼就代表這一個點是一個強連通分量的根
先看一下演算法流程

void Tarjan(int u) {
    dfn[u] = low[u] = ++Index; //將dfn和low都初始化為時間戳,也就是dfs到的時刻
    Stack[++top] = u; //將該點壓入dfs棧中
    vis[u] = 1; //標記點在棧中
    for(int i = head[u]; i; i = edge[i].next) { //DFS過程
        if(!dfn[edge[i].to]) { //如果該點沒有被搜索到過
            Tarjan(edge[i].to); //對於該點進行演算法(即dfs的過程)
            low[u] = min(low[u],low[edge[i].to]); //搜索完成返回時更新一下這個強連通分量里的所有點的在dfs樹中的根節點
        } else if(vis[edge[i].to]) { //如果該點已經在搜索棧中,那麼代表當前棧中在這個點後的點在一個強連通分量里,那麼這個搜索到的點就是這個強連通分量的根節點..
            low[u] = min(low[u],dfn[edge[i].to]); //將當前點所在強連通分量的根節點修改為搜索到的這個節點,也就是根節點..
        }
    }
    if(dfn[u] == low[u]) { //按照上面的定義我們知道這是判斷是否是一個強連通分量的根節點
        while(Stack[top] != u) { //按照上面所說的將該點在棧後的所有節點都彈出(在一個強連通分量里..也就是我們要求的了..可以染色存儲起來備用或者縮成一個點(縮點演算法))
            printf("%d ",Stack[top]);
            vis[Stack[top--]] = 0;
        }
        printf("%d\n",Stack[top]); //彈出當前的這個根
        vis[Stack[top--]] = 0;
    }
}

首先來一張有向圖 \(G\).我們一點一點來模擬整個演算法.




首先是一點一點的入棧..也就是上面DFS遍歷的時候的順序..叫做DFS序或者入棧序..即:


Step1: 1號點入棧 dfn[1] = low[1] = ++index (1)
此時棧為: 1
Step2: 2號點入棧 dfn[2] = low[2] = ++index (2)
此時棧為: 1 2
Step3: 3號點入棧 dfn[3] = low[3] = ++index (3)
此時棧為: 1 2 3
Step4: 6號點入棧 dfn[6] = low[6] = ++index (4)
此時棧為: 1 2 3 6

左邊這張樹的圖就是當前操作完成後的DFS樹...
走到這裡之後我們看到右邊圖中六號節點沒有了出邊...也就是上面說的第一步或者說是第一個判斷結束了...
我們就要開始返回了,明顯 low[6] == dfn[6] 所以6就是一個強連通分量的根節點;
棧要一直彈出直到彈出6號節點 此時棧為: 1 2 3
然後返回到三號..三號再無出邊..
也一直彈出直到將其彈出 存為一個強連通分量的根 此時棧為: 1 2


發現2號節點還有可以繼續遍歷下去的邊..於是將五號節點壓入棧中即:
dfn[5] = dfn[5] = ++index(5) 此時棧為: 1 2 5
再遍歷發現一個6號..已經遍歷過就不在管他了..
再遍歷發現一個1號節點..1號在棧中..於是進入第二個if語句,修改
low[5] = min(low5,low1) 所以 low[5] 也就是五號節點所在的強連通分量的根就是1
五號沒有出邊了..返回上一層..修改 low[2] = min(low2,low5),low[2] = 1
二號沒有出邊了..返回上一層..修改 low[1] = min(low1,low2),low[1] = 1 low[1]依然等於1
一號還有出邊..遍歷到四號 dfn[4] = low[4] = ++index(6) 此時棧為 1 2 5 4
四號遍歷到五號..五號在棧中所以更新一下 low[4] = min(low4,low5),low[4] = 1;
再返回 low[1] = min(low1,low4) ,low[1] = 1;
然後1號也沒有出邊了..這時棧一直彈出直到將1號彈出 棧空

最後的DFS樹是這樣的

按照我們找到的根節點拆成一個個的強連通分量

這樣就完成了

我們把以一號為開始的連通圖都遍歷一遍了...為了防止圖有多個(不連通) 我們要在調用Tarjan的時候這樣寫

for(int i = 1; i <= n; ++i) 
  if(!dfn[i]) Tarjan(i); //如果沒有時間戳,那就代表沒有遍歷到,從此點開始Tarjan

這樣就可以把所有的圖都給遍歷一遍了...




來一道裸代碼。
輸入:
一個圖有向圖。
輸出:
它每個強連通分量。

這個圖就是剛纔講的那個圖。一模一樣。

Input:
6 8
1 3
1 2
2 4
3 4
3 5
4 6
4 1
5 6
Output:
6
5
3 4 2 1


代碼:

#include <cstdio>
#include <algorithm>

using namespace std;

struct node {
    int to,next;
} edge[1001];

int cnt,Index,top;
int dfn[1001],low[1001];
int stack[1001],head[1001],visit[1001];

void add(int x,int y) {
    edge[++cnt].next = head[x];
    edge[cnt].to = y;
    head[x] = cnt;
}
void tarjan(int x) { //代表第幾個點在處理。遞歸的是點。
    dfn[x] = low[x] = ++Index; // 新進點的初始化。
    stack[++top] = x; //進棧 
    visit[x] = 1; //表示在棧里
    for(int i = head[x]; i; i = edge[i].next) {
        if(!dfn[edge[i].to]) { //如果沒訪問過
            tarjan(edge[i].to); //往下進行延伸,開始遞歸
            low[x] = min(low[x],low[edge[i].to]);//遞歸出來,比較誰是誰的兒子/父親,就是樹的對應關係,涉及到強連通分量子樹最小根的事情。
        } else if(visit[edge[i].to]) { //如果訪問過,並且還在棧里。
            low[x] = min(low[x],dfn[edge[i].to]);//比較誰是誰的兒子/父親。就是鏈接對應關係
        }
    }
    if(low[x] == dfn[x]) { //發現是整個強連通分量子樹里的最小根。
        do {
            printf("%d ",stack[top]);
            visit[stack[top]] = 0;
            top--;
        } while(x != stack[top+1]);//出棧,並且輸出。
        printf("\n");
    }
    return ;
}
int main() {
    int n,m;
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i = 1; i <= m; i++) {
        scanf("%d%d",&x,&y);
        add(x,y);
    }
    for(int i = 1; i <= n; i++)
        if(!dfn[i]) tarjan(i);//當這個點沒有訪問過,就從此點開始。防止圖沒走完
    return 0;
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 切片實例 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38 ...
  • 一、安裝nginx https://yq.aliyun.com/articles/101144?spm=5176.10695662.1996646101.searchclickresult.70af967bColksb 二、nginx.conf文件配置 /usr/local/nginx/conf/c ...
  • 原創作品,可以轉載,但是請標註出處地址: "https://www.cnblogs.com/V1haoge/p/10125279.html" SpringBoot整合MyBatis plus 步驟 第一步:添加必要的依賴 第一種是在已存在MyBatis的情況下,直接添加mybatis plus包即可 ...
  • 前面介紹了處理字元串的常用方法,還有一種分割字元串的場景也很常見,也就是按照某個規則將字元串切割為若幹子串。分割規則通常是指定某個分隔符,根據字元串內部的分隔符將字元串進行分割,例如逗號、空格等等都可以作為字元串的分隔符。正好String類型提供了split方法用於切割字元串,只要字元串變數調用sp ...
  • Shiro引入Spring 添加jar包/maven配置 <!-- shiro支持 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.4</ver ...
  • lua中有兩種閉包, c閉包和lua閉包 兩種閉包的公共部分: C閉包的結構 結構比較簡單, f是一個滿足 int lua_func(lua_State ) 類型的c函數 upvalue是創建C閉包時壓入的upvalue, 類型是TValue, 可以得知, upvalue可以是任意的lua類型 Lu ...
  • Python開髮菜鳥入坑 項目要求pdf轉成圖片,網上較多的方案對於windows極其不友好,wand,Pythonmagick(win下載地址:www.lfd.uci.edu/~gohlke/pythonlibs/#pythonmagick),imagemagick(win下載地址:www.ima ...
  • Inno Setup 系列之安裝、卸載前檢測進程運行情況並關閉相應進程 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...