B樹的結構有:B-Tree, B-Tree, B*Tree
BTree(二叉排序樹)B-Tree:B樹也是二叉排序樹的變異版本,是N叉的排序樹。
M階BTree的幾個重要特性
1.結點最多含m棵子樹(指針),m-1個關鍵字(存的數據,空間)(m >= 2)
2.除根節點和葉子結點外,其它每個結點至少有ceil(m / 2)個子節點,ceil向上取整
3.若根節點不是葉子節點,則至少有兩棵子樹。
二叉排序樹BST,也稱二叉查找樹
在二叉查找樹中,因為中序遍歷的順序是 左子樹, 根, 右子樹,而在二叉排序樹中,左子樹結點值 < 根節點值 < 右子樹結點值,所以,二叉排序樹的中序遍歷序列是遞增的結果。
二叉排序樹的中序遍歷序列一定是一個遞增的有序序列。
對於這棵樹,中序遍歷序列是:1, 2, 3, 4, 5, 7, 8, 10, 16
對於二叉排序樹而言,我們最常用的操作並不是排序,而是查找。
查找操作
1.判斷二叉樹是否為空
2.二叉樹若不為空,則查找根節點,若相等則查找成功
3.若根節點不相等,則當小於根節點則查找左子樹;當大於根結點值時則查找右子樹。
4.當查找到葉節點仍沒查找到相對應的值(判斷指針是否為空),則查找失敗。
該算法的時間復雜度為O(h)
1 BSTNode* BST_Search(BiTree T, ElemType key, BSTNode* &p) 2 { 3 p = NULL; 4 while(T != NULL && key != T.data) 5 { 6 if(key < T.data) 7 T = T -> lchild; 8 if(key > T.data) 9 T = T -> rchild; 10 } 11 return T; 12 13 }
插入
若二叉排序樹為空,則直接插入結點
若二叉排序樹非空,當值小於根節點時插入左子樹;當值大於根節點時,插入右子樹,當值等於根節點時不進行插入。
注意,在樹的實現中,我們是通過二叉鏈表或者三叉鏈表來實現的,所以我們在插入操作或者刪除操作時修改指針指向即可。
1 //返回的是整型變量,若插入成功則返回值1,插入失敗則返回值0 2 int BST_Insert(BiTree &T, KeyType k){ 3 if(T == NULL) 4 { 5 T = (BiTree)malloc(sizeof(BSTNode)); 6 T->data = k; 7 T -> lchild = NULL; 8 T -> rchild = NULL; 9 return1; 10 } 11 12 else if(key == T->data) 13 return 0; 14 15 else if(key > T -> data) 16 return BST_Insert(T->rchild, k); 17 18 else 19 return BST_Insert(T->lchild, k) 20 21 }
構造二叉排序樹
構造二叉排序樹是一個動態的問題。就是不斷調用插入函數進行構造二叉排序樹。
讀入一個元素並建立結點,若二叉樹為空,則將其作為根節點;
若二叉樹非空,當值小於根結點時,插入左子樹;當值大於根節點時,插入右子樹;當值等於根節點時不進行插入。
1 void Create_BST(BiTree &T, KeyType str[], int n){ 2 T = NULL; 3 int i = 0; 4 while(i < n) 5 { 6 BST_Insert(T, str[i]); 7 i++; 8 } 9 }
但是這樣的構造法生成的二叉順序樹與元素的順序有關
刪除操作
二叉排序樹的刪除操作是比較復雜的,分為以下三種情況
1.所被刪除的結點是葉節點,則直接刪除
2.若被刪除的結點只有一棵子樹,則讓該結點的子樹稱為該節點父結點的子樹,從而代替該節點
3.若被刪除的結點右兩棵子樹,則讓該節點的中序序列直接后繼代替該節點,並刪除直接后繼結點。
這里只做第三種情況的分析。
第三種情況中,被刪除的中序直接后繼節點是比該結點值大,但是又是比被刪除結點的右子樹中其他結點值小的一個結點,所以將被刪除結點的中序直接后繼結點代替該結點時最合適的操作。
在刪除二叉排序樹中刪除並插入某結點,得到的二叉排序樹是否與原來相同。
答案是可能相同,也可能不同,要視情況具體分析。
仍以上圖中圖1所示的二叉排序樹為例,若刪除的是結點7,那么得到的二叉排序樹是相同的。
但是若刪除的是結點5,那么再重新將該節點插入該二叉樹時,結點5就會成為結點7的左孩子結點。
二叉排序樹的查找效率
平均查找長度(ASL)取決於樹的高度。
如果二叉排序樹是平衡二叉樹的話,查找效率是O(log2n), 最壞情況下是O(n),此時二叉排序樹類似於單鏈表。
平衡二叉樹
平衡二叉樹AVL:任意結點的平衡因子的絕對值不超過1。
平衡因子:左子樹高度與右子樹高度之差
高度為h的最小平衡二叉樹的結點數Nh的計算
Nh = Nh-1 + Nh-2 + 1
N0 = 0
N1 = 1
平衡二叉樹的后序遍歷過程
利用遞歸的后序遍歷過程:
1.判斷左子樹是一棵平衡二叉樹
2.判斷右子樹是一棵平衡二叉樹
3.判斷以該節點為根的二叉樹為平衡二叉樹
判斷條件:若左子樹和右子樹均為平衡二叉樹,且左子樹與右子樹高度差的絕對值小於等於1,則平衡。
需要保留兩個變量:表示平衡性的變量B,為布爾類型,平衡為1,不平衡為0;表示高度的變量,為整型。
1 void Judge_AVL(BiTree bt, int &balance, int &h) 2 { 3 //左子樹的平衡性、右子樹的平衡性, 左子樹的高度,右子樹的高度 4 int bl = 0; br = 0, hl = 0; hr = 0; 5 if(bt == NULL) 6 { 7 h = 0; 8 balance = 1; 9 } 10 11 else if(bt -> lchild == NULL && bt -> rchild == NULL) 12 { 13 h = 1; 14 balance = 1; 15 } 16 17 else 18 { 19 Judge_AVL(bt->lchild, bl, hl); 20 Judge_AVL(bt -> rchild, br, hr); 21 if(hl > hr) 22 h = hr + 1; 23 else 24 h = hl + 1; 25 26 27 if(abs(hl - hr) < 2 && bl ==1 && br == 1) 28 balance = 1; 29 else 30 balance = 0; 31 } 32 }
插入操作
先按照二叉排序樹的插入過程進行插入,然后對插入后的二叉排序樹進行調整,將其調整為平衡二叉樹。
調整過程:調整過程是針對最小的不平衡子樹,也就是從我們的插入結點開始依次向上找到最小的不平衡子樹。
根據不平衡的情況,我們分為了四種調整的過程。
LL平衡旋轉(右單旋轉)
原因:在結點A的左孩子的左子樹上插入新結點導致二叉樹的不平衡
調整方法:右旋操作:將A的左孩子B代替A,將A結點稱為B的右子樹根節點,而B的原右子樹作為A的左子樹。
RR平衡旋轉(左單旋轉)
原因:在結點A的右孩子的右子樹上插入了新結點導致了二叉樹的不平衡
調整:左旋操作,將A的右孩子B代替A,將A結點稱為B的左子樹根結點,而B的原左子數則作為A的右子樹。
LR平衡旋轉(先左后右雙旋轉)
原因:在結點A的左孩子的右子樹上插入了新結點
調整:先左旋后右旋操作:先將A的左孩子B的右孩子結點C代替B,然后再將C結點向上代替A的位置
1.在本圖中,既可以插到C結點的左子樹CL上,也可以插到C結點的右子樹CR上
2.本圖是將結點插入到了CL子樹上,所以CL是H層, CR是H-1層。
3.之所以標注(H)是考慮到CL和CR可能是空的
RL平衡旋轉(先右后做雙旋轉)
原因:在結點A的右孩子的左子樹上插入了新結點
調整方法:先右旋后左旋操作:將A的右孩子B的左孩子結點C代替B,然后再將C結點向上代替A的位置