C++ AVL树(高度平衡的二叉搜索树)

一、思路

   1.创建AVL树:

      每次输入一个数num,然后用Insert()函数,将num插入到AVL树中。

// 创建AVL平衡树
template <class T>
void AVLTree<T>::CreateAVLTree() {
    T num;
    
    cout << "请输入数(以-1结束输入)建立平衡二叉树:" << endl;
    while(cin >> num && num != -1)
        Insert(num); // 插入到树中
}

2.插入值x:

 (1)寻找插入的位置:如果AVL树已经存在该x值,则不插入。如果x小于当前结点的值,则往左子树中找插入位置;否则,往右子树找插入位置。

// 1.寻找插入位置
    while(p != NULL) { // 树不为空
        if(x == p->data) // 已经存在该值,不用再插入
            return false;
        pre = p; // 保存当前结点
        st.push(pre); // 用栈记忆查找路径
        if(x < p->data) // 要插入的值小于当前结点的值,往左子树继续查找
            p = p->left;
        else // x大于当前结点的值
            p = p->right; // 往右子树查找
    }

(2)把x插入到AVL平衡树中:如果x小于父结点的值,则插入到父结点的左孩子位置;否则, 插入到父结点的右孩子上。

// 2.找到了准备插入的位置,把x插入到AVL平衡树中
    p = new AVLNode<T>(); // 创建新结点
    p->data = x; // 存储要插入的值
    p->bf = 0; // 新结点,平衡因子为0
    p->left = p->right = NULL;
    if(p == NULL) {
        cerr << "存储空间分配失败!" << endl;
        exit(1);
    }
    if(pre == NULL) { // 空树,新结点成为根结点
        root = p;
        return true;
    }
    if(x < pre->data) // 插入到左子树
        pre->left = p;
    else
        pre->right = p; // 插入到右子树

(3)重新平衡化AVL树:因为插入的新结点,可能导致AVL树平衡被破坏。

         从插入的新结点的父结点开始往根结点调整。如果当前结点是父结点的左孩子,则父结点的平衡因子要减1,如果当前结点是父结点的右孩子,则父结点的平衡因子要加1

         然后,通过父结点parent的平衡因子来决定是否需要平衡化旋转:

         A. 父结点parent的平衡因子为0说明刚才是在parent的较矮的子树上插入了新结点,父结点处平衡,且高度没有增减。此时从parent到根的路径上各结点为根的子树高度不变,从而各个结点的平衡因子不变,可以结束重新平衡化的处理。

         B.父结点parent的平衡因子的绝对值|bf|=1说明插入前parent结点的平衡因子为0,插入新结点后,以parent为根的子树没有失去平衡,不需要平衡化旋转。但该子树的高度增加,还需从结点parent向根的方向回溯,继续考查结点parent的父结点的平衡状态。

         C.父结点parent的平衡因子的绝对值|bf|=2说明新结点在较高的子树上插入,造成了不平衡,需要做平衡化旋转。旋转要分情况讨论。旋转后以parent为根的子树高度降低,因此无需继续向上层回溯。

// 3.重新平衡化AVL树
    while(st.empty() == false) { // 栈不空
        pre = st.top(); // 取栈顶元素
        st.pop();
        if(p == pre->left) // 如果是左孩子结点,则父结点平衡因子减1
            pre->bf--; // 调整父结点的平衡因子
        else
            pre->bf++; // 如果是右孩子结点,则父结点平衡因子加1
        
        if(pre->bf == 0) // 第1种情况,平衡退出
            break;
        else if(pre->bf == 1 || pre->bf == -1) // 第2种情况,|bf|=1
            p = pre; // 往上继续考察父结点的平衡状态
        else { // 第3中情况,|bf| = 2,需要做平衡处理
            d = (pre->bf < 0) ? -1 : 1; // 区别单双旋转标志
            if(p->bf == d) { // 两结点平衡因子同号,单旋转
                if(d == -1)
                    RotateR(pre); // 右单旋转
                else
                    RotateL(pre); // 左单旋转
            } else { // 两结点平衡因子反号,双旋转
                if(d == -1)
                    RotateLR(pre); // 先左后右双旋转
                else
                    RotateRL(pre); // 先右后左双旋转
            }
            break; // 不再往上调整
        } // else
    } // while

(4)把调整的子树,重新链接到原树中

