5分鐘瞭解二叉樹之LeetCode裡的二叉樹

morningli發表於2022-03-26

有讀者反饋,現在誰不是為了找工作才學的資料結構,確實很有道理,是我膚淺了。所以為了滿足大家的需求,這裡總結下LeetCode裡的資料結構。對於我們這種職場老人來說,刷LeetCode會遇到個很尷尬的問題,就是每道題看起來都很熟悉,都覺得是十拿九穩了,但是真要你寫出來,又很容易卡殼。如果人家讓你寫個反轉連結串列的題目都能卡殼,那場面就會相當尷尬了。起初我還是對刷題比較抗拒的,感覺就跟學習交通法規一樣,你花個半天時間就能背下來,就能考個九十多分,靠這個來面試實在是太low了。現在也釋然了,一方面是現在人才太多了,企業已經沒法通過面試來篩選人才,所以選擇演算法來做一層保底的篩選,如果你不會,就算你有多牛,也只能歸類到垃圾的隊伍了,所以這事得順應時勢,適者生存;另一方面比如位元組這種公司確實對演算法需求比較強,人家考核演算法也是實際需要,也沒必要覺得他是在有意刁難。

LeetCode題型

LeetCode應該怎麼刷呢,首先很重要的一點是要多花時間,每天刷一兩道題,保持好做題的感覺。這玩意熟能生巧,經常練習有助於治療老年痴呆。另外我在網路收集了一些網友刷LeetCode的經驗,對於初刷LeetCode的同學比較有用:

  • 按分類刷;每個分類從 Easy 到 Medium 順序刷;
  • 優先刷 樹、連結串列、二分查詢、DFS、BFS 等面試常考型別;
  • 優先刷題號靠前的題目;
  • 優先刷點贊較多的題目;

本文主要是講LeetCode中的資料結構以及考點,方便大家加深認識。這次我們首先講的是LeetCode裡的樹。為什麼先講樹呢,因為樹是我們用的最多,實用性最強,也最容易在面試中被問到的一種資料結構,小到我們程式語言裡的map,大到mysql的索引,都離不開樹這個資料結構。可以這樣說,你把樹搞明白了,至少能應付一半的面試了。

在LeetCode的標籤分類題庫中,和樹有關的標籤有:樹(227)、二叉樹(198)、二叉搜尋樹(54)、字典樹(49)、線段樹(28)、樹狀陣列(20)、最小生成樹(5)。這些題中,二叉樹又是樹中的重點,可以歸納為:

 

上圖主要是歸納了一些LeetCode常見題型和概念,方便大家記憶和查漏補缺。其中打星的是其中比較難的部分,不過這東西見仁見智,也許有些人也會覺得很簡單。

下面幫大家回憶一下關於二叉樹的基礎概念。

要理解什麼是二叉樹,首先需要理解什麼是樹( tree )。可以使用遞迴的方式定義樹:

一棵樹是一些節點( node )的集合。這個集合可以是空集,說不是空集,則樹由稱作( root )的節點 r 以及0個或多個非空的(子)樹T1, T2, ..., Tk組成,這些子樹中每一棵都被來自根r的一條有向的( edge )所連線。

每一棵子樹的根叫作根 r 的兒子 (child), 而 r 是每一棵子樹的根的父親 ( parent )。下圖展示了用遞迴定義的典型的樹:

在下面的樹中,節點A是根。節點F有一個父親A並且有兒子K、L和M。每一個節點可以有任意多個兒子,也可能是是零個兒子。沒有兒子的節點稱為樹葉( leaf ),圖中的樹葉是B、C、H、I、P、Q、K、L、M和N。具有相同父親的節點為兄弟 ( siblings )。因此,K、L和M都是兄弟。用類似的方法可以定義祖父( grandparent )和孫子( grandchild )關係。

 

從節點 n1 到 nk路徑( path )定義為節點n1, n2, ..., nk的一個序列,是的對於 1 <= i < k 的節點ni是ni+1的父親,這條路徑的( length ) 是該路徑上的邊的條數,即 k-1。從每一個節點到他自己有一條長為0的路徑。注意,在一棵樹中從根到每個節點恰好存在一條路徑。

對於任意節點ni,n深度( depth ) 為從根到 ni 的唯一路徑的長。因此,根的深度為 0。節點 ni 的高( height )是從ni到一片樹葉的最長路徑的長。因此所有的樹葉的高都是0。一棵樹的高度( height of a tree )等於它的根的高。對於圖中的樹,E的深度為1而高為2;F的深度為1而高也是1;該樹的高為3。一棵樹的深度( depth of a tree )等於其最深的樹葉的深度,該深度總是等於這棵樹的高。

如果存在從n1到n2的一條路徑,那麼n1是n2的一位祖先( ancestor ),而n2是n1的一個後裔( descendant )。如果n1 != n2,那麼n1是n2真祖先(proper ancestor),而n2是n1真後裔(proper descendant)。

二叉樹

二叉樹(Binary tree)是樹形結構的一個重要型別,是AVL樹,紅黑樹,二叉堆,大頂堆,小頂堆,多叉樹的基礎。那麼什麼是二叉樹?下面給出二叉樹的遞迴定義:

二叉樹是一棵空樹,或者是一棵由一個根節點和兩棵互不相交的,分別稱作根的左子樹和右子樹組成的非空樹;左子樹和右子樹又同樣都是二叉樹。

