数据结构之树(五)

5. 二叉树的应用

5.2 二叉排序树

5.2.1 二叉排序树

  1. 排序二叉树的基本定义

二叉排序树(Binary Search Tree,BST),也称作是二叉查找树.二叉排序树或者是一棵空树,或者是一棵具有以下特性的非空二叉树:
① 若左子树非空,那么左子树上的所有结点关键字均小于根结点的关键字值;
② 若右子树非空,则右子树上所有的结点关键字均大于根结点的关键字的值;
③ 左子树与右子树本身也分别是一棵二叉排序树.

二叉排序树也是一棵递归的数据结构,所以可以非常方便地使用递归算法对二叉树进行各种运算.注意,对二叉排序树进行中序遍历的话可以得到一个有序递增的序列.
2. 二叉排序树的查找

二叉排序树的查找比较简单,它是从根结点开始,沿着某一个分支逐层向下进行比较的过程.若二叉排序树是非空的,将给定值与根结点关键字进行比较,若相等,则查找成功;若不相等,则当根结点关键字大于给定的关键字的时候,从左子树中查找,否则就从右子树中查找.算法的实现如下所示

template<typename ElementType>
BiNode<ElementType>* SearchNodes(BiNode<ElementType>*root,ElementType data){
        if(root==NULL) return NULL;
        BiNode<ElementType>* ptr = root;
        while(ptr!=NULL && data!=ptr->GetData()){
            if(data<ptr->GetData()) ptr=ptr->GetLeft();
            else if (data>ptr->GetData()) ptr=ptr->GetRight();
        }
        return ptr;
 }

当然,二叉树的查找可以使用递归的方式进行查找,但是执行的效率比较低.
3. 二叉排序树的插入
二叉排序树插入的过程是:若原二叉树排序为空,则直接插入结点,否则,若关键字 k k k小于根结点的关键字,则插入到左子树当中,若关键字 k k k大于根结点关键字,则插入到右子树当中.
根据上述的算法可以得到以下的递归算法表示

template<typename ElementType>
void CreateNodes(BiNode<ElementType>* root,ElementType data){
        if (data>root->GetData()){
            if(root->GetRight()!=NULL) CreateBiTree(root->GetRight(),data);
            else {
                BiNode<ElementType>*ptr = new BiNode<ElementType>(data);
                root->SetRight(ptr);
            }
        }else if(data<root->GetData()){
            if(root->GetLeft()!=NULL) CreateBiTree(root->GetLeft(),data);
            else{
                BiNode<ElementType>*ptr = new BiNode<ElementType>(data);
                root->SetLeft(ptr);
            }
        } else return;
}
template<typename ElementType>
BiNode<ElementType>* InsertNode(BiNode<ElementType>* root,ElementType data){
    if(root==NULL) return new BiNode<ElementType>(data);
    else {
        CreateNodes(root,data);
        return root;
    }
}
  1. 二叉排序树的构造
    二叉排序树的构造依赖于二叉树的结点数值的插入,是将一个序列的元素插入到二叉排序树中的适当位置即可.这样可以直接参考二叉排序树的插入过程进行适当的修改即可:
template<typename ElementType> 
BiNode<ElementType>* CreateTree(ElementType arr[],unsigned int n){
    BiNode<ElementType>*ptr = NULL;
    for(unsigned int k=0;k<n;k++) ptr = InsertNode(ptr,arr[k]);
    return ptr;
}
  1. 二叉排序树的删除
    在二叉树删除一个结点的时候,不能直接以该结点为根的子树上的结点都删除,必须是先把被删除结点从存储二叉排序树的链表上摘下来,将因删除结点而断开的二叉链表重新链接起来,同时确保二叉排序树的性质不会丢失.
    删除的操作分为以下的三种情况:
    ① 如果被删除的结点z是叶子结点,则直接删除,不会破坏二叉排序树的性质;
    ② 若结点z只有一棵左子树或者右子树,则让z的子树成为z父结点的子树,并且代替z的位置;
    ③ 若结点有左子树和右子树,则令z的后继(或者是直接前驱)替代z,然后从二叉排序树中删除这个直接后继(直接前驱),这样就转换成为了第一种或者是第二种的情况.

