【演算法與資料結構 02】二叉樹的引入

褪色的零發表於2020-10-18

【演算法與資料結構 02】二叉樹的引入


相信大家對於樹都不會太陌生,今天就來簡單聊聊關於資料結構中的樹的那些東西吧~

一、樹的簡單介紹

1.1 什麼是樹?

先來看看百度百科上對樹的解釋

樹狀圖是一種資料結構,它是由n(n >= 1) 個有限結點組成一個具有層次關係的集合

把它叫做 “樹” 是因為它看起來像一顆倒掛的樹,也就是說它是根朝上,而葉朝下

它大多長這樣:

再來看看一些結點的稱呼或是概念性的東西

  • A 結點是 B 和 C 結點的上級,也就是說 A 結點是 B 和 C 結點的父節點,B 和 C 結點是 A 結點的子結點
  • B 和 C 結點同時是 A 結點的子結點,稱為兄弟結點
  • A 結點沒有父節點,稱為根節點
  • D 、E 和 F 結點沒有子結點,稱為葉子節點
  • 樹深即為樹中結點的最大層次數(也稱高度深度),如圖深度即為 3

可以看出,樹由根結點和若干顆子樹構成,換句話說,剔除一顆樹的根節點後,它的子結構也滿足樹的特性:

  • 每個結點有零個或多個結點
  • 每個非根結點有且僅有一個父節點
  • 樹裡面沒有環路

提到樹就不得不說起二叉樹了

1.2 什麼是二叉樹

二叉樹:每個結點最多含有兩個結點的樹,兩個結點分別為左子結點和右子結點

來看看一個二叉樹的簡單實現

public class TreeNode {
	int val;
	TreeNode left;
	TreeNode right;
	TreeNode() {}
	TreeNode(int val) { this.val = val; }
	TreeNode(int val, TreeNode left, TreeNode right) {
		this.val = val;
		this.left = left;
		this.right = right;
	}
}

二叉樹當中也有幾種特殊的型別:

  • 滿二叉樹:所有結點都有兩個子結點(當然除開最後一層的葉子結點)

[(img

  • 完全二叉樹:除最後一層結點個數都達到最大,並且最後一層的葉子結點都往左排列

    (從定義可以發現,一個滿二叉樹一定是完全二叉樹)

之所以會稱為完全二叉樹,是從它儲存空間利用率來看的

比如上面的完全二叉樹:

012345678
ABCDEFGH

如果這裡是一顆非完全二叉樹,則會浪費比較多的儲存空間:

01234567
ABCDEF
89101112131415
GHI
  • 二叉搜尋樹:滿足下列約束條件:

    1. 若左子樹不為空,則左子樹上所有結點的值均小於它的根節點的值
    2. 若右子樹不為空,則右子樹上所有結點的值均大於它的根節點的值
    3. 左、右子樹也必須是二叉搜尋樹

二叉查詢樹中,會盡可能避免兩個結點數值相等的情況

二叉查詢樹也有幾種優化,例如 AVL 樹、紅黑樹、哈夫曼樹 … 之後再做介紹

二、二叉樹的遍歷方式

二叉樹的遍歷是指從根結點觸發,按照某種次序依次訪問二叉樹中所有結點,使得每個結點被訪問且僅被訪問一次

二叉樹的遍歷方式大體上有四種經典方式:前序遍歷、中序遍歷、後序遍歷、層序遍歷

其中前序、中序和後序遍歷本質上可以認為是深度優先遍歷(DFS),而二叉樹的層序遍歷本質上可以認為是廣度優先遍歷(BFS),這裡不作詳細介紹,想了解的朋友可以期待下一篇文章哦~

DFS 簡單來說就是對每一個可能的分支路徑深入到最底的遍歷方式;而 BFS 簡單來說就是一層一層由內而外的遍歷方式

  • 前序遍歷:對於樹中的任意結點,先列印這個結點,然後前序遍歷它的左子樹,最後前序遍歷它的右子樹

  • 中序遍歷:對於樹中的任意結點,先中序遍歷它的左子樹,然後列印這個結點,最後中序遍歷它的右子樹

  • 後序遍歷:對於樹中的任意結點,先後序遍歷它的左子樹,然後 後序遍歷它的右子樹,最後列印這個結點

    (也就是說,這裡的序指的是父節點的遍歷順序)

前序、中序、後序遍歷

實現前序、中序和後序遍歷普遍用的還是遞迴,個人覺得遞迴相比非遞迴的方法更妙一些~
下面貼出程式碼片段方便大家理解

其實這三種遍歷方式有很大的相同之處,正如它們的定義一般

2.1 前序遍歷

public static void preOrderTraversal(TreeNode head) {
    if (head == null) {
        return;
    }
    System.out.print(head.val + " ");
    preOrderTraversal(head.left);
    preOrderTraversal(head.right);
}

2.2 中序遍歷

public static void inOrderTraversal(TreeNode head) {
    if (head == null) {
        return;
    }
    inOrderTraversal(head.left);
    System.out.print(head.val + " ");
    inOrderTraversal(head.right);
}

這裡額外提一下二叉查詢樹的中序遍歷:

對二叉查詢樹進行中序遍歷,就可以輸出一個按數值從小到大的有序資料佇列(這裡就直接拿上面的圖吧,偷懶~)

比如這裡就會列印出:9、13、15、16、18、21、23、25

2.3 後序遍歷

public static void postOrderTraversal(TreeNode head) {
    if (head == null) {
        return;
    }
    postOrderTraversal(head.left);
    postOrderTraversal(head.right);
    System.out.print(head.val + " ");
}

2.4 層序遍歷

層序遍歷就像上面所提到的那樣,即逐層的、從左到右訪問所有結點

層序遍歷

層序遍歷的實現可以使用佇列的特性,把每個還沒有訪問到的結點依次放入佇列,然後再彈出佇列的頭部元素當作是當前的結點:

public static void levelOrder(TreeNode head){
	LinkedList<TreeNode> queue = new LinkedList<>();
	queue.offer(head);
	while (!queue.isEmpty()){
		TreeNode cur = queue.poll();
		System.out.print(cur.val + " ");
		if(cur.left != null) queue.add(cur.left);
		if(cur.right != null) queue.add(cur.right);
	}
}

使用佇列儲存每層的所有結點,每次把佇列裡原先的所有結點進行出佇列操作,再把每個元素的非空左右子節點入隊,即可得到每層的遍歷


時隔五個月左右自己又有了寫部落格的想法,希望自己能一直堅持下去,另外,朋友們的點贊和關注是對我最大的支援 ?

相關文章