二叉平衡树

Balanced Binary Tree Link

平衡二叉树性质

//平衡二叉树:首先是一颗二叉查找树,构建这颗二叉查找树的过程中进行平衡化的处理形成二叉平衡树
    //性质
        //a)它的左子树和右子树都是平衡二叉树
        //b)任意节点的左子树和右子树的高度之差不超过1
    //平衡因子的概念:该节点的左子树高度减去他的右子树高度(或右子树高度减去左子树高度)
    //那么最为一颗平衡二叉树,上面任意一节点的平衡因子只能是 -1、0、1三个值中的一个。即。
        //节点的平衡因子 = 左子树高度 - 右子树高度

    //平衡二叉树插入操作后的平衡性调整以及实现代码
        //插入和删除两种操作会改变平衡二叉树的平衡性
    //最小不平衡子树:需要进行平衡性调整的子树称为 “最小不平衡子树”
        //只要将最小不平衡子树进行平衡性调整(高度复原),整个二叉查找树就会恢复平衡。
        //平衡性调整是对二叉树进行“旋转”操作来达成的。
    //四种旋转方式:前两种称为“单旋转”,后两种称为“双旋转”
        //a)左旋转
        //b)右旋转
        //c)先左后右旋转
        //d)先右后左旋转
    //对于最小不平衡子树(将该子树命名为 A)的产生又分为四种情况
        //a)在A的 "左孩子的左子树"中插入节点导致A不平衡(简称LL:L代表Left)。此时需要通过“右旋转”调整平衡
        //b)在A的 "右孩子的右子树"中插入节点导致A不平衡(简称RR:R代表Right)。此时需要通过“左旋转”调整平衡
        //a)在A的 "左孩子的右子树"中插入节点导致A不平衡(简称LR)。此时需要通过“先左后右”调整平衡
        //a)在A的 "右孩子的左子树"中插入节点导致A不平衡(简称RL)。此时需要通过“先右后左”调整平衡

        //1)在A的“左子树的左子树”中插入节点导致A不平衡   【右侧导航栏右旋转】
            //将失衡的节点A也就是最上边的这个失衡的节点向右侧旋转来作为节点B的右孩子
                //如果节点A有右孩子则该右孩子也要跟着节点A一起旋转,如果节点B原来有右孩子
                    //则首先将节点B这个右孩子作为节点A的左孩子。

        //2)在A的“右子树的右子树”中插入节点导致A不平衡  【右侧导航栏左旋转】
            //将失衡的节点A也就是最上边的这个失衡的节点向左侧旋转来作为节点B的左孩子
                //如果节点A有左孩子则该左孩子也要跟着节点A一起旋转,如果节点B原来有左孩子
                    //则首先将节点B这个左孩子作为节点A的右孩子。

        //3)在A的“左孩子的右子树”中插入节点导致A不平衡(LR)【右侧导航栏先左后右旋转】
            //通过先左后右旋转让最小不平衡子树恢复平衡
                //3.1)将节点B(60),也就是从上向下数第二行的节点向左侧旋转让其向左下方下落,如果节点B有左孩子则该左孩子也要跟着节点B一起旋转
                    //那么节点B的右子树(从c(70)为根,可以看成70这个节点为c)就被提起来顶替了节点B的位置。
                    //得到的效果就是节点B(60)变成了其原右孩子(节点70)的左孩子
                    //a)如果节点C(节点70)原来有左孩子,则将这个左孩子跟着节点B(节点60)的右孩子
                    //b)如果节点C(节点70)原来有右孩子,则将这个右孩子跟着c(节点70)一起旋转
                //3.2)将失衡的节点A(节点95)也就是最上边失衡的节点向右侧旋转来作为新节点B(节点70)的右孩子
                    //a)如果节点70有右孩子,则将这个右孩子变成95这个节点的左孩子
                    //b)如果节点95原来有右孩子,则这个右孩子要跟着节点95一起旋转。
                    
        //4)在A的“右孩子的左子树”中插入节点导致A不平衡(RL)【右侧导航栏先右后左】
        //如何通过先右后左旋转让最小不平衡子树恢复平衡
                //4.1)将节点B(节点120)也就是从上向下数第二行的节点向右侧旋转让其向右下方下落,如果节点B有右孩子则该右孩子也要跟着节点B一起旋转
                    //那么节点B的左子树(以c(95)为根,可以看成95这个节点为c)就被提起来顶替了节点B的位置。
                    //得到的效果就是节点B(节点120)变成了其原左孩子(节点95)的右孩子
                    //a)如果节点C(节点95)原来有左孩子,则这个左孩子要跟着节点C(95)一起旋转
                    //b)如果节点C(节点95)原来有右孩子,则将这个右孩子变成了节点B(节点120)的左孩子
                //4.2)将失衡的节点A(节点60)也就是最上边失衡的节点向左侧旋转来作为新节点B(节点95)的左孩子
                   //a)如果节点C(节点95)原来有左孩子,则将这个左孩子变成了节点60的右孩子。
                   //b)如果节点60原来有左孩子,则这个左孩子要跟着节点60一起旋转。  

        //综合测试:这组数据大概会进行其次平衡性调整.{12,4,1,3,7,8,10,9,2,11,6,5}

    //平衡二叉树删除操作后的平衡性调整及实现代码
    //具体的删除过程分为三个主要步骤
    //a)在平衡二叉树中查找要删除的节点
    //b)针对所要删除的节点的子节点个数不同,有如下集中情况需要处理
        //b.1)要删除的节点是叶子节点,则直接把该节点删除并指向该被删除节点的父亲节点的相应孩子指针设置为空。
        //b.2)要删除的节点有左子树或右子树(单分支节点),则把该节点删除并更新指向该被删除节点的父节点的相应孩子指针,
                    //相应孩子指针,让该指针指向要删除节点的非空的子节点(节点替换)。
        //b.3)要删除的节点左子树和右子树都不为空,则
            //b.3.1)找到要删除节点的左子树的最右下节点(也可以找到这个要删除节点右子树的最左下节点)
            //b.3.2)将该节点的值替换到要删除的节点上;
            //b.3.3)接着把刚刚找到的那个最右下节点删除即可
    //c)平衡性调整:从被删除的节点从根节点回溯(从下向上寻找)
            //c.1)如果回溯发现所有节点都是平衡的,则不需要调整(表示删除该节点并不影响二叉树的平衡性)
            //c.2)如果回溯找到了第一个不平衡的节点(以该节点为根的这棵需要进行平衡性调整的子树前面已经说过,叫做“最小不平衡子树”。
                //这个不平衡节点的平衡因子为 -2 或者 2,则
                //c.2.1)如果平衡因子(节点10)为 -2,参考图,【右侧导航栏,如果平衡因子为-2】
                    //-)如果其右孩子(节点12)的平衡因子是-1, 则说明该右孩子的右子树(以节点13为根)更高
                        //这种总情形等同于RR型插入操作所要进行的平衡性调整,也就是要通过 “左旋转” 来恢复二叉树的平衡
                    //-)如果其右孩子(节点12)的平衡因子是1,则说明该右孩子左子树(以节点11为根)更高,
                            //这种情形等同于RL型插入操作所要进行的平衡性调整,也就是要通过“先右后左旋转”来恢复二叉树的平衡
                    //-)如果其右孩子(节点12)的平衡因子是0,则既可以通过 “左旋转” 也可以通过 “先右后左旋转”来恢复平衡
            //c.2.2)如果平衡因子(节点10)为 2,参考图,     【右侧导航栏,如果平衡因子为2】
                    //-)如果其左孩子(节点12)的平衡因子是-1, 则说明该左孩子的右子树(以节点9为根)更高
                        //这种总情形等同于LR型插入操作所要进行的平衡性调整,也就是要通过 “先左后右旋转” 来恢复二叉树的平衡
                    //-)如果其左孩子(节点8)的平衡因子是1,则说明该左孩子左子树(以节点6为根)更高,
                            //这种情形等同于LL型插入操作所要进行的平衡性调整,也就是要通过“右旋转”来恢复二叉树的平衡
                    //-)如果其左孩子(节点12)的平衡因子是0,则既可以通过 “右旋转” 也可以通过 “先左后右旋转”来恢复平衡

    //复杂点的情况:删除一个节点后,需要做两次平衡性调整才能是二叉树恢复平衡
    //几个结论
        //a)插入新节点导致失衡,这个子树有多高,调整平衡后这个数就会恢复为多高
        //b)将一棵最小不平衡子树进行平衡性调整后,会发现这棵调整后的子树的根的平衡因子变为0 
        //c)从写程序的角度来看,当插入一个新节点导致原来平衡二叉树失衡后:
            //c.1)新插入的节点平衡因子自然是0(因为新插入的节点肯定是叶子节点)
            //c.2)沿着新插入的节点向上逐个找寻父亲节点并调整父亲节点的平衡因子。

    //仔细分析一下删除65步骤  【右侧导航栏,一个例子】
    //a)如果希望删除节点65,则会寻找节点63,用63替换65,最终会把63这个叶子节点删除。节点删除后,要尝试调整这棵二叉树
    //b)因为要删除的叶子节点63,肯定会回溯到节点62,该节点的平衡因子从原来的-1变成0,继续回溯。
        //回溯到了节点63(原来的节点65),此时该节点的平衡因子从原来的-1 变成了-2, 必须对这棵以63为根的最小平衡子树进行左旋转来调整平衡
            //左旋转调整平衡后,如图所示:
    //c)事情并没有结束,图中根节点60的右子树平衡性调整完毕后,这棵右子树高度由4变成3,左子树高度还是5没有改变
        //这意着根节点60的平衡因子从最初的 1 变成了 2,也就是说根节点60变得不平衡了。此时,既要对这棵以60为根的最小不平衡子树(实际上就是这棵树)
            //进行“先左后右”的旋转来进行平衡调整,最终调整的结果如图
    //最终,经过了两次平衡性调整,整个二叉树又恢复了平衡,根节点也从原来的60变成了现在的42。

 