用程序的方式表示删除结点的操作如下所示

template<typename ElementType> 
void DeleteNodes(BiNode<ElementType>*root,BiNode<ElementType>*node){
        if(root==NULL||node==NULL) return ;
        if(node->GetLeft()==NULL && node->GetRight()==NULL){
            BiNode<ElementType>*parent = BinaryTree<ElementType>::GetParent(root,node);
            if(parent->GetLeft() == node) parent->SetLeft(NULL);
            else parent->SetRight(NULL);
            delete node;
        }else if(node->GetLeft()!=NULL && node->GetRight()!=NULL){
            //找到最左下角的结点
            BiNode<ElementType>* ptr = node;
            Stack<BiNode<ElementType>*> st;
            while(ptr->GetLeft()!=NULL){
                st.Push(ptr);
                ptr = ptr->GetLeft();
            }
            //交换数值
            ElementType data = node->GetData();
            node->SetData(ptr->GetData());
            ptr->SetData(data);
            //删除数据
            DeleteNodes(root,ptr);
        }else{
            BiNode<ElementType>*parent = BinaryTree<ElementType>::GetParent(root,node);
            if(node->GetLeft()!=NULL && node->GetRight()==NULL) parent->SetLeft(node->GetLeft());
            if(node->GetLeft()==NULL && node->GetRight()!=NULL) parent->SetRight(node->GetRight());
            delete node;
        }
}
template<typename ElementType> 
void DeleteNode(BiNode<ElementType>*root,ElementType data){
    BiNode<ElementType>*ptr = SearchNode(root,data);
    BiSortTree<ElementType>::DeleteNodes(root,ptr);
}
  1. 二叉排序树查找效率分析

设二叉排序树的高度为 H H H,那么其中插入和删除操作的运行时间均为 O ( H ) O(H) O(H),但是在最坏的情形下,即构造二叉怕叙述的输入序列是有序的,那么就会形成一个倾斜的单支树,此时二叉排序树的性能显著变坏,输的高度增加为元素个数 N N N,此时搜索的效率为 O ( N ) O(N) O(N).
注意:二叉排序树和二分查找相似.平均时间性能而言的话,二叉排序树查找和二分法查找的性能差不多.但是二分查找的判定树是唯一的,二叉排序树是不唯一的,相同的关键字其插入的顺序不一样会形成不一样的二叉排序树.

5.2.2 平衡二叉树(AVL树)

  1. 平衡二叉树的定义

平衡二叉树是对排序二叉树的一种优化方法.我们规定在插入和删除二叉树结点的时候,要保证任意结点的左子树、右子树高度之差的绝对值不超过1,这样的二叉树称为平衡二叉树.定义结点左子树右子树的高度之差为该结点的平衡因子,则平衡二叉树结点的平衡因子的值只可能是-1,0,1.
平衡二叉树的定义如下所示:它是一棵空树,或者是有以下性质的二叉树:它的左子树和右子树都是平衡二叉树,并且左子树和右子树的高度差的绝对值不超过1.

  1. 平衡二叉树的插入与删除

