二叉排序树和平衡树

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的位置

原文地址:https://www.cnblogs.com/hxhlrq/p/13338959.html