平衡二叉树四个重要旋转操作

  右旋转

  在A的 "左孩子的左子树"中插入节点导致A不平衡(简称LL:L代表Left)。此时需要通过“右旋转”调整平衡

  

  左旋转

  在A的 "右孩子的右子树"中插入节点导致A不平衡(简称RR:R代表Right)。此时需要通过“左旋转”调整平衡

  

 

  先左后右

  在A的 "左孩子的右子树"中插入节点导致A不平衡(简称LR)。此时需要通过“先左后右”调整平衡
  

  先右后左

  在A的 "右孩子的左子树"中插入节点导致A不平衡(简称RL)。此时需要通过“先右后左”调整平衡

  

 

删除平衡调整

  如果平衡因子为-2

  

  如果平衡因子为2

  

 

一个例子

  删除65节点

  

节点定义

//平衡二叉树中每个节点的定义
template<typename T>
struct AVLNode
{
    T        data;              //节点类型
    AVLNode* LeftChild;         //左子节点
    AVLNode* RightChild;        //右子节点
    int      balfac;            //平衡因子
};

平衡二叉树定义

template<typename T>
class AVLTree
{
public:
    AVLTree();   //构造函数
    ~AVLTree();  //析构函数
private:
    void ReleaseNode(AVLNode<T>* pnode);

public:
    void inOrder();                                      //中序遍历
    void InsertElem(const T& e);                         //插入元素,程序内部会自动确定插入位置
    void DeleteElem(const T& e);                         //删除某个节点

private:
    void inOrder(AVLNode<T>* tNode);                     //中序遍历
    void InsertElem(AVLNode<T>*& tNode, const T& e);     //注意第一个参数类型不要搞错
    void RotateRight(AVLNode<T>*& pointer);              //右旋转(LL插入导致的失衡)
    void RotateLeft(AVLNode<T>*& pointer);               //左旋转(RR插入导致失衡),注意参数类型
    void RotateLeftRight(AVLNode<T>*& pointer);          //先左后右(LR插入导致的失衡),不调用上述二个现成的函数
    void RotateRightLeft(AVLNode<T>*& pointer);          //先右后左旋转
    void DeleteElem(AVLNode<T>*& tNode, const T& e);     //节点的删除与失衡调整

private:
    AVLNode<T>* root;   //树根指针
};