二叉排序树保证平衡的基本思想:每当在二叉树中插入(或者删除)一个结点的时候,首先要检查其插入路径上的结点是否因为与此次操作而导致了不平衡.若导致了不平衡的话,则先找到插入路径上距离插入结点最近的平衡因子绝对值大于1的结点A,再对以A为根的子树,在保持二叉排序树特性的前提下,调整各结点的位置关系,使之重新达到平衡.
注意: 每次调整的对象都是最小不平衡子树,即在插入路径上离插入结点最近的平衡因子的绝对值大于1的结点作为根的子树.
平衡二叉树插入过程的前半部分与二叉排序树是一致的,但是在插入新的结点之后,如果造成了查找路径上某个结点不再平衡,需要作出相应的调整.一般地归纳为下面的4种情况:
① LL平衡旋转(右单旋转)
这种情况是由于在结点A的左孩子 L L L的左子树上插入了新结点, A A A的平衡因子由1增加至2,导致以A为根的子树失去平衡,需要一次向右旋转操作.将A的左孩子B向右上旋转代替A成为根结点,将A结点向右下旋转成为B的右子树的根节点,而B的原右子树则作为A结点的左子树.
下图就是演示这种情况
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PlTncYWk-1609564932515)(LL平衡旋转.jpg)]
② RR平衡旋转(左单旋转)
这种情况是由于在结点A的右孩子 R R R的右子树上插入了新结点, A A A的平衡因子由-1减少至-2,导致以A为根的子树失去平衡,需要一次向左旋转操作.即将A的右孩子B向左上旋转代替A称为根结点,将A结点向左下旋转成为B左子树的根结点,而B的原左子树则作为A结点的右子树.
下图演示即为这种情况
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-03krmWZN-1609564932518)(RR平衡旋转.jpg)]
③ LR平衡旋转(先左后右双旋转)
这种情形是由于在结点A的左孩子 L L L的右子树上插入了新结点, A A A的平衡因子由1增加至2,导致以A为根节点的子树失去平衡,需要先进行左旋转操作然后进行右旋转操作.首先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后将该C结点向右上旋转提升到A结点的位置.
旋转的过程如下图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LQft5bWA-1609564932519)(LR平衡旋转.jpg)]
④ RL平衡旋转(先右后左双旋转)
这种情况是由于在结点A的右孩子 R R R的左子树上插入了新结点, A A A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要进行两次旋转的操作,先右旋转然后左旋转.先将A结点的右孩子B的左子树的根结点C向右上旋转提升到B结点的位置,然后在将该C结点向左上旋转提升到A结点的位置.
旋转的过程如下图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-63CRtYlk-1609564932520)(RL平衡旋转.jpg)]
注意: LR和RL旋转的时候,究竟新结点插入在C的左子树和右子树上,并不影响旋转的过程.
所以在平衡旋转二叉树的时候,用程序代码复现如下所示:

template<typename ElementType>
BiNode<ElementType>* balance(BiNode<ElementType>*root){
        if(root == NULL) return root;
        int left = BinaryTree<ElementType>::GetTreeHeight(root->GetLeft());
        int right = BinaryTree<ElementType>::GetTreeHeight(root->GetRight());
        if(left-right>AVLTree<ElementType>::ALLOW_IMBALANCE){
            //判断左子树的左边高还是右边高
            unsigned int Lleft = BinaryTree<ElementType>::GetTreeHeight(root->GetLeft()->GetLeft());
            unsigned int Lright = BinaryTree<ElementType>::GetTreeHeight(root->GetLeft()->GetRight());
            //std::cout<<Lleft<<","<<Rleft<<std::endl;
            if(Lleft>Lright) return rotateWithLeft(root);
            else return doubleWithLeft(root);
        }else if(right-left>AVLTree<ElementType>::ALLOW_IMBALANCE){
            //判断右子树的左边高还是右边高
            unsigned int Rleft = BinaryTree<ElementType>::GetTreeHeight(root->GetRight()->GetLeft());
            unsigned int Rright = BinaryTree<ElementType>::GetTreeHeight(root->GetRight()->GetRight());
            //std::cout<<Lright<<","<<Rright<<std::endl;
            if(Rright>Rleft)return rotateWithRight(root);
            else return doubleWithRight(root);
        }else return root;
}
//LL 旋转
template<typename ElementType>
BiNode<ElementType>* rotateWithLeft(BiNode<ElementType>*k2){
        BiNode<ElementType>* k1 = k2->GetLeft();
        //开始旋转
        k2->SetLeft(k1->GetRight());
        k1->SetRight(k2);
        return k1;
}
//LR 旋转
template<typename ElementType>
BiNode<ElementType>* doubleWithLeft(BiNode<ElementType>*k3){
        k3->SetLeft(AVLTree<ElementType>::rotateWithRight(k3->GetLeft()));
        return AVLTree<ElementType>::rotateWithLeft(k3);
}
//RR 旋转
template<typename ElementType>
BiNode<ElementType>* rotateWithRight(BiNode<ElementType>*k2){
        BiNode<ElementType>* k1 = k2->GetRight();
        //开始旋转
        k2->SetRight(k1->GetLeft());
        k1->SetLeft(k2);
        return k1;
}
//RL 旋转
BiNode<ElementType>* doubleWithRight(BiNode<ElementType>*k3){
        k3->SetRight(AVLTree<ElementType>::rotateWithLeft(k3->GetRight()));
        return AVLTree<ElementType>::rotateWithRight(k3);
}