// 调整到树的根结点,根结点变为当前的pre
    if(st.empty())
        root = pre;
    else { // 中间重新链接,说明是在根结点的子树中进行平衡化
        // 平衡化的子树,子树的父结点已丢失,要与原树重新进行链接,所以要取栈顶元素,
        // 然后挂入到栈顶元素的孩子树上。
        q = st.top(); // 获取栈顶元素
        if(q->data > pre->data)
            q->left = pre;
        else
            q->right = pre;
    }

 3.删除:

(1)查找要删除的位置:

while(p != NULL) { // 1.寻找删除位置
    if(x == p->data) // 找到了,停止搜索
        break;
    pre = p;
    st.push(pre); // 用栈记忆查找路径
    if(x < p->data) // 继续往左子树查找插入位置
        p = p->left;
    else // 继续往右子树查找插入位置
        p = p->right;
}

(2)找到位置后,判断当前要删除的结点p有两个子女,还是最多只有一个子女:

      A.p有两个子女:

       首先搜索p在中序次序下的直接前驱q(同样可以找直接后继),再把结点q的内容传送给结点p,现在问题转移到删除结点q。把结点q当作被删结点p,它是只有一个子女的结点。

if(p->left != NULL && p->right != NULL) { // 被删除结点有左右子女
    pre = p;
    st.push(pre);
    q = p->left; // 往p左子树找p的直接前驱
    while(q->right != NULL) {
        pre = q;
        st.push(pre);
        q = q->right;
    }
    p->data = q->data; // 用q的值填补p
    p = q; // 被删除结点转为q
}

    B.如果被删结点p最多只有一个子女q:

       可以简单地把p的父结点pre中原来指向p的指针改指到q;如果p没有子女,p的父结点pre的相应位置为 NULL。然后将原来以结点pre为根的子树的高度减1,并沿pre通向根的路径反向追踪高度的这一变化对路径上各个结点的影响。

if(p->left != NULL) // 被删除结点p只有一个子女q
    q = p->left;
else
    q = p->right;

考查结点q的父结点pre:

     如果q是pre的左子女,则pre的平衡因子应当加1(应为被删除的结点p为pre的原左子女),否则减少1。

    (1)pre的平衡因子原来为0,在它的左子树或右子树被缩短后,则它的平衡因子改为1或-1。由于以pre为根的子树高度没有改变,从pre到根结点的路径上所有结点都不需要调整。此时可结束本次被删除的重新平衡过程。

    (2)结点pre的平衡因子原不为0,且较高的子树被缩短,则p的平衡因子改为0。此时以pre为根的子树平衡,但其高度减1。为此需要继续考查结点pre的父结点的平衡状态。

    (3)结点pre的平衡因子原不为0,且较矮的子树又被缩短,则在结点pre发生不平衡。为此需进行平衡旋转来恢复平衡。令pre的较高的子树的根为q(该子树未被缩短)。然后,根据q的平衡因子,进行以下操作:

A.第一种情况:pre平衡因子为-2或2,对应的子女q(以q为根的子树原左右子女高度相同)的平衡因子为0,做单旋转即可。

B.第二种情况,pre平衡因子为-2或2,对应的子女q(以q为根的子树原左右子女高度差1)的平衡因子为-1或1,同号则单旋转,不同号则左右双旋转

 

二、实现程序:

1.AVLTree.h

#ifndef AVLTree_h
#define AVLTree_h

#include <iostream>
#include <stack>
using namespace std;

template <class T>
struct AVLNode { // AVL树结点结构的定义
    T data; // 存储数据
    int bf; // 平衡因子:-1, 0, 1
    AVLNode<T> *left, *right; // 左右子结点
};

template <class T>
class AVLTree { // AVL树的类定义
public:
    AVLTree(); // 构造函数
    ~AVLTree();// 析构函数
    void CreateAVLTree(); // 创建AVL平衡树
    bool Insert(T x); // 插入值x
    bool Remove(T x); // 删除值x
    void Traverse(); // 中序遍历AVL树
    // int Height() const; // 求高度
    bool Find(T x); // 查找x
private:
    AVLNode<T> *root; // 根结点
    void RotateL(AVLNode<T> *&ptr); // 左单旋
    void RotateR(AVLNode<T> *&ptr); // 右单旋
    void RotateLR(AVLNode<T> *&ptr); // 先左后右双旋
    void RotateRL(AVLNode<T> *&ptr); // 先右后左双旋
    void Traverse(AVLNode<T> *&ptr); // 中序遍历AVL树
};

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