构造与析构

template<typename T>
AVLTree<T>::AVLTree()   //构造函数
{
    root = nullptr;
}

template<typename T>
AVLTree<T>::~AVLTree()  //析构函数
{
    ReleaseNode(root);
}

节点释放

template<typename T>
void AVLTree<T>::ReleaseNode(AVLNode<T>* pnode)
{
    if (pnode != nullptr)
    {
        ReleaseNode(pnode->LeftChild);
        ReleaseNode(pnode->RightChild);
    }
    delete pnode;
}

中序遍历

template<typename T>
void AVLTree<T>::inOrder()          //中序遍历
{
    inOrder(root);
}

template<typename T>
void AVLTree<T>::inOrder(AVLNode<T>* tNode)
{
    if (tNode != nullptr)
    {
        inOrder(tNode->LeftChild);
        cout << tNode->data << " ";
        inOrder(tNode->RightChild);
    }
}

元素的插入

template<typename T>
void AVLTree<T>::InsertElem(const T& e)      //程序内部会自动确定插入位置
{
    InsertElem(root, e);
}

template<typename T>
void AVLTree<T>::InsertElem(AVLNode<T>*& tNode, const T& e)     //注意第一个参数类型不要搞错
{
    AVLNode<T>* point = tNode;      //从指向根节点开始
    AVLNode<T>* parent = nullptr;   //保存父亲节点,根节点的父节点肯定先为空

    //借助以往实现的(链栈)代码保存插入节点的路径信息(用于后面调节平衡因子、调整平衡)
    LinkStack<AVLNode<T>* > slinkobj;

    //通过while循环寻找要插入的节点的位置,同时还要把插入路线上的所经过的所有节点都保存在栈中
        //因为这些节点的平衡因子可能需要调整
    while (point != nullptr)
    {
        if (e == point->data)
            return;   //要插入的数据与当前树中的某节点数据相同,则不允许插入,直接返回。
        parent = point;     //记录父节点,因为后续子节点要往下走,找合适自己的位置
        slinkobj.Push(parent);  //入栈(树根在最底下,越往上离要插入的节点越近,最上面元素是要插入节点的父节点。

        if (e > point->data)
        {
            point = point->RightChild;
        }
        else
        {
            point = point->LeftChild;
        }
    }//end while;

    //走到这里,point等于nullptr,该生成新节点了
    point = new AVLNode<T>;
    point->data = e;
    point->LeftChild = nullptr;
    point->RightChild = nullptr;
    point->balfac = 0;                  //插入时肯定是个叶子节点,所以平衡因子肯定等于0

    if (parent == nullptr)
    {
        //创建的是根节点
        tNode = point;
        return;
    }

    //走到这里表示创建的不是根节点,那么要把孩子链到父亲上
    if (e > parent->data)
    {
        parent->RightChild = point;         //在父节点的右树链路上
    }
    else
    {
        parent->LeftChild = point;          //在父节点的左树链路上
    }

    //下面即将修改平衡因子,以及调整平衡,前面栈slinkobj,里的节点就是可能要更改平衡因子和做平衡调整的节点
    while (slinkobj.Empty() != true)
    {
        if (slinkobj.Pop(parent) == true)    //去栈顶元素的parent中,第一次获取到的是新插入节点的父节点
        {
            //(1)因为插入了孩子,所以调整父的平衡因子
            if (parent->LeftChild == point)  //说明插入的是左子节点
            {
                parent->balfac++;           //平衡因子加一,左边的加一
            }
            else
            {
                parent->balfac--;           //平衡因子减一,右边的减一
            }

            //(2)找最小不平衡子树的根节点并进行平衡性调整
            if (parent->balfac < -1 || parent->balfac > 1)
            {
                //有四种旋转方式
                //1)如果这棵最小不平衡子树的根节点和其盖子节点的平衡因子 > 0,那就是LL情形,需要通过右旋转来恢复平衡
                if (parent->balfac > 0 && point->balfac > 0)
                {
                    RotateRight(parent);
                }
                //2)如果这棵最小不平衡子树的根节点和其他孩子节点的平衡因子都 < 0,那就是RR情形,要左旋转来恢复平衡  
                //根相关指针的调整 
                else if (parent->balfac < 0 && point->balfac < 0)
                {
                    RotateLeft(parent);
                }
                //3)如果这棵最小不平衡子树的根节点的平衡因子 > 0,其他孩子节点的平衡因子 < 0, 那就是LR情形,要先左旋在右旋恢复平衡
                else if (parent->balfac > 0 && point->balfac < 0)
                {
                    RotateLeftRight(parent);
                }
                //3)如果这棵最小不平衡子树的根节点的平衡因子 < 0,其他孩子节点的平衡因子 > 0, 那就是RL情形,要先右旋在左旋恢复平衡
                else if (parent->balfac < 0 && point->balfac > 0)
                {
                    RotateRightLeft(parent);
                }
                if (slinkobj.Empty() == true)
                {
                    //本条件成立,表示本次平衡性调整,调整到了这个数的最上面的节点,因为平衡性调整会使树根节点发生改变
                        //所以要更新根节点的指向
                    root = parent;
                }
                else
                {
                    //本次平衡性调整并没有调整到 整个数最上面的根节点, 但是因为平衡性调整会使树根发生改变,所以
                        //老根节点的孩子指针应该指向新根节点
                    AVLNode<T>* pParentPoint = nullptr;
                    slinkobj.GetTop(pParentPoint);      //拿到老根的父节点,一定会取得成功,因为栈不为空
                    //判断让老根节点(pParentPoint)的左孩子指针还是右孩子指针指向新根(parent)
                    if (pParentPoint->data > parent->data)
                    {
                        pParentPoint->LeftChild = parent;
                    }
                    else
                    {
                        pParentPoint->RightChild = parent;
                    }
                }
                break;   //最小平衡子树调整完成后,其他不平衡节点自然会恢复平衡,所以不需要在向上调整
            }
            //比如某个节点A有个左子树B1,所以其平衡因子为1,现在如果给其增加一个右子树B2,其平衡因子为0
            //此时就不要在继续向上调整节点A的父的平衡因子了,否则会把A父亲的平衡因子调整错误。
            else if (parent->balfac == 0)
            {
                //已经平衡,不需要在继续回溯调整
                break;
            }
            else
            {
                point = parent;         //让point指向父亲节点,后续parent的新值要从栈顶获取
            }//end if(parent->balfac < -1 || parent->balfac > 1)
        }//end if(slinkobj.Pop(parent) == true)
    }//end while(slinkobj.Empty() != true)

}

