二叉樹
什么是二叉樹?
父節點至多只有兩個子樹的樹形結構成為二叉樹。如下圖所示,圖1不是二叉樹,圖2是一棵二叉樹。
圖1 普通的樹 圖2 二叉樹
如果一棵樹所有的非葉子節點都有兩個子節點,則稱該樹為完全二叉樹,圖2就是一棵完全二叉樹。
二叉查找樹(ADT)
二叉樹一個重要的應用是二差查找樹,顧名思義,二叉查找樹是二叉樹在查找方面的應用。根據以往的知識,如果給你一個數組或者是鏈表,可能需要遍歷一整個數組或者是量表才能找到需要查找的目標。使用二叉樹作為查找的數據結構,能夠大大縮小查找的深度(若鏈表和數組的長度為N,一棵二叉樹的深度為logN),提高查找的效率。
如何定義一棵查找樹?
設每一個節點對應一個鍵值,而該節點的左子節點對用的數值小於該節點的值,該節點的右子節點的數值大於該節點的值。如下圖所示:
圖3 二差查找樹 圖4 非二差查找樹
圖3是二叉查找樹,因為他總數滿足上述的條件,而圖4不是二叉查找樹,圖中紅色框住的部分不滿足二叉查找樹的條件(父節點6<左子節點7)。
代碼:
struct Tree{
double value;
Tree *left;
Tree *right;
};
TreeNode表示二叉樹節點。其中value表示樹對應的值,left表示該節點的左節點指針,right表示該節點的右節點指針。
二叉查找樹的重要操作
1.查找
1.1 查找固定的數值
給定一個數值,查找二叉樹中是否有對應的數值,如果樹中包含該數值,則返回數值對應的節點,否則,返回NULL。查找的過程:
首先定位到跟節點,如果查找的數值跟節點的數值相等,則根節點為所求;否則,如果數值小於根節點的數值,則查找根節點的左節點,相反,則查找跟節點的右節點。這樣一直遍歷下去,知道找到對應的或者或者已經不能繼續往下查找為止(即已經到達子節點)。
代碼:
1 Tree* Find(double value,Tree *t){ 2 if(t==NULL) 3 return NULL; 4 if(value==t->value) 5 return t; 6 else if(value<t->value) 7 return Find(value,t->left); 8 else 9 return Find(value,t->right); 10 }
1.2查找最大值或最小值
二叉查找樹一個很大的特點是左子節點的數值<父節點的數值,而右子節點的數值>父節點的數值,因此查找最大值或者最小值就相當的方便,只需要從跟節點開始,不斷遍歷左子結點,直到到達葉子節點,就可以得到最小值;而查找最大值則從根節點開始遍歷右子結點,直到到達葉子節點,對用的數值即為最大值。
代碼:
超找最小元素的節點
1 Tree* FindMin(Tree* t){ 2 if(t==NULL) 3 return NULL; 4 if(t->left==NULL) 5 return t; 6 else{ 7 return FindMin(t->left); 8 } 9 }
查找最大元素的節點
1 Tree* FindMax(Tree *t){ 2 if(t==NULL) 3 return NULL; 4 if(t->right==NULL) 5 return t->right; 6 else 7 return FindMax(t->right); 8 }
2. 插入
插入操作是將一個數值插入到二叉查找樹中的過程,插入后的樹依然滿足二叉查找樹的條件。一棵二叉查找樹的構建過程,就是一個不斷將元素插入到二叉樹的過程。
插入的操作最關鍵的是一個查找的過程,如果在二叉樹中找到要插入的數值,則什么都不用做,如果找不到,只需要在最后我們查找的葉子節點上新增加一個子節點即可。舉一個例子:
上圖是一棵二叉樹,如果我們要插入11,則按以下步驟進行:
再比如要找17,按以下步驟查找:
代碼:
1 void Insert(double value,Tree* t){ 2 if(t==NULL){ 3 t = new Tree(); 4 if(t==NULL) 5 return; 6 else{ 7 t->value=value; 8 t->left=NULL; 9 t->right=NULL; 10 } 11 } 12 if(t->value>value) 13 Insert(value,t->left); 14 else 15 Insert(value,t->right); 16 }
3. 刪除
刪除操作就是刪除鍵值與數值相同的節點。刪除操作相比查找和插入來說是一個較為復雜的過程。如果要刪除的節點就是葉子節點,直接將該節點刪除即可,但是,如果要刪除非葉子節點,就需要通過調整其他節點的位置來構建新的二叉樹。一般來說,直接用該節點的右子樹的最小值替換該節點的值,然后再刪除右子樹的最小節點即可。
例子:
AVL樹
雖說二叉查找樹是一種優秀的數據結構,能夠大大降低數據查詢的復雜度。但是,並不是說有情況下二叉樹都能夠達到快速查找的目的。
我們發現,如果按照[7,10,11,12,14,15,18]這樣的順序一個個元素進行插入的話就會出現右圖所示的二叉樹,這樣的二叉樹跟一個鏈表幾乎是沒有區別的,查找的效率一樣,沒有體現出二叉樹的優勢。出現這種原因是構建二叉樹的過程中沒有平衡節點的左右子樹的高度。根節點7的右子樹有很高的深度,但是左子樹是空的。我們需要的是一棵左右節點平衡的二叉樹,而其中一種傳統的平衡二叉樹是AVL樹。
定義:AVL樹是二叉樹,其各個節點的左右子樹的高度相差不超過1。
定義:數的高度可以看做是節點與最低的葉子節點的距離。跟節點的高度最大,而葉子節點的高度為0,一個不存在的節點的高度定義為-1。
例如:左圖中節點12的高度為2,節點18的高度為0,而右圖中節點12的高度為3,節點18的高度為1。
左圖是一棵AVL樹,右圖不是一棵AVL樹,因為右圖節點15的左右子樹的高度相差2(左子樹的高度為-1,右子樹的蓋度為1)。
AVL樹的構建
AVL樹的構建同樣是不斷將元素插入的過程,但是與二叉查找樹不同,AVL樹在插入的過程中需要滿足AVL樹的條件,如果發現插入新的元素后不能滿足AVL條件,需要通過調整元素的位置直到滿足條件。
元素的插入無非就只有以下四種情況:1.左子樹插入一個左節點;2.左子樹插入一個右節點;3.右子樹插入一個左節點;4.右子樹插入一個右節點。
其中,1和4、2和3是對稱的操作,因此下面只討論1和2兩種情況。
1. 左子樹插入一個左節點
如上圖所述,左邊是原始的二叉樹,該二叉樹滿足AVL樹的條件,在插入元素5后變成了右邊的二叉樹,而此時不滿足AVL樹的條件,因為節點10的左右兩棵子樹的高度相差2(左子樹的高度為1,右子樹的高度為-1)。
此時需要通過對子樹進行調整才能讓二叉樹再次滿足AVL的條件。
如上圖所示,調整后的二叉樹重新變成一棵AVL樹,則種調整的方法稱為“左旋轉”,通過旋轉調整節點的位置,使二叉樹滿足AVL樹的條件。同理,如果新增的元素為右子樹的右子樹,而且新增后子樹的左右子樹高度相差為2,此時進行“右旋轉”即可調整為AVL樹。
2. 左子樹插入一個右節點
如上圖所述,左邊是原始的二叉樹,該二叉樹滿足AVL樹的條件,在插入元素8后變成了右邊的二叉樹,而此時不滿足AVL樹的條件,因為節點10的左右兩棵子樹的高度相差2(左子樹的高度為1,右子樹的高度為-1)。
與情況1(左子樹新增左節點)不一樣,此時不能通過一個簡單的“坐旋轉”來調整左右子樹的高度。怎么辦呢?需要通過兩次“旋轉”,顯示通過對元素7進行右旋轉,然后再對10進行左旋轉。如下圖所示:
同理,對於右子樹插入一個左節點的情況,如果此時不符合AVL樹的條件,需要先進性“左旋轉”,再進行“右旋轉”即可。
代碼:

1 AVLTree* AVLInsert(double value,AVLTree * tree){ 2 if(tree==NULL){ 3 tree = new AVLTree(); 4 tree->value=value; 5 tree->left=NULL; 6 tree->right=NULL; 7 tree->height=0; 8 return; 9 } 10 if(value>tree->value){ 11 tree->right=AVLInsert(value,tree->right); 12 if(Height(tree->right)-Height(tree->left)==2){ 13 if(value>tree->right->value) 14 tree=SingleRotateWithRight(tree); 15 else 16 tree=DoubleRouteWithRight(tree); 17 } 18 } 19 else{ 20 tree->left=AVLInsert(value,tree->left); 21 if(Height(tree->left)-Height(tree->right)==2){ 22 if(value<tree->left->value) 23 tree=SingleRotateWithLeft(tree); 24 else 25 tree=DoubleRouteWithLeft(tree); 26 } 27 } 28 tree->height = Height(tree->left)>Height(tree->right)?Height(tree->left)+1:Height(tree->right)+1; 29 return tree; 30 } 31 //左旋轉 32 AVLTree* SingleRotateWithLeft(AVLTree * tree){ 33 AVLTree * left = tree->left; 34 tree->left=left->right; 35 left->right=tree; 36 tree->height=Height(tree->left)>Height(tree->right)?Height(tree->left)+1:Height(tree->right)+1; 37 left->height=Height(left->left)>Height(left->right)?Height(left->left)+1:Height(left->right)+1; 38 return left; 39 } 40 //右旋轉 41 AVLTree* SingleRotateWithRight(AVLTree * tree){ 42 AVLTree * right = tree->right; 43 tree->right=right->left; 44 right->left=tree; 45 tree->height=Height(tree->left)>Height(tree->right)?Height(tree->left)+1:Height(tree->right)+1; 46 right->height=Height(right->left)>Height(right->right)?Height(right->left)+1:Height(right->right)+1; 47 return right; 48 } 49 //右—左雙旋轉 50 AVLTree* DoubleRouteWithLeft(AVLTree* tree){ 51 tree->left = SingleRotateWithRight(tree->left); 52 return SingleRotateWithLeft(tree); 53 } 54 //右—左雙旋轉 55 AVLTree* DoubleRouteWithRight(AVLTree* tree){ 56 tree->right = SingleRotateWithLeft(tree->right); 57 return SingleRotateWithRight(tree); 58 }