// 析构函数
template <class T>
AVLTree<T>::~AVLTree() {
    
}

// 创建AVL平衡树
template <class T>
void AVLTree<T>::CreateAVLTree() {
    T num;
    
    cout << "请输入数(以-1结束输入)建立平衡二叉树:" << endl;
    while(cin >> num && num != -1)
        Insert(num); // 插入到树中
}

// 在以ptr为根的AVL树中插入新元素x, 如果插入成功,函数返回true,否则返回false
template <class T>
bool AVLTree<T>::Insert(T x) {
    int d;
    AVLNode<T> *pre = NULL, *p = root, *q;
    stack<AVLNode<T> *> st; // 用栈记忆查找路径
    
    // 1.寻找插入位置
    while(p != NULL) { // 树不为空
        if(x == p->data) // 已经存在该值,不用再插入
            return false;
        pre = p; // 保存当前结点
        st.push(pre); // 用栈记忆查找路径
        if(x < p->data) // 要插入的值小于当前结点的值,往左子树继续查找
            p = p->left;
        else // x大于当前结点的值
            p = p->right; // 往右子树查找
    }
    // 2.找到了准备插入的位置,把x插入到AVL平衡树中
    p = new AVLNode<T>(); // 创建新结点
    p->data = x; // 存储要插入的值
    p->bf = 0; // 新结点,平衡因子为0
    p->left = p->right = NULL;
    if(p == NULL) {
        cerr << "存储空间分配失败!" << endl;
        exit(1);
    }
    if(pre == NULL) { // 空树,新结点成为根结点
        root = p;
        return true;
    }
    if(x < pre->data) // 插入到左子树
        pre->left = p;
    else
        pre->right = p; // 插入到右子树
    
    // 3.重新平衡化AVL树
    while(st.empty() == false) { // 栈不空
        pre = st.top(); // 取栈顶元素
        st.pop();
        if(p == pre->left) // 如果是左孩子结点,则父结点平衡因子减1
            pre->bf--; // 调整父结点的平衡因子
        else
            pre->bf++; // 如果是右孩子结点,则父结点平衡因子加1
        
        if(pre->bf == 0) // 第1种情况,平衡退出
            break;
        else if(pre->bf == 1 || pre->bf == -1) // 第2种情况,|bf|=1
            p = pre; // 往上继续考察父结点的平衡状态
        else { // 第3中情况,|bf| = 2,需要做平衡处理
            d = (pre->bf < 0) ? -1 : 1; // 区别单双旋转标志
            if(p->bf == d) { // 两结点平衡因子同号,单旋转
                if(d == -1)
                    RotateR(pre); // 右单旋转
                else
                    RotateL(pre); // 左单旋转
            } else { // 两结点平衡因子反号,双旋转
                if(d == -1)
                    RotateLR(pre); // 先左后右双旋转
                else
                    RotateRL(pre); // 先右后左双旋转
            }
            break; // 不再往上调整
        } // else
    } // while
    
    // 调整到树的根结点,根结点变为当前的pre
    if(st.empty())
        root = pre;
    else { // 中间重新链接,说明是在根结点的子树中进行平衡化
        // 平衡化的子树,子树的父结点已丢失,要与原树重新进行链接,所以要取栈顶元素,
        // 然后挂入到栈顶元素的孩子树上。
        q = st.top(); // 获取栈顶元素
        if(q->data > pre->data)
            q->left = pre;
        else
            q->right = pre;
    }
    return true;
}

// 中序遍历AVL树
template <class T>
void AVLTree<T>::Traverse() {
    Traverse(root);
}

// 中序遍历AVL树
template <class T>
void AVLTree<T>::Traverse(AVLNode<T> *&ptr) {
    if(ptr != NULL) {
        Traverse(ptr->left); // 中序遍历左子树
        cout << ptr->data << " " ; // 输出数据
        Traverse(ptr->right); // 中序遍历右子树
    }
}