元素删除

template<typename T>
void AVLTree<T>::DeleteElem(const T& e)        //删除某个节点
{
    return DeleteElem(root, e);
}

template<typename T>
void AVLTree<T>::DeleteElem(AVLNode<T>*& tNode, const T& e)
{
    AVLNode<T>* ptmp = tNode;     //要删除的节点
    AVLNode<T>* parent = nullptr;   //保存父亲节点,根节点的父亲节点肯定先为nullptr
    LinkStack<AVLNode<T>* > slinkobj;
    while (ptmp != nullptr)        //通过while循环尝试让ptmp指向要被删除的节点
    {
        if (ptmp->data == e)
        {
            break;
        }
        parent = ptmp;    //记录父节点
        slinkobj.Push(parent);      //入栈

        if (ptmp->data > e)
        {
            ptmp = ptmp->LeftChild;
        }
        else
        {
            ptmp = ptmp->RightChild;
        }
    }//end while

    if (ptmp == nullptr)   //没找到要删除的节点
    {
        return;
    }

    //找到了要删除的节点,二叉查找树删除节点分为几种情况;
    AVLNode<T>* q = nullptr;     //临时变量指针
    if (ptmp->LeftChild == nullptr && ptmp->RightChild == nullptr)
    {
        if (parent != nullptr)
        {
            if (parent->LeftChild == ptmp)
            {
                parent->LeftChild = nullptr;
            }
            else
            {
                parent->RightChild = nullptr;
            }
        }
    }
    else if (ptmp->LeftChild != nullptr && ptmp->RightChild != nullptr)
    {
        //如果要删除的节点左子树和右子树都不为空,则把对当前节点的删除转换为当前节点左子树的最右下节点的删除
        //这里涉及到的问题是要记录最总删除的节点的路径
        //删除举例:比如删除如下的D节点,最后会变成删除F节点,ptmp指向的是F节点
        //a)该入栈的节点入栈
        parent = ptmp;      //记录父节点
        slinkobj.Push(parent);  //入栈
        //b)找到这个要删除节点的左子树的最右下节点(也可以是右子树的最左下节点)
            //节点的值替换到要删除的节点上
        q = ptmp->LeftChild;
        while (q->RightChild != nullptr)
        {
            parent = q;
            slinkobj.Push(parent);
            q = q->RightChild;
        }
        ptmp->data = q->data;
        ptmp = q;           //让ptemp指向真正删除的节点,也就是把删除一个既有左子树又有右子树的节点转化为删除一个叶子节点     

        //上面找到的这个节点肯定没有右子树,因为找到的是左子树的最右下节点
        if (parent != nullptr)
        {
            if (parent->LeftChild == ptmp)
            {
                parent->LeftChild = ptmp->LeftChild;
            }
            else
            {
                parent->RightChild = ptmp->LeftChild;
            }
        }
    }
    else
    {
        //如果要删除的节点的左子树或者右子树为空(两者肯定有一个为空才能走到这个分支),让q指向不空的孩子
        if (ptmp->LeftChild != nullptr)
            q = ptmp->LeftChild;
        else
            q = ptmp->RightChild;

        if (parent != nullptr)
        {
            //把被删除的子节点连接到被删除节点的父节点上面去
            if (parent->LeftChild == ptmp)       //要删除节点是其父亲的左孩子
                parent->LeftChild = q;
            else
                parent->RightChild = q;
        }
    }

    //parent不为空的情况都处理了,这里处理parent为空的情况,parent为空删除的是根节点
    if (parent == nullptr)
    {
        if (ptmp->LeftChild == nullptr && ptmp->RightChild == nullptr)   //就一个根节点,并且删除的就是根节点
            tNode = nullptr;
        else if (ptmp->LeftChild == nullptr || ptmp->RightChild == nullptr)  //要删除这棵树的根节点,并且这棵树根的左子树为空或者右子树为空
            tNode = q;
        else
        {
            //这个else一直不会成立
            assert(false);   //如果这行报错,include<assert.h>
        }
    }

    //处理平衡因子的改变(平衡性调整)
    while (slinkobj.Empty() != true)
    {
        if (slinkobj.Pop(parent) == true)
        {
            //如果删除的是叶子节点,并且其父亲只有一个叶子,那么删除后,其父亲就变成叶子了,叶子的平衡因子自然就为0
            if (parent->LeftChild == nullptr && parent->RightChild == nullptr)
            {
                parent->balfac = 0;
            }
            else if (parent->LeftChild == q)    //删除的是左树
                parent->balfac--;              //平衡因子减少1
            else                               //删除右树
                parent->balfac++;              //平衡因子增加
            if (parent->balfac == -1 || parent->balfac == 1)
            {
                //说明原来的平衡因子为0,也就是原来的左右孩子都有,那么删除任意一个孩子,
                    //除parent节点平衡因子发生变化外,其他任何其他的parent的父亲节点等节点的平衡因子不会发生变化,这里可以直接退出
                break;
            }
            if (parent->balfac == 0)
            {
                //说明parent节点原来的平衡因子为-1 或者 1,的继续向上回溯尝试调整其他父节点平衡因子
                q = parent;
                continue;
            }

            if (parent->balfac == -2)    //平衡因子为-2
            {
                // 平衡因子为-2,所以能确定的是,一定有右孩子
                if (parent->RightChild->balfac == -1)   //左旋转
                    RotateLeft(parent);
                else if (parent->RightChild->balfac == 1)  //先右后左
                    RotateRightLeft(parent);
                else //if(parent->RightChild->balfac == 0)  //左旋转
                {
                    RotateLeft(parent);
                    parent->balfac = 1;
                    parent->LeftChild->balfac = -1;
                }
            }
            else
            {
                //平衡因子为2,所以能确定的是,一定有左孩子
                if (parent->LeftChild->balfac == -1) //先左后右旋转
                    RotateLeftRight(parent);
                else if (parent->LeftChild->balfac == 1) //右旋转
                    RotateRight(parent);
                else //if(parent->RightChild->balfac == 0)
                {
                    RotateRight(parent);
                    parent->balfac = -1;
                    parent->RightChild->balfac = 1;
                }
            }// end if(parent->balfac == -2)

            //根相关指针的调整
            if (slinkobj.Empty() == true)
            {
                //本条件成立表示本次平衡性调整调整到了整个树最上面的节点,因为平衡性调整会是树根节点发生改变
                root = parent;
            }
            else
            {
                //本次平衡性调整 并没有调整到整个数最上面的根节点,但因为平衡性调整会使树根节点发生改变,
                    //所以老根节点的孩子指针应该指向新根节点
                AVLNode<T>* pParentPoint = nullptr;
                slinkobj.GetTop(pParentPoint);         //拿到老根的父节点,一定会取得成功

                //判断让老根父节点(pParentPoint)的左孩子指针还是右孩子指针指向新根
                if (pParentPoint->data > parent->data)
                    pParentPoint->LeftChild = parent;
                else
                    pParentPoint->RightChild = parent;
            } // end if(slinkobj.Empty() == true)
        } // end ifslinkobj.Pop(parent) == true()
    }// end whlie
    delete ptmp;   //释放被删除节点所占用的内存
    return;
}

