1. 程式人生 > 實用技巧 >重談DFS序、時間戳和尤拉序

重談DFS序、時間戳和尤拉序

重談DFS序、時間戳和尤拉序

本篇隨筆複習總結一下演算法競賽中的DFS序、時間戳、尤拉序的相關知識。

DFS序的部分抄的是本蒟蒻今年年初的部落格,連結放在下面:

淺談DFS序

DFS序的概念

先來上張圖:

樹的DFS序列,也就是樹的深搜序,它的概念是:樹的每一個節點在深度優先遍歷中進出棧的時間序列。

樹的DFS序,簡單來講就是對樹從根開始進行深搜,按搜到的時間順序把所有節點排隊。

就比如上面這棵樹,它的一個DFS序就是:

1 4 6 6 3 9 9 3 4 7 7 2 5 5 8 8 2 1

注意兩點:

首先,一棵樹的DFS序不唯一。因為深搜的時候選擇哪個子節點的順序是不一樣的。

其次,對於一棵樹進行DFS序,需要把回溯的時候的節點編號也記錄一下,這就是為什麼每個數字在DFS序中會出現兩遍的原因。

很容易發現的是,樹的DFS序的長度是\(2N\)


求DFS序的程式碼實現

程式碼實現很簡單,就是從根節點開始深搜,然後按順序打標記就可以了。

在下面的程式碼中,\(id[]\)陣列就是DFS序的陣列,\(cnt\)就是計時變數,上傳引數的\(f\)表示當前節點\(x\)的父親。

#include<cstdio>
using namespace std;
const int maxn=1e5+10;
int n;
int tot,to[maxn<<1],nxt[maxn<<1],head[maxn];
int id[maxn],cnt;
void add(int x,int y)
{
    to[++tot]=y;
    nxt[tot]=head[x];
    head[x]=tot;
}
void dfs(int x,int f)
{
    id[++cnt]=x;
    for(int i=head[x];i;i=nxt[i])
    {
        int y=to[i];
        if(y==f)
            continue;
        dfs(y,x);
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    dfs(1,0);
    for(int i=1;i<=cnt;i++)
        printf("%d ",id[i]);
    return 0;
}

DFS序的性質

觀察上圖:

和這棵樹的一個DFS序:

1 2 8 8 5 5 2 7 7 4 3 9 9 3 6 6 4 1

我們發現,一個數字兩次出現的位置所夾的區間,正好是以這個數為根的一個子樹。比如:

2 8 8 5 5 2

就表示以2為根的子樹是:2 8 5

我們發現:DFS序的一個性質就是把一棵子樹放在一個區間裡。這個優秀的性質把樹狀結構變成了線性結構。方便我們進行統計。


DFS序的部分應用

剛剛提到的DFS序的性質讓DFS序列成為了描述樹的一種方式。準確地來說,DFS序讓我們把樹狀結構變成了一個線性的結構。我們只需要在這個線性結構上進行區間修改、區間查詢,而不需要再一遍遍地遍歷整個子樹來做到修改和查詢。

這種性質的最顯然應用就是在樹鏈剖分中。樹鏈剖分就是把樹拆成一條條輕重鏈來把樹的所有點對映到一棵線段樹上來進行樹上的修改與統計。它的實現原理就是DFS序。

如果有興趣學習樹鏈剖分的讀者,請移步這篇部落格:

樹鏈剖分詳解

樹上的很多問題都可以用到DFS序,這裡不再一一列舉。希望讀者能掌握:樹轉區間的這個性質。在以後的OI生涯中加以應用。


關於時間戳

時間戳的概念是:按照深度優先遍歷的過程,按每個節點第一次被訪問的順序,依次給予這些節點\(1-N\)的標記,這個標記就是時間戳。

在鄙人的理解中,時間戳是DFS序的反向對映。

就是:DFS序的概念是按照深搜時間順序的節點編號序列,陣列下角標存的是時間。

而時間戳的概念是按照深搜時間順序的時間編號序列,陣列下角標存的是節點編號。

也就是:

\(id[i]=x\),i是時間,x是節點,id是DFS序。

\(dfn[x]=i\),i是時間,x是節點,dfn是時間戳。

很好理解吧。

一個誤區

網上很多講解說樹鏈剖分是用的DFS序,嚴格地說,這個說法是不太嚴謹的,事實上它是通過維護時間戳來實現樹轉連續區間的。並且,因為樹剖是輕重鏈剖分,並不是嚴格的深度優先遍歷,所以也不能武斷地說它就是DFS序或者時間戳。但是這麼久了大家都這麼說,況且時間戳也就是DFS序的一個“反函式”,所以大家這麼說其實也沒太大問題。這個部分僅表示蒟蒻個人觀點,歡迎大佬們和蒟蒻探討相關問題。


尤拉序的概念和一些性質

還是這張圖:

尤拉序的定義是:從根節點出發到回到根節點為止,按深度優先遍歷的順序所經過的所有點的順序。

比如上面的樹的一個尤拉序就是:

1 2 8 2 5 2 1 7 1 4 3 9 3 4 6 4 1

和DFS序相似的,尤拉序也不唯一。

並且,可以探究出的性質是,每個點在尤拉序中出現的次數等於這個點的度數,因為DFS到的時候加進一次,回去的時候也加進。

所以尤拉序的長度是\(n+n-1=2n-1\)