以上圖為例,其中 6 是二叉樹的根節點,A和B分別是根節點 6 的左子樹和右子樹;直線相連的節點分別是父節點和子節點,比如 6 是 2 和 8 的父節點,2 和 8 是 6 的子節點;沒有子節點的節點叫葉子節點,比如 0、3、5、7、9 、10都是葉子節點。 

相關術語

節點(node):包含一個資料元素及若干指向子樹分支的資訊。上圖中的每個圓圈都表示一個節點。

度(degree):一個節點擁有子樹的數目稱為節點的度 ,葉子的度一定是0。上圖中的6、2、8、4的度為2,9的度為1,0、3、5、7、9 、10的度為0。

葉子節點(leaf node:也稱為外部節點(external node  outer node終端節點(terminal node,指沒有子樹的節點或者度為零的節點。上圖中的0、3、5、7、9 、10都是葉子節點。

內部節點(internal node 或 inner node:也稱為非終端節點分支節點branch node),度不為零的節點稱為非終端節點。上圖中的6、2、8、4、9都是內部節點。

樹的度(degree of tree):樹中所有節點的度的最大值。上面的樹的度是2,二叉樹的度不會超過2。

層(level:節點的層是沿著它和根節點之間的唯一路徑的邊數。LeetCode中的層是從0開始的,比如上圖中的6是第0層,2是第一層。

距離(Distance):沿兩個節點之間最短路徑的邊數。

有序樹(Ordered tree):為每個頂點的子節點指定排序的有根樹。

森林(Forest):由m(m≥0)棵互不相交的樹構成一片森林。如果把一棵非空的樹的根節點刪除,則該樹就變成了一片森林,森林中的樹由原來根節點的各棵子樹構成。

滿二叉樹(full binary tree):每個節點都有 0 或 2 個子節點的二叉樹。

完全二叉樹(complete binary tree):除了最底層節點可能沒填滿外,其餘每層節點數都達到最大值,並且最下面一層的節點都集中在該層最左邊的若干位置。若最底層為第 h 層,則該層包含 1~ 2個節點。下圖所示是一棵完全二叉樹:

完美二叉樹(perfect binary tree):所有內部節點都有兩個孩子並且所有葉子都具有相同深度或相同級別。一棵完美的樹總是完全二叉樹,但一棵完全二叉樹不一定是完美的。

基本操作

遞迴

針對二叉樹的問題的解決中,遞迴是一種十分常用的程式設計方式,有必要說明一下。

定義

在數學和電腦科學中,遞迴指由一種(或多種)簡單的基本情況定義的一類物件或方法,並規定其他所有情況都能被還原為其基本情況。

例如,下列為某人祖先的遞迴定義:

  • 某人的雙親是他的祖先(基本情況)。
  • 某人祖先的雙親同樣是某人的祖先(遞迴步驟)。

斐波那契數列是典型的遞迴案例:

  • F(0) = 0(初始值)
  • F(1) = 1(初始值)
  • 對所有大於1的整數n:F(n) = F(n-1) + F(n-2)(遞迴定義)

一種便於理解的心理模型,是認為遞迴定義對物件的定義是按照“先前定義的”同類物件來定義的。例如:你怎樣才能移動100個箱子?答案:你首先移動一個箱子,並記下它移動到的位置,然後再去解決較小的問題:你怎樣才能移動99個箱子?最終,你的問題將變為怎樣移動一個箱子,而這時你已經知道該怎麼做的。

C++的遞迴

直接或間接呼叫自己的函式稱為遞迴函式(recursion function)。一個簡單的定義函式的例子是階乘的計算。數n的階乘是從1到n的乘積。例如5的階乘就是120。

1 * 2 * 3 * 4 * 5 = 120

解決這個問題的自然方法就是遞迴:

int factorial(int val)
{
    if (val > 1)
        return factorial(val-1)*val;
    return 1;    
}

遞迴函式必須定義一個終止條件;否則就會永遠遞迴下去,這意味著函式會一直呼叫自身知道程式棧耗盡。有時候這種現象稱為“無限遞迴錯誤”(infinite recursion error)。對於函式factorial,val為1是終止條件。

前序遍歷

若二叉樹非空,則依次執行如下操作:
⑴ 訪問根結點;
⑵ 遍歷左子樹;
⑶ 遍歷右子樹。

中序遍歷

若二叉樹非空,則依次執行如下操作:
⑴遍歷左子樹;
⑵訪問根結點;
⑶遍歷右子樹。

後序遍歷

若二叉樹非空,則依次執行如下操作:
⑴遍歷左子樹;
⑵遍歷右子樹;
⑶訪問根結點。

層序遍歷

設二叉樹的根節點所在層數為1,層序遍歷就是從所在二叉樹的根節點出發,首先訪問第一層的樹根節點,然後從左到右訪問第2層上的節點,接著是第三層的節點,以此類推,自上而下,自左至右逐層訪問樹的結點的過程就是層序遍歷。

 

引用:

https://blog.csdn.net/fuxuemingzhu/article/details/105183554

https://www.cnblogs.com/joelwang/p/10640599.html

https://www.cnblogs.com/liuzhen1995/p/11921771.html

https://leetcode-cn.com/circle/discuss/J8XFse/

https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91/1602879

https://www.jianshu.com/p/c5d48937f2d9

https://en.wikipedia.org/wiki/Binary_tree

https://en.wikipedia.org/wiki/Tree_(data_structure)

https://zh.wikipedia.org/wiki/%E9%80%92%E5%BD%92

《C++ Primer 中文版》第4版

https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91%E9%81%8D%E5%8E%86/9796049?fr=aladdin

相關文章