旋转操作

  右旋转

template<typename T>
void AVLTree<T>::RotateRight(AVLNode<T>*& pointer)
{
    AVLNode<T>* ChildRight = pointer;     //最小不平衡子树的根节点
    pointer = ChildRight->LeftChild;      //新根(节点B) == 老根(节点A)的左孩子节点
    ChildRight->LeftChild = pointer->RightChild;    //如果节点B原来有右孩子,则首先将节点B这个右孩子作为节点A的左孩子
    pointer->RightChild = ChildRight;     //将失衡的节点A作为节点B的右孩子

    //需要变动的平衡因子的处理,其他节点平衡因子不改变,不需要处理
    pointer->balfac = ChildRight->balfac = 0;       //节点A和节点B的平衡因子恢复为0
}

  左旋转

template<typename T>
void AVLTree<T>::RotateLeft(AVLNode<T>*& pointer)     //注意参数类型
{
    AVLNode<T>* childLeft = pointer;      //pointer是“最小不平衡子树”的根节点,即失衡的节点
    pointer = childLeft->RightChild;      //新根节点B == 老根节点A
    childLeft->RightChild = pointer->LeftChild; //如果节点B原来有左孩子,则首先将节点B这个左孩子作为节点A的右孩子
    pointer->LeftChild = childLeft;       //将失衡的节点A作为节点B的左孩子

    //需要变动的平衡因子的处理、其他的节点不需要处理
    pointer->balfac = childLeft->balfac = 0;   //节点A和节点B的平衡因子恢复为零

}

  先左后右