所以在每次插入或者是删除结点的时候,需要将插入的根节点进行平衡调节.插入结点的代码如下所示:

template<typename ElementType>
void InsertNodes(BiNode<ElementType>**rootptr,ElementType data){
        BiNode<ElementType>* root = (*rootptr);
        if(root == NULL) {
            (*rootptr) = new BiNode<ElementType>(data);
            return;
        }
        if (data>root->GetData()){
            if(root->GetRight()!=NULL) {
                BiNode<ElementType>** tmp = new BiNode<ElementType>*;
                (*tmp) = root->GetRight();
                InsertNodes(tmp,data);
                root->SetRight(*tmp);
                delete tmp;
            }
            else {
                BiNode<ElementType>*ptr = new BiNode<ElementType>(data);
                root->SetRight(ptr);
            }
        }else if(data<root->GetData()){
            if(root->GetLeft()!=NULL) {
                BiNode<ElementType>** tmp = new BiNode<ElementType>*;
                (*tmp) = root->GetLeft();
                InsertNodes(tmp,data);
                root->SetLeft(*tmp);
                delete tmp;
            }
            else{
                BiNode<ElementType>*ptr = new BiNode<ElementType>(data);
                root->SetLeft(ptr);
            }
        } else return ;
        (*rootptr) = balance(root);
}

删除结点的代码如下所示

template<typename ElementType>
void DeleteNodes(BiNode<ElementType>**rootptr,BiNode<ElementType>*node){
        BiNode<ElementType>* root = (*rootptr);
        if(root==NULL||node==NULL) return ;
        if(node->GetLeft()==NULL && node->GetRight()==NULL){
            BiNode<ElementType>*parent = BinaryTree<ElementType>::GetParent(root,node);
            if(parent->GetLeft() == node) parent->SetLeft(NULL);
            else parent->SetRight(NULL);
            delete node;
        }else if(node->GetLeft()!=NULL && node->GetRight()!=NULL){
            //找到最左下角的结点
            BiNode<ElementType>* ptr = node;
            Stack<BiNode<ElementType>*> st;
            while(ptr->GetLeft()!=NULL){
                st.Push(ptr);
                ptr = ptr->GetLeft();
            }
            //交换数值
            ElementType data = node->GetData();
            node->SetData(ptr->GetData());
            ptr->SetData(data);
            //删除数据
            DeleteNodes(rootptr,ptr);
            root = (*rootptr);
        }else{
            BiNode<ElementType>*parent = BinaryTree<ElementType>::GetParent(root,node);
            if(node->GetLeft()!=NULL && node->GetRight()==NULL) parent->SetLeft(node->GetLeft());
            if(node->GetLeft()==NULL && node->GetRight()!=NULL) parent->SetRight(node->GetRight());
            delete node;
        }
        root = balance(root);
        (*rootptr) = root;
}
  1. 平衡二叉树的查找以及效率分析

平衡二叉树上查找的方法与二叉排序树上查找的方式是一致的,并且在查找的过程中和给定值进行比较的关键字的个数不超过树的深度.含有n个结点平衡二叉树的最大深度为 O ( log ⁡ 2 n ) O(\log_{2}n) O(log2n),平衡二叉树的平均查找长度为 O ( log ⁡ 2 n ) O(\log_{2}n) O(log2n).

小结

本节主要讲述了二叉排序的基本定义和平衡二叉树的基本算法,当然对于平衡二叉树的算法改进还有很多,下一小节准备讲述一下红黑树的应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值