zoukankan      html  css  js  c++  java
  • 手撸平衡二叉树!保证讲懂平衡过程!

    md还不会用博客园的编辑器,排版炸了,可以去csdn看。

    https://blog.csdn.net/wonder13579/article/details/105415879

     

    首先我们来复习一下基础知识吧

    二叉查找树

    左子树的所有节点,值都小于本节点,右子树的所有节点,值都大于本节点。

    由于这个性质,在查找时可以把要查值和节点比较,如果大于当前节点,就去右子树找,小于就去左子树找。这样,每次比较一个节点,就把待查数据排除了一半,可以达到logn的查找效率。

    10次比较,就能处理1,024个数据。

    20次比较,就能处理1,048,576个数据。

    此外,中序遍历一颗二叉查找树,显然结果应该是升序的。

    缺点:形成依赖于节点插入顺序,当有序插入时,会退化为链表。

     

    例:当依次插入1,2,3,4,就会产生这样的查找树,此时它跟一个链表是一样的。为了解决这个问题,出现了平衡二叉树。

     

    平衡二叉树

    定义:是一颗二叉查找树,并且左右子树深度差距不超过1,其左右子树也是平衡二叉树。

    平衡二叉树通过旋转操作实现平衡。分LL,LR,RR,RL四种,观察时注意看图。

    首先记住,平衡二叉树还是一个二叉查找树,它的中序遍历结果还是有序的。

    而旋转操作,只是把中序遍历结果中,中心节点的位置,向左或者向右移动了一位,避免退化为链表。

     

     

    红黑树

    简单介绍,以后可能把红黑树也实现了。给平衡二叉树添加了红黑性质,只保证黑色节点是完全平衡的,这让它的平衡操作没有那么频繁,但是最差的情况下,只比最好的情况多一倍。

    定义

    (1)每个节点或者是黑色,或者是红色。

    (2)根节点是黑色。

    (3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]

    (4)如果一个节点是红色的,则它的子节点必须是黑色的。

    (5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

     

    下面开始讨论实现过程

    讨论具体过程之前,我们先给出二叉树的定义,以及我使用的工具函数。

    工具函数包括一个disp,用来把二叉树转换为leetcode格式,然后就可以用leetcode可视化节点了。还有一个显示二叉树主要数据的,方便调试。

     

     

     

    平衡二叉树的旋转

    注意,L指left左,R指right右,LL就是跟左左节点有关,以此类推。

    LL旋转,调整根,根左边节点,根左边节点的左边节点。

    RR旋转,调整根,根右边节点,根右边节点的右边节点。

    LR旋转,调整根,根左边节点,根左边节点的右边节点。

    RL旋转,调整根,根右边节点,根右边节点的左边节点。

     

    这个旋转过程,算是平衡二叉树最难的部分,光看懂旋转都很难。我找到一种比较简单的方法总算把它给理解了。

    首先记住,平衡二叉树还是一个二叉查找树,它的中序遍历结果还是有序的。

    而旋转操作,只是把中序遍历结果中,中心节点的位置,向左或者向右移动了一位,避免退化为链表。

    由此我们可以直接得到旋转前后的节点顺序,剩下的就是观察那些节点被变化了,编程实现这些变化,即可完成旋转操作。

    口说无凭,我们根据实例来观察。

     

    RR旋转

     

    如图所示,进行了一次RR旋转。

    中序遍历的结果都是[1,2,3,4,5,6,7]。不同的是旋转后4成为了根节点。

    仔细观察旋转过程,发现变化的节点是2,4,3.

    即4变成了根节点,3变成了2的右节点,2变成了4的左节点。

     

    想想为什么要这么做,下面一段注意结合图理解。调整前根节点的左右子树高度差大于1,不符合平衡二叉树定义,现在换4做根节点,他就可以符合了。而原来的2节点比4小,应该成为4的左节点。原来4的左节点3就消失了,应该找个位置再把它放回去。好了,清楚了,按这个顺序把它写成代码:

     

     

    其他旋转过程

    有了RR旋转,LL旋转就是左右换了一下,差不多。看看LR旋转

     

    对于LR旋转其实就是先对根的左节点做一次RR旋转,再对根自己做一次LL旋转。

    全部四种旋转过程

     

     

    实现平衡二叉树的插入

    如果是一个简单的查找二叉树,只要递归插入新节点即可。

     

     

    平衡二叉树在这个基础上,加入计算左右子树高度差的过程。如果不平衡,只要进行对应的旋转操作即可让它重新平衡。

     

     

    手写平衡二叉树的删除

    删除比插入要复杂的多。

    需要在子树中找到一个节点,然后用它替换要删除的节点的位置。

    同时每次调整都需要重新计算节点深度,对不平衡的节点需要进行旋转操作。

     

    删除的主要过程

    首先,递归找到需要删除的节点,类似于查找,如果目标值大于当前节点,在右边找,否则去左边找。

    对要删除的节点,在其深度更大的一个子树进行操作(递归)。

    如左子树深度更大,就在左子树查找最大的节点,用它替换要删除的节点的位置。

    此处查找最大节点过程写了个子函数,依然使用递归,在后面介绍。

    最后,需要重新计算递归路径上所有节点的高度,如果出现了不平衡,需要使用旋转调整。

     

     

    删除子过程,寻找替代节点

    找到替代节点并把它摘出来的过程,因为左右子树处理过程类似,用在左子树中找最大举例。

    因为平衡二叉树还是有序的,只要一直取右子树即可找到最大节点。

    注意找到后,需要调整替代节点的父节点,因为替代节点被摘出去了,父节点不能再指向它。

    父节点应该指向替代节点的左节点。

    对于右子树的处理过程类似。

     

     

    调整子过程

    对于一个节点,如果左子树高度比右子树高度大超过1,需要调整左子树。

    通过判断要删除的值,和左子树值的大小,可以确定该使用LL旋转,还是LR旋转。

    在每个递归的结束,都需要进行调整。

     

     

    测试

    我把1到15依次加入平衡二叉树,然后依次删除根节点,输出每个操作完成后的树。

    结果正常,输出如下,可以拿到leetcode上看看效果。

    [1,null,null]

    [1,null,2,null,null]

    [2,1,3,null,null,null,null]

    [2,1,3,null,null,null,4,null,null]

    [2,1,4,null,null,3,5,null,null,null,null]

    [4,2,5,1,3,null,6,null,null,null,null,null,null]

    [4,2,6,1,3,5,7,null,null,null,null,null,null,null,null]

    [4,2,6,1,3,5,7,null,null,null,null,null,null,null,8,null,null]

    [4,2,6,1,3,5,8,null,null,null,null,null,null,7,9,null,null,null,null]

    [4,2,8,1,3,6,9,null,null,null,null,5,7,null,10,null,null,null,null,null,null]

    [4,2,8,1,3,6,10,null,null,null,null,5,7,9,11,null,null,null,null,null,null,null,null]

    [8,4,10,2,6,9,11,1,3,5,7,null,null,null,12,null,null,null,null,null,null,null,null,null,null]

    [8,4,10,2,6,9,12,1,3,5,7,null,null,11,13,null,null,null,null,null,null,null,null,null,null,null,null]

    [8,4,12,2,6,10,13,1,3,5,7,9,11,null,14,null,null,null,null,null,null,null,null,null,null,null,null,null,null]

    [8,4,12,2,6,10,14,1,3,5,7,9,11,13,15,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]

    [8,4,12,2,6,10,14,1,3,5,7,9,11,13,15,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]

    [9,4,12,2,6,10,14,1,3,5,7,null,11,13,15,null,null,null,null,null,null,null,null,null,null,null,null,null,null]

    [10,4,12,2,6,11,14,1,3,5,7,null,null,13,15,null,null,null,null,null,null,null,null,null,null,null,null]

    [11,4,13,2,6,12,14,1,3,5,7,null,null,null,15,null,null,null,null,null,null,null,null,null,null]

    [12,4,14,2,6,13,15,1,3,5,7,null,null,null,null,null,null,null,null,null,null,null,null]

    [7,4,14,2,6,13,15,1,3,5,null,null,null,null,null,null,null,null,null,null,null]

    [6,4,14,2,5,13,15,1,3,null,null,null,null,null,null,null,null,null,null]

    [5,3,14,2,4,13,15,1,null,null,null,null,null,null,null,null,null]

    [4,2,14,1,3,13,15,null,null,null,null,null,null,null,null]

    [13,2,14,1,3,null,15,null,null,null,null,null,null]

    [14,2,15,1,3,null,null,null,null,null,null]

    [3,2,15,1,null,null,null,null,null]

    [2,1,15,null,null,null,null]

    [15,1,null,null,null]

    [1,null,null]

     

    资料

    红黑树,超强动静图详解,简单易懂

    https://zhuanlan.zhihu.com/p/79980618?utm_source=cn.wiz.note

     

    源代码

    /*

    平衡二叉树

    */

    // #include "tool.h"

    #include<iostream>

    #include<stdio.h>

    #include<vector>

    #include<queue>

    using namespace std;

     

     

    class AVLTree{

    public:

        class AVLTreeNode{

        public:

            int val;

            int height;

            AVLTreeNode *left;

            AVLTreeNode *right;

        public:

            AVLTreeNode(int _val){

                val=_val;

                height=0;

                left=NULL;

                right=NULL;

            }

        };

        //不得不使用全局变量,有些递归函数需要返回多个值

        AVLTreeNode *_my_temp;

        /*这部分是为了输出树的结构信息,按照leetcode树节点可视化格式输出*/

        void Disp(){

            if(root==NULL){

                cout<<"[]"<<endl;

                return;

            }

            vector<int> ans;

            queue<AVLTreeNode *> q;

            q.push(root);

            while(!q.empty()){

                AVLTreeNode *p = q.front();q.pop();

                if(p==NULL){

                    ans.push_back(-1);

                }else{

                    ans.push_back(p->val);

                    

                    if(p->left!=NULL)q.push(p->left);

                    else q.push(NULL);

                    if(p->right!=NULL)q.push(p->right);

                    else q.push(NULL);

                }

            }

            cout<<"["<<ans[0];

            for(int i=1;i<ans.size();i++){

                if(ans[i]==-1)cout<<",null";

                else cout<<","<<ans[i];

            }

            cout<<"]"<<endl;

        }

        //输出所有节点信息。

        void Show(AVLTreeNode *node){

            printf("val=%d,height=%d,path=%d,left=%d,right=%d "

                ,node->val,node->height,node,node->left,node->right);

            if(node->left!=NULL)Show(node->left);

            if(node->right!=NULL)Show(node->right);

        }

        void Show(){

            Show(root);

        }

        /****************************************************************/

        

        AVLTreeNode *root=NULL;

     

     

        //取得节点高度

        int GetHeight(AVLTreeNode * node){

            if(node==NULL)return -1;

            return node->height;

        }

        //调整节点高度

        void SetHeight(AVLTreeNode * node){

            int leftDeep = node->left!=NULL?node->left->height:-1;

            int rightDeep = node->right!=NULL?node->right->height:-1;

            node->height = max(leftDeep,rightDeep)+1;

            // printf("val=%d,leftdeep=%d,rightdeep=%d,height=%d ",node->val,leftDeep,rightDeep,node->height);

        }

        void Add(int val){

            root = Add(root,val);

            Disp();

        }

        //插入操作

        AVLTreeNode *Add(AVLTreeNode * node,int val){

            if(node==NULL){

                node = new AVLTreeNode(val);

                return node;

            }

            if(val < node->val){

                node->left=Add(node->left,val);

                if(GetHeight(node->left)-GetHeight(node->right)>1){

                    if(val<node->left->val)node=LLRotate(node);

                    else node=LRRotate(node);

                }

            }else{

                node->right=Add(node->right,val);

                if(GetHeight(node->right)-GetHeight(node->left)>1){

                    if(val>node->right->val)node=RRRotate(node);

                    else node=RLRotate(node);

                }

            }

            SetHeight(node);

            return node;

        }

     

     

        //删除操作

        bool Delete(int val){

            root = Delete(root,val);

            return false;

        }

        AVLTreeNode *Delete(AVLTreeNode * node,int val){

            //首先查找要删除的节点

            if(node==NULL)return NULL;

            else if(node->val<val)node->right = Delete(node->right,val);

            else if(node->val>val)node->left  = Delete(node->left,val);

            else {

                // printf("Delete find target,path=%d ",node);

     

     

                //在高度高的一侧,寻找替代节点

                //左侧

                if(GetHeight(node->left)>GetHeight(node->right)){

                    node->left = DeleteSubLeft(node->left);

                }else{

                //右侧

                    node->right = DeleteSubRight(node->right);

                }

     

     

                AVLTreeNode *temp=_my_temp;

                if(temp!=NULL){

                    //这个值是从递归过程赋值的全局变量取回的,是要替代的节点

                    // printf("find replace node,path=%d ",temp);

                    temp->left=node->left;

                    temp->right=node->right;

                }

                delete node;

                node=temp;

            }

            //重新计算每个节点高度,并计算

            SetHeight(node);

            node = Adjust(node,val);

            return node;

        }

        //寻找替代节点的过程

        AVLTreeNode *DeleteSubLeft(AVLTreeNode * node){

            if(node==NULL)return NULL;

            if(node->right!=NULL){

                node->right = DeleteSubLeft(node->right);

                SetHeight(node);

                node = Adjust(node,_my_temp->val);

                return node;

            }

            //这里必须返回多个值,用了全局变量

            _my_temp = node;

            return node->left;

        }

        //寻找替代节点的过程

        AVLTreeNode *DeleteSubRight(AVLTreeNode * node){

            if(node==NULL)return NULL;

            if(node->left!=NULL){

                node->left = DeleteSubRight(node->left);

                SetHeight(node);

                node = Adjust(node,_my_temp->val);

                return node;

            }

            //这里必须返回多个值,用了全局变量

            _my_temp = node;

            return node->right;

        }

        //调整节点,通过旋转调整平衡二叉树

        //添加一个参数,表示引起改变的值的大小,方便判断进行那种旋转。

        AVLTreeNode *Adjust(AVLTreeNode * node,int val){

            if(node==NULL)return NULL;

            int leftHeight = GetHeight(node->left);

            int rightHeight = GetHeight(node->right);

            if(leftHeight-rightHeight>1){

                if(val<node->left->val)

                    node = LLRotate(node);

                else node = LRRotate(node);

            }else if(rightHeight-leftHeight>1){

                if(val>node->right->val)

                    node=RRRotate(node);

                else node = RLRotate(node);

            }

            SetHeight(node);

        }

        //查找

        AVLTreeNode *Find(int target){

            AVLTreeNode *node = Find(root,target);

            printf("find ans path=%d ",node);

            return node;

        }

        AVLTreeNode *Find(AVLTreeNode * node,int target){

            if(node==NULL)return NULL;

            if(node->val!=target){

                if(target>node->val)

                    return Find(node->right,target);

                else return Find(node->left,target);

            }

            return node;

        }

        //寻找某节点下最小的节点

        AVLTreeNode *GetMin(AVLTreeNode *node){

            while(node->left==NULL)return node;

            return GetMin(node->left);

        }

        //寻找某节点下最大的节点

        AVLTreeNode *GetMax(AVLTreeNode *node){

            while(node->right==NULL)return node;

            return GetMin(node->right);

        }

     

     

        /*四种旋转操作*/

        AVLTreeNode * LLRotate(AVLTreeNode *node){

            if(node==NULL||node->left==NULL)return node;

            AVLTreeNode *temp=node->left;

            node->left=temp->right;

            temp->right=node;

            SetHeight(node);

            SetHeight(temp);

            return temp;

        }

        AVLTreeNode * RRRotate(AVLTreeNode *node){

            if(node==NULL||node->right==NULL)return node;

            AVLTreeNode *temp=node->right;

            node->right=temp->left;

            temp->left=node;

            SetHeight(node);

            SetHeight(temp);

            return temp;

        }

        AVLTreeNode *LRRotate(AVLTreeNode *node){

            //对左子树RR旋转

            node->left = RRRotate(node->left);

            //对node LL旋转

            node = LLRotate(node);

        }

        AVLTreeNode *RLRotate(AVLTreeNode *node){

            //对右子树LL旋转

            node->right = LLRotate(node->right);

            //对node RR旋转

            node = RRRotate(node);

        }

        //测试用,用来访问类的对象

        void Test(){

            AVLTreeNode *node=NULL;

            //根据顺序把数据插入平衡二叉树

            // int size=15,all[]={8,4,12,2,6,10,14,1,3,5,7,9,11,13,15};

            for(int i=1;i<=15;i++){

                Add(i);

            }

            Disp();

            //依次删除平衡二叉树顶点的元素

            for(int i=1;i<15;i++){

                Delete(root->val);

                Disp();

                // Show();

            }

        }

    };

     

     

     

     

    int main(){

        AVLTree *tree = new AVLTree();

        tree->Test();

        return 0;

    }

     

     

     

     

  • 相关阅读:
    Win7下使用TortoiseGit设置保存密码
    MacOS软件清单
    ubuntu安装python
    Mac使用SSH连接远程服务器
    CentOS常用命令
    Docker追加容器端口映射
    Docker安装CentOS7
    Windows操作路由表
    Docker部署MySQL8并实现远程连接
    Qt——容器类(译)
  • 原文地址:https://www.cnblogs.com/Wonder007/p/12668458.html
Copyright © 2011-2022 走看看