template<typename T>
void AVLTree<T>::RotateLeftRight(AVLNode<T>*& pointer)  //不调用上述二个现成的函数
{
    AVLNode<T>* ChildRight = pointer;       //pointer“是最小不平衡子树的根节点”,即失衡节点A
    AVLNode<T>* ChildLeft = ChildRight->LeftChild;   //节点B == 节点A的左孩子节点

    pointer = ChildLeft->RightChild;                  //节点C,节点b的右孩子,也是最总先左后右旋转后的新根

    //进行左旋转
    ChildLeft->RightChild = pointer->LeftChild;       //如果节点c如果有左孩子,则将左孩子变成节点B的右孩子
    pointer->LeftChild = ChildLeft;                   //节点B(childleft)变成了其原右孩子(节点c:pointer)的左孩子

    //节点B的平衡因子会改变,这主要取决于节点C原来带的是左孩子还是右孩子(或者说是取决于节点C的平衡因子)
    if (pointer->balfac > 0)    //原来带的是左孩子
        ChildLeft->balfac = 0; //因为节点C带的左孩子刚好成了节点B的右孩子,节点B原来有个左孩子,所以现在节点B平衡了。
    else                        //节点C没有带左孩子
        ChildLeft->balfac = 1;  //节点B只有一个左孩子,所以节点B平衡因子为1

    //进行上述左旋转之后,新的节点B其实已经是pointer了。而且此时节点C(新根)的平衡因子还保持为老值(1或者-1)
    //在进行右旋转
    ChildRight->LeftChild = pointer->RightChild;            //如果节点C原来有右孩子,则将这个右孩子变成节点A的左孩子
    pointer->RightChild = ChildRight;                        //将失衡节点A向右侧旋转作为新节点B(pointer)的右孩子

    if (pointer->balfac == 1)        //新节点B原来带的是左孩子
    {
        ChildRight->balfac = -1;        //节点A原来有个右孩子,所以节点A平衡因子变成-1。
    }
    else
    {
        ChildRight->balfac = 0;         //新节点B没带孩子或者带右孩子,这个右孩子会挂到节点A上当左孩子,所以节点A平衡因子变成0
    }

    //根节点(  新节点B)的平衡因子调整为0
    pointer->balfac = 0;
}

  先右后左