// 删除x
template <class T>
bool AVLTree<T>::Remove(T x) {
    AVLNode<T> *pre = NULL, *p = root, *q, *ppre;
    int d, dd = 0;
    
    stack<AVLNode<T>*> st;
    while(p != NULL) { // 1.寻找删除位置
        if(x == p->data) // 找到了,停止搜索
            break;
        pre = p;
        st.push(pre); // 用栈记忆查找路径
        if(x < p->data) // 继续往左子树查找插入位置
            p = p->left;
        else // 继续往右子树查找插入位置
            p = p->right;
    }
    if(p == NULL) // 未查找到被删结点,删除失败
        return false;
    if(p->left != NULL && p->right != NULL) { // 被删除结点有左右子女
        pre = p;
        st.push(pre);
        q = p->left; // 往p左子树找p的直接前驱
        while(q->right != NULL) {
            pre = q;
            st.push(pre);
            q = q->right;
        }
        p->data = q->data; // 用q的值填补p
        p = q; // 被删除结点转为q
    }
    if(p->left != NULL) // 被删除结点p只有一个子女q
        q = p->left;
    else
        q = p->right;
    if(pre == NULL) // 被删结点为根结点,需要将root指向被删结点的子女q
        root = q;
    else { // 被删结点不是根结点
        if(pre->left == p) // 被删结点p为父结点pre的左子女
            pre->left = q; // 则把父结点pre的左子女变为被删结点p的子女q
        else
            pre->right = q;
        while(!st.empty()) { // 重新平衡化
            pre = st.top();
            st.pop(); // 从栈中退出父结点
            if(pre->right == q)
                pre->bf--; // 调整父结点的平衡因子
            else
                pre->bf++;
            if(!st.empty()) {
                ppre = st.top(); // 从栈中取出祖父结点
                dd = (ppre->left == pre) ? -1 : 1; // 旋转后与上层链接方向
            } else // 栈空,旋转后不与上层链接
                dd = 0;
            if(pre->bf == 1 || pre->bf == -1) // |bf| = 1
                break;
            if(pre->bf != 0) { // |bf|=2
                if(pre->bf < 0) {
                    d = -1;
                    q = pre->left;
                } else {
                    d = 1;
                    q = pre->right; // 用q指示较高的子树
                }
                // 第一种情况:pre平衡因子为-2或2,对应的子女q(以q为根的子树原左右子女高度相同)的平衡因子为0,做单旋转即可。
                if(q->bf == 0) { // 较高的子树平衡因子为0
                    if(d == -1) { // 较高的子树是在父结点pre的左子树上,需要做右单旋转
                        RotateR(pre);
                        pre->bf = 1;
                        pre->left->bf = -1;
                    } else { // 较高的子树是在父结点pre的右子树上,需要做左单旋转
                        RotateL(pre);
                        pre->bf = -1;
                        pre->right->bf = 1;
                    }
                    break;
                }
                // 第二种情况,pre平衡因子为-2或2,对应的子女q(以q为根的子树原左右子女高度差1)的平衡因子为-1或1,同号则单旋转,不同号则左右双旋转
                if(q->bf == d) { // 两结点平衡因子同号
                    if(d == -1)
                        RotateR(pre); // 右单旋转
                    else
                        RotateL(pre); // 左单旋转
                } else { // 两结点平衡因子反号
                    if(d == -1)
                        RotateLR(pre); // 先左后右双旋转
                    else
                        RotateRL(pre); // 先右后左双旋转
                }
                if(dd == -1) // 新根称为祖父结点ppre的左子女
                    ppre->left = pre;
                else if(dd == 1) // 新根称为祖父结点ppre的右子女
                    ppre->right = pre; // 旋转后新根与上层链接
            }
            q = pre; // |bf| = 0;以pre根结点的高度减1,要往上,调整祖先结点的平衡因子
        } // while结束
        if(st.empty())
            root = pre; // 调整到树的根结点
    } // else结束
    delete p;
    return true;
}

// 左单旋
template <class T>
void  AVLTree<T>::RotateL(AVLNode<T> *&ptr) {
    AVLNode<T> *subL =  ptr; // 要左旋转的结点
    
    ptr = subL->right; // 原根的右子女
    subL->right = ptr->left; // ptr成为新根前卸掉左边负载
    ptr->left = subL; // 左单旋转,ptr为新根
    ptr->bf = subL->bf = 0; // 平衡因子变为0
}

