資料結構中的樹(二叉樹、二叉搜尋樹、AVL樹)
阿新 • • 發佈:2020-08-04
> [資料結構動圖展示網站](https://www.cs.usfca.edu/~galles/visualization/Algorithms.html)
### 樹的概念
樹(英語:tree)是一種抽象資料型別(ADT)或是實作這種抽象資料型別的資料結構,用來模擬具有樹狀結構性質的資料集合。它是由n(n>=1)個有限節點組成一個具有層次關係的集合。把它叫做“樹”是因為它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。它具有以下的特點:
- 每個節點有零個或多個子節點;
- 沒有父節點的節點稱為根節點;
- 每一個非根節點有且只有一個父節點;
- 除了根節點外,每個子節點可以分為多個不相交的子樹;
- **節點的度**:一個節點含有的子樹的個數稱為該節點的度;
- **樹的度**:一棵樹中,最大的節點的度稱為樹的度;
- **葉節點或終端節點**:度為零的節點;
- **父親節點或父節點**:若一個節點含有子節點,則這個節點稱為其子節點的父節點;
- **孩子節點或子節點**:一個節點含有的子樹的根節點稱為該節點的子節點;
- **兄弟節點**:具有相同父節點的節點互稱為兄弟節點;
- **節點的層次**:從根開始定義起,根為第1層,根的子節點為第2層,以此類推;
- **樹的高度或深度**:樹中節點的最大層次;
- **堂兄弟節點**:父節點在同一層的節點互為堂兄弟;
- **節點的祖先**:從根到該節點所經分支上的所有節點;
- **子孫**:以某節點為根的子樹中任一節點都稱為該節點的子孫。
- **森林**:由m(m>=0)棵互不相交的樹的集合稱為森林;
### 二叉樹
每個節點最多含有兩個子樹的樹稱為二叉樹
- 平衡二叉樹(AVG樹): 當且僅當任何節點的兩棵子樹的高度差不大於1的二叉樹
- 完全二叉樹: 對於一顆二叉樹,假設其深度為d(d>1)。除了第d層外,其它各層的節點數目均已達最大值,且第d層所有節點從左向右連續地緊密排列,這樣的二叉樹被稱為完全二叉樹,其中滿二叉樹的定義是所有葉節點都在最底層的完全二叉樹;
- 排序二叉樹: (二叉查詢數 Binary Search Tree), 也稱二叉搜尋樹,有序二叉樹,任意一個結點左邊子節點的資料要比根結點的值小,右邊子節點的資料要比根結點的值大。**但是如果二叉樹是單增的情況會退化成連結串列**
### 二叉樹的遍歷
1. 深度優先遍歷
- 先序遍歷 preorder 在先序遍歷中,我們先訪問根節點,然後遞迴使用先序遍歷訪問左子樹,再遞迴使用先序遍歷訪問右子樹 根節點->左子樹->右子樹
- 中序遍歷 inorder 在中序遍歷中,我們遞迴使用中序遍歷訪問左子樹,然後訪問根節點,最後再遞迴使用中序遍歷訪問右子樹 左子樹->根節點->右子樹
- 後序遍歷 postorder 在後序遍歷中,我們先遞迴使用後序遍歷訪問左子樹和右子樹,最後訪問根節點 左子樹->右子樹->根節點
![image.png](https://i.loli.net/2020/07/02/MSakJX4DvoEN2qF.png))
2. 廣度優先遍歷(層次遍歷)
### 二叉樹反推
如果已知***中序和先序,或者中序和後序***,可以確定二叉樹的結構
> eg:
> 先序:A B C D E F
> 中序: C B A E D F
**先序找根,中序定兩邊**
先序遍歷序列為ABCDEF,第一個字母是A被打印出來,就說明A是根結點的資料。
再由中序遍歷序列是CBAEDF,可以知道C和B是A的左子樹的結點,
E、D、F是A的右子樹的結點
![image.png](https://i.loli.net/2020/07/02/GYbw1ljUVigH9Cu.png)
然後我們看先序中的C和B,它的順序是A**BC**DEF,B是在C的前面列印,所以B應該是A的左孩子,而C就只能是B的孩子,此時是左還是右孩子還不確定。再看中序序列是**CB**AEDF,C是在B的前面列印,這就說明C是B的左孩子,否則就是右孩子了
![image.png](https://i.loli.net/2020/07/02/EC51AthuW98Ymqf.png)
再看先序中的E、D、F,它的順序是ABC**DEF**,那就意味著D是A結點的右孩子,E和F是D的子孫,注意,它們中有一個不一定是孩子,還有可能是孫子的。再來看中序序列是CBA**EDF**,由於E在D的左側,而F在右側,所以可以確定E是D的左孩子,F是D的右孩子
![image.png](https://i.loli.net/2020/07/02/vOMSEj2QyIdZAhb.png)
> 注:如果已經先序和後序無法判斷二叉樹結構
>
> 先序序列:ABC
>
> 後序序列:CBA
**我們可以確定A一定是根結點,但接下來,我們無法知道,哪個結點是左子樹,哪個是右子樹**
![image.png](https://i.loli.net/2020/07/02/UDL2t8PQ9hEsYNZ.png)
### 二叉查詢樹(二叉搜尋樹)
節點的左子樹只包含小於當前節點的數。
節點的右子樹只包含大於當前節點的數。
***所有左子樹和右子樹自身必須也是二叉搜尋樹***
[Python實現二叉查詢樹](https://github.com/Panlq/NoteBook/blob/master/tree/binary-search-tree.py)
參考以下兩篇文章(**最好是自己畫圖容易理解**):
- [6天通吃樹結構—— 第一天 二叉查詢樹](https://www.cnblogs.com/huangxincheng/archive/2012/07/21/2602375.html)
- [二叉排序樹、紅黑樹、AVL 樹最簡單的理解](https://juejin.im/entry/58f4268f61ff4b0058fcb190)
### 二叉平衡樹
[Python實現平衡二叉樹](https://github.com/Panlq/NoteBook/blob/master/tree/AVL-tree.py) 刪除和新增調整的是最小不平衡子樹
平衡二叉樹 (Height-Balanced Binary Search Tree) 是一種二叉排序樹,
其中**每一個結點的左子樹和右子樹的高度差不超過1(小於等於1)**
二叉樹的平衡因子 (Balance Factor) 等於該結點的左子樹深度減去右子樹深度的值稱為平衡因子。平衡因子只可能是[-1,0,1]。距離插入結點最近的,且平衡因子的絕對值大於1的結點為根的子樹,稱為最小不平衡子樹
平衡二叉樹就是二叉樹的構建過程中,每當插入一個結點,看是不是因為樹的插入破壞了樹的平衡性,若是,則找出最小不平衡樹。在保持二叉樹特性的前提下,**調整最小不平衡子樹中各個結點之間的連結關係**,進行相應的旋轉,使之成為新的平衡子樹。簡記為: **步步調整,步步平衡**
參考以下兩篇文章(**最好是自己畫圖**):
> **注:**第一篇文章中針對左右失衡和右左失衡的處理圖片和程式碼中有誤,但是主要是看個人理解,作者可以只對根節點進行失衡處理,而我這邊是按照第二篇文章說的,**調整最小不平衡子樹**
- [6天通吃樹結構—— 第二天 平衡二叉樹](https://www.cnblogs.com/huangxincheng/archive/2012/07/22/2603956.html)
- [二叉排序樹、紅黑樹、AVL 樹最簡單的理解](https://juejin.im/entry/58f4268f61ff4b0058fcb190)
對於其中新增元素的遞迴程式碼的理解:
![](https://img2020.cnblogs.com/blog/778496/202008/778496-20200804004427859-1887224367.jpg)
### 霍夫曼樹
(用於資訊編碼):帶權路徑最短的二叉樹稱為哈夫曼樹或最優二叉樹;
應用: 壓縮檔案
### B樹(B-Tree)
一種對讀寫操作進行優化的自平衡的二叉查詢樹,能夠保持資料有序,擁有多餘兩個子樹。B樹是多路平衡查詢樹,2階B樹才是平衡二叉樹
應用: 資料庫儲存
M階的Btree的幾個重要特性:
1. 節點最多含有m棵字樹(指標), m-1個關鍵字(存的資料,空間)(m > 2)
2. 除根節點和葉子節點外,其他每個節點至少有ceil(m / 2)個子節點,(ceil為上取整)
3. 若根節點不是葉子節點,則至少有兩棵子樹
M階: 這個由磁碟的頁大小決定,頁記憶體是4KB, 好處是一次性取資料就可以取出這個節點即這個頁資料,不會造成IO讀取的浪費。
![img](https://upload-images.jianshu.io/upload_images/3575048-49b083c1a49cd6de.png?imageMogr2/auto-orient/strip|imageView2/2/format/webp)
### B+Tree
1. 每個節點最多有m個子節點
2. 除根節點外,每個節點至少有m/2個子節點,注意如果結果除不盡,就取上蒸,如 5/2=3
3. 根節點要麼是空,要麼是獨根,否則至少有2個子節點
4. 有k個子節點的節點必有k個關鍵字
5. 葉節點的高度一致
![image.png](https://i.loli.net/2020/07/02/OIiknK2oALMXWcT.png)
![img](https://upload-images.jianshu.io/upload_images/3575048-9e8f1e7ab7a3e729.png?imageMogr2/auto-orient/strip|imageView2/2/format/webp)
適合大資料的磁碟索引,經典的MySQL,所有的資料都存在葉子節點,其他上層節點都是索引,增加了系統的穩定性以及遍歷查詢效率。葉子節點之間是雙向指標,這一點就有利於範圍查詢。
**MyISAM儲存引擎的資料結構(非聚集)**
索引檔案和資料檔案是分離的,非聚集(非聚族)
![image.png](https://i.loli.net/2020/07/02/Kuxp6tzA3qsP9EC.png)
.MYD 儲存資料的檔案
.MYI 儲存索引的檔案
.FRM 表結構檔案,管理索引和資料的框架
**InnoDB索引的實現(聚集)**
- 表資料本身就是按B+Tree組織的一個索引結構檔案
- 聚集索引-葉子節點包含了完整的資料記錄,**索引跟資料合併,MySQL預設節點大小為16KB,所以說高度為3的B+樹就能夠儲存千萬級別的資料。**
- 為什麼InnoDB表必須有主鍵,並且推薦使用整形的自增主鍵?
- 整形儲存佔用比較少,且比較容易,如果是uuid字串還需要進行轉換且佔用空間大
- 使用自增是為了避免二叉樹的頻繁自平衡分裂,自增主鍵,只需要每次都忘後面增加即可,不會造成大範圍的效能開銷
- 為什麼非主鍵索引結構葉子節點儲存的是主鍵值?(一直性)
![image.png](https://i.loli.net/2020/07/02/CVhNTp9cRgSrsGa.png)
**聯合索引的底層儲存結構**
![image.png](https://i.loli.net/2020/07/02/cDp2sUWAaknjVCB.png)
1. [B站-100分鐘講透MySQL索引底層原理](https://www.bilibili.com/video/BV1aE41117sk?p=5)
2. [MySQL底層索引演算法](https://www.bilibili.com/video/BV1AE41117R5?p=8)
3. [為什麼 MySQL 使用 B+ 樹](https://draveness.me/whys-the-design-mysql-b-plus-tree/)
4. [MYSQL-B+TREE索引原理-詳細解釋了SQL語句的執行過程](https://www.jianshu.com/p/486a514b0ded)
### 常見樹的應用場景
1. xml,html等,那麼編寫這些東西的解析器的時候,不可避免用到樹
2. 路由協議就是使用了樹的演算法
3. mysql資料庫索引
4. 檔案系統的目錄結構
5. 所以很多經典的AI演算法其實都是樹搜尋,此外機器學習中的decision tree也是樹結構
![image.png](https://i.loli.net/2020/07/02/t8PM4ZyIi51EF