template<typename T>
void AVLTree<T>::RotateRightLeft(AVLNode<T>*& pointer)
{
    AVLNode<T>* ChildLeft = pointer;    //60节点
    AVLNode<T>* ChildRight = ChildLeft->RightChild; //120
    pointer = ChildRight->LeftChild;    //95

    //进行右旋转
    ChildRight->LeftChild = pointer->RightChild;  //120的左孩子设置为100
    pointer->RightChild = ChildRight;             //95的右孩子指向120

    //调整120的平衡因子
    if (pointer->balfac <= 0)                    //如果95的平衡因子是0,-1(95不带孩子或者带右孩子)
        ChildRight->balfac = 0;                 //120的平衡因此就是0
    else                                        //节点95带左孩子
        ChildRight->balfac = -1;                //120的平衡因子是-1

    //再进行左旋转
    ChildLeft->RightChild = pointer->LeftChild; //60的右孩子指向65
    pointer->LeftChild = ChildLeft;             //95的左孩子指向60
    //调节60的平衡因子
    if (pointer->balfac == -1)                   //如果95的平衡因子是-1  (95带右子树)
        ChildLeft->balfac = 1;
    else                                        //节点95不带子树,或者带左子树
        ChildLeft->balfac = 0;

    pointer->balfac = 0;                        //95的平衡因子设置为0

}

