摘要:日常生活中,很多事物都可以用樹來描述,例如書的目錄、工作單位的組織架構等等。樹是計算機中非常重要的一種資料結構,樹儲存方式可以提高資料的儲存、讀取效率。
本文分享自華為雲社群《【雲駐共創】想了解二叉樹,看這篇文章就夠了》,作者: liuzhen007 。
前言
日常生活中,很多事物都可以用樹來描述,例如書的目錄、工作單位的組織架構等等。樹是計算機中非常重要的一種資料結構,樹儲存方式可以提高資料的儲存、讀取效率。
一、樹的基本定義
日常生活中,很多事物都可以用樹來描述,樹是計算機中非常重要的一種資料結構,樹儲存方式可以提高資料的儲存、讀取效率,比如利用二叉排序樹,既可以保證資料的檢索速度,同時,也可以保證資料的插入、刪除、修改的速度。
其實,樹的種類有很多種,比如普通的二叉樹、完全二叉樹、滿二叉樹、線索二叉樹、哈夫曼樹、二叉排序樹、平衡二叉樹、AVL平衡二叉樹、紅黑樹、B樹、B+樹、堆等。今天介紹的主要內容是二叉樹的基本知識和幾種基礎型別的二叉樹。
二、二叉樹的相關術語
在正式開講前,首先介紹一些關於二叉樹的專業名詞和術語,包括結點、結點的度、葉子結點、分支結點、結點的層次、樹的度、樹的深度等,瞭解這些基礎的專業名詞和術語對於我們理解二叉樹的特性有非常重要的幫助作用。
1)結點:包含一個資料元素及若干指向子樹分支的資訊。
2)結點的度:一個結點擁有子樹的資料成為結點的度。
3)葉子結點:也稱為終端結點,沒有子樹的結點或者度為零的結點。
4)分支結點:也稱為非終端結點,度不為零的結點成為非終端結點。
5)結點的層次:從根結點開始,根結點的層次為1,根的直接後繼層次為2,以此類推。
6)樹的度:樹中所有結點的度的最大值。
7)樹的深度:樹中結點的最大層次。
1. 樹的特點
樹作為一種特殊的資料結構,有非常多的特性,比如:
1)每個結點有多個或者零個子結點
2)沒有父結點的結點成為根結點,沒有子結點的結點成為葉結點
3)每一個非根結點只有一個父結點
4)每個結點及其後代結點整體上可以看做是一棵樹,稱為當前結點為根的子樹
2. 二叉樹的基本定義
1)二叉樹就是度不超過2的樹,其每個結點最多有兩個子結點
2)二叉樹的結點分為左結點和右結點
3. 滿二叉樹
1)二叉樹的每一層的結點度都達到最大值,則這個二叉樹就是滿二叉樹
2)一棵深度為n的滿二叉樹,有2^n-1個結點
4. 完全二叉樹
葉子結點只能出現在最下層和次下層,最後一層的葉子結點在左邊連續,倒數第二層的葉子結點在右邊連續,我們稱為完全二叉樹。
三、二叉樹的建立
接下來,我們透過程式碼來描述二叉樹,語言以Java為例,其中結點類定義如下:
二叉查詢樹類定義如下:
相關類定義好後,我們來看具體的方法實現,下面分別介紹。
1. size()方法
size()方法相對簡單,每次新增元素加一,刪除元素減一,維護一個公共的變數 num 即可,程式碼實現如下:
2. put(Key key,Value value)方法
put(Key key,Value value)方法可以利用過載方法 put(Node x,Key key,Value value),因此實現也相對簡單,其中第一個引數只需要傳根結點即可,程式碼實現如下:
3. put(Node x,Key key,Value value)方法
put(Node x,Key key,Value value)方法應該是整個類中實現相對較為複雜的,下面進行簡單的分析。
第一種情況,當前樹中沒有任何一個結點,直接將新插入的結點作為根結點。
第二種情況,當前樹不為空,則從根結點開始。這種情況有可以細分為三種情況:
1)如果新結點的key小於當前結點的key,則繼續查詢當前結點的左子結點。
2)如果新結點的key大於當前結點的key,則繼續查詢當前結點的右子結點。
3)如果新結點的key等於當前結點的key,則樹中已經存在這樣的結點,替換該結點的value值即可。
具體的程式碼實現如下:
4. get(Key key)方法
get(Key key)方法和 put(Key key,Value value)方法類似,也可以利用過載方法 get(Node x,Key key)來實現,程式碼實現如下:
5. get(Node x,Key key)方法
get(Node x,Key key)方法實現查詢方法從根結點開始,如果要查詢的key小於當前結點的key,則繼續找當前結點的左子結點;如果要查詢的key大於當前結點的key,則繼續找當前結點的右子結點;如果要查詢的key等於當前結點的key,則返回當前結點的value。
具體的程式碼實現如下:
透過上面的程式碼,我們可以看出 get()方法和 put()方法類似,都是透過遞迴呼叫實現的。
6. delete(Key key)方法
delete(Key key)方法也是一樣的思路,呼叫後面的過載方法就行了,程式碼實現如下:
7. delete(Node x,Key key)方法
刪除方法的實現思路,以最複雜的情況為例,首先找到被刪除的結點,找到被刪除結點右子樹中的最小結點 minNode,刪除右子樹中的最小結點,讓被刪除結點的左子樹成為最小結點 minNode 的左子樹,讓被刪除結點右子樹成為最小結點minNode的右子樹,讓被刪除結點的父結點指向最小結點 minNode。
具體的程式碼實現如下:
既然程式碼已經寫完了,接下來透過程式碼來驗證一下,看看我們寫得是否正確:
答案輸出:
3
李四
2
Nice,太好了,透過上述輸出結果已經證明了程式是正確的。
四、查詢二叉樹中最小和最大的鍵
比如二叉樹中用來記錄某個公司員工薪資和員工姓名資料,或者某班級學生們的排名和姓名資料。如何快速找出排名最高和最低的同學資料?
這樣的話,該怎麼做呢?其實,一般還可以整理出如下四個方法,定義如下:
1. min()方法
min()方法和上面提到的 get()和 put()方法類似,也可以使用下面的過載方法實現,具體程式碼如下:
// 找出樹中最小的鍵
public key min() {
return min(root).key;
}
2. min(Node x)方法
min(Node x)方法需要根據傳入的結點位置,查詢左子樹中的最小的結點,如果為空,則直接返回空,具體程式碼實現如下:
// 找出樹中最小鍵所在的結點
public Node min(Node x) {
if (x == null) {
return x;
}
Node minNode = x;
while (minNode.left != null) {
minNode = minNode.left;
}
return minNode;
}
3. max()方法
max()方法和上面提到的 min()方法類似,也可以使用下面的過載方法實現,具體程式碼如下:
// 找出樹中最小的鍵
public key max() {
return max(root).key;
}
4. max(Node x)方法
max(Node x)方法需要根據傳入的結點位置,查詢右子樹中的最大的結點,如果為空,則直接返回空,具體程式碼實現如下:
// 找出樹中最大鍵所在的結點
public Node min(Node x) {
if (x == null) {
return x;
}
Node maxNode = x;
while (maxNode.right != null) {
maxNode = maxNode.right;
}
return maxNode;
}
五、二叉樹的遍歷
二叉樹的遍歷有三種方式,分別是前序遍歷、中序遍歷、後序遍歷。
1. 前序遍歷
先訪問根結點,再訪問左子樹,最後訪問右子樹,比如下圖中的二叉樹,前序遍歷結果如下:
EBADCGFH。
2. 中序遍歷
先訪問左子樹,再訪問根結點,最後訪問右子樹,比如下圖中的二叉樹,中序遍歷結果如下:
ABCDEFGH。
3. 後序遍歷
先訪問左子樹,再訪問右子樹,最後訪問根結點,比如下圖中的二叉樹,後序遍歷結果如下:
ACDBFHGE。
結論
二叉樹的不僅在基礎的資料結構方面有非常重要的研究意義,在實際應用中也有非常重要的應用場景,兼顧了常規資料結構陣列和連結串列的優點,同時又避免了二者天生的不足。許多實際的問題抽象出來的資料結構往往是二叉樹的形式,從而利用二叉樹的儲存結構和演算法特性,因此學習二叉樹就非常的必要。希望透過今天本文的介紹能夠幫助大家深入理解和掌握二叉樹。