// 右单旋
template <class T>
void AVLTree<T>::RotateR(AVLNode<T> *&ptr) {
    AVLNode<T> *subR =  ptr; // 要右旋转的结点
    
    ptr = subR->left; // 原根的左子女
    subR->left = ptr->right; // ptr成为新根前卸掉右边负载
    ptr->right = subR; // 右单旋转,ptr为新根
    ptr->bf = subR->bf = 0; // 平衡因子变为0
}

// 先左后右双旋
template <class T>
void AVLTree<T>::RotateLR(AVLNode<T> *&ptr) {
    // 原根结点,准备旋转为右结点;原根的左结点旋转还是为左结点
    AVLNode<T> *subR = ptr, *subL = subR->left;
    ptr = subL->right; // 原根的左孩子结点的右孩子结点旋转为新根结点
    
    subL->right = ptr->left; // ptr成为新根前甩掉它左边的负载
    ptr->left = subL; // 左单旋转,ptr成为新根
    if(ptr->bf <= 0) // 插入新结点后,ptr左子树变高
        subL->bf = 0;
    else // 在ptr右边插入新结点,subL的左孩子树比右孩子树高一层
        subL->bf = -1;
    subR->left = ptr->right; // ptr成为新根前甩掉它右边的负载
    ptr->right = subR; // 右单旋转,ptr成为新根
    if(ptr->bf == -1) // 是在ptr的左孩子树插入
        subR->bf = 1;
    else // 在ptr的右孩子树插入
        subR->bf = 0;
    ptr->bf = 0;
}

// 先右后左双旋
template <class T>
void AVLTree<T>::RotateRL(AVLNode<T> *&ptr) {
    // 原根结点,准备旋转为右结点;原根的左结点旋转还是为左结点
    AVLNode<T> *subL = ptr, *subR = subL->right;
    ptr = subR->left; // 原根的右孩子结点的左孩子结点旋转为新根结点
    
    subR->left = ptr->right; // ptr成为新根前甩掉它右边的负载
    ptr->right = subR; // 右单旋转,ptr成为新根
    if(ptr->bf >= 0) // 在ptr的右孩子树插入新结点
        subR->bf = 0;
    else // 在ptr的左孩子树插入新结点
        subR->bf = 1;
    subL->right = ptr->left; // ptr成为新根前甩掉它左边的负载
    ptr->left = subL; // 左单旋转,ptr成为新根
    if(ptr->bf == 1) // // 在ptr的右孩子树插入新结点
        subL->bf = -1;
    else // 在ptr的左孩子树插入新结点
        subL->bf = 0;
    ptr->bf = 0;
}

// 查找x
template <class T>
bool AVLTree<T>::Find(T x) {
    AVLNode<T> *p = root;

    while(p != NULL) { // 树不为空
        if(x == p->data) // 已经存在该值,不用再插入
            return true;
        if(x < p->data) // 要插入的值小于当前结点的值,往左子树继续查找
            p = p->left;
        else // x大于当前结点的值
            p = p->right; // 往右子树查找
    }
    return false; // 没找到
}
#endif /* AVLTree_h */

2.main.cpp

//  二叉平衡树:
//  (1)建立
//  (2)插入
//  (3)删除
//  (4)查找
//  (5)中序遍历

// 测试数据:16 3 7 11 9 26 18 14 15 -1
#include "AVLTree.h"

int main(int argc, const char * argv[]) {
    AVLTree<int> st; // 创建AVL树对象
    bool finished = false;
    int choice, x;
    
    while(!finished) {
        cout << "[1]创建AVL树" << endl;
        cout << "[2]中序遍历" << endl;
        cout << "[3]删除" << endl;
        cout << "[4]查找某个值x:" << endl;
        cout << "[5]退出" << endl;
        cout << "请输入你的选择[1-5]:" << endl;
        cin >> choice;
        switch(choice) {
            case 1:
                st.CreateAVLTree();
                break;
            case 2:
                st.Traverse();
                cout << endl;
                break;
            case 3:
                cout << "请输入要删除的值x:";
                cin >> x;
                st.Remove(x);
                break;
            case 4:
                cout << "请输入要查找的值x:";
                cin >> x;
                if(st.Find(x))
                    cout << "查找到了" << x << endl;
                else
                    cout << "未找到" << x << endl;
                break;
            case 5:
                finished = true;
                break;
            default:
                cout << "输入选择错误!" << endl;
        }
    }
    return 0;
}

测试结果:

猜你喜欢

转载自blog.csdn.net/chuanzhouxiao/article/details/88790677