测试用例

int main()
{
    AVLTree<int> tree;
    //测试右旋转用到的数据
    // int myarray[] = {95, 60, 120, 40, 20};
    // int account = sizeof(myarray) /sizeof(int);
    // for(int i = 0; i < account; ++i)
    // {
    //     tree.InsertElem(myarray[i]);
    // }

    //测试左旋转用到的数据
    // int myarray[] = {40, 20, 60, 95, 120};
    // int account = sizeof(myarray) /sizeof(int);
    // for(int i = 0; i < account; ++i)
    // {
    //     tree.InsertElem(myarray[i]);
    // }

    //测试先左后右
    // int myarray[] = {95, 60, 120, 40, 50};
    // int account = sizeof(myarray) /sizeof(int);
    // for(int i = 0; i < account; ++i)
    // {
    //     tree.InsertElem(myarray[i]);
    // }

    //测试右后左
    // int myarray[] = {60, 40, 70, 120, 95};
    // int account = sizeof(myarray) /sizeof(int);
    // for(int i = 0; i < account; ++i)
    // {
    //     tree.InsertElem(myarray[i]);
    // }

    //综合测试
    // int myarray[] = {12,4,1,3,7,8,10,9,2,11,6,5};
    // int account = sizeof(myarray) /sizeof(int);
    // for(int i = 0; i < account; ++i)
    // {
    //     tree.InsertElem(myarray[i]);
    // }

    //删除的测试
    int myarray[] = { 60,31,65,15,42,62,75,12,25,37,50,63,69,85,2,32,38,48,56,82,34 };
    int account = sizeof(myarray) / sizeof(int);
    for (int i = 0; i < account; ++i)
    {
        tree.InsertElem(myarray[i]);
    }
    cout << "中序遍历一下: " << endl;;
    tree.inOrder();
    cout << endl;

    cout << "删除之后在中序遍历: " << endl;;
    tree.DeleteElem(65);
    tree.inOrder();
    cout << endl;
    return 0;
}

 注意

//用到了前面所介绍的链式栈(LinkStack.cc)
posted @ 2022-08-03 21:32  huahuati  阅读(122)  评论(0)    收藏  举报