首頁 > 軟體

詳解如何用c++實現平衡二元樹

2021-06-11 16:01:29

一、概述

平衡二元樹具有以下性質:它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二元樹。這個方案很好的解決了二叉查詢樹退化成連結串列的問題,把插入,查詢,刪除的時間複雜度最好情況和最壞情況都維持在O(logN)。但是頻繁旋轉會使插入和刪除犧牲掉O(logN)左右的時間,不過相對二叉查詢樹來說,時間上穩定了很多。

平衡二元樹大部分操作和二叉查詢樹類似,主要不同在於插入刪除的時候平衡二元樹的平衡可能被改變,並且只有從那些插入點到根結點的路徑上的結點的平衡性可能被改變,因為只有這些結點的子樹可能變化。

二、平衡二元樹不平衡的情形

把需要重新平衡的結點叫做α,由於任意兩個結點最多隻有兩個兒子,因此高度不平衡時,α結點的兩顆子樹的高度相差2.容易看出,這種不平衡可能出現在下面4中情況中:

1.對α的左兒子的左子樹進行一次插入

2.對α的左兒子的右子樹進行一次插入

3.對α的右兒子的左子樹進行一次插入

4.對α的右兒子的右子樹進行一次插入

情形1和情形4是關於α的映象對稱,二情形2和情形3也是關於α的映象對稱,因此理論上看只有兩種情況,但程式設計的角度看還是四種情形。

第一種情況是插入發生在「外邊」的情形(左左或右右),該情況可以通過一次單旋轉完成調整;第二種情況是插入發生在「內部」的情形(左右或右左),這種情況比較複雜,需要通過雙旋轉來調整。

三、調整措施

3.1、單旋轉

上圖是左左的情況,k2結點不滿足平衡性,它的左子樹k1比右子樹z深兩層,k1子樹中更深的是k1的左子樹x,因此屬於左左情況。

為了恢復平衡,我們把x上移一層,並把z下移一層,但此時實際已經超出了AVL樹的性質要求。為此,重新安排結點以形成一顆等價的樹。為使樹恢復平衡,我們把k2變成這棵樹的根節點,因為k2大於k1,把k2置於k1的右子樹上,而原本在k1右子樹的Y大於k1,小於k2,就把Y置於k2的左子樹上,這樣既滿足了二叉查詢樹的性質,又滿足了平衡二元樹的性質。

這種情況稱為單旋轉。

3.2、雙旋轉

對於左右和右左兩種情況,單旋轉不能解決問題,要經過兩次旋轉。

對於上圖情況,為使樹恢復平衡,我們需要進行兩步,第一步,把k1作為根,進行一次右右旋轉,旋轉之後就變成了左左情況,所以第二步再進行一次左左旋轉,最後得到了一棵以k2為根的平衡二元樹。

四、AVL樹的刪除操作

同插入操作一樣,刪除結點時也有可能破壞平衡性,這就要求我們刪除的時候要進行平衡性調整。

刪除分為以下幾種情況:

首先在整個二元樹中搜尋要刪除的結點,如果沒搜尋到直接返回不作處理,否則執行以下操作:

1.要刪除的節點是當前根節點T。

如果左右子樹都非空。在高度較大的子樹中實施刪除操作。

分兩種情況:

(1)、左子樹高度大於右子樹高度,將左子樹中最大的那個元素賦給當前根節點,然後刪除左子樹中元素值最大的那個節點。

(1)、左子樹高度小於右子樹高度,將右子樹中最小的那個元素賦給當前根節點,然後刪除右子樹中元素值最小的那個節點。

如果左右子樹中有一個為空,那麼直接用那個非空子樹或者是NULL替換當前根節點即可。

2、要刪除的節點元素值小於當前根節點T值,在左子樹中進行刪除。

遞迴呼叫,在左子樹中實施刪除。

這個是需要判斷當前根節點是否仍然滿足平衡條件,

如果滿足平衡條件,只需要更新當前根節點T的高度資訊。

否則,需要進行旋轉調整:

如果T的左子節點的左子樹的高度大於T的左子節點的右子樹的高度,進行相應的單旋轉。否則進行雙旋轉。

3、要刪除的節點元素值大於當前根節點T值,在右子樹中進行刪除。

五、程式碼實現

AvlTree.h

#include <iostream>
#include <algorithm>
using namespace std;
#pragma once

//平衡二元樹結點
template <typename T>
struct AvlNode
{
    T data;
    int height; //結點所在高度
    AvlNode<T> *left;
    AvlNode<T> *right;
    AvlNode<T>(const T theData) : data(theData), left(NULL), right(NULL), height(0){}
};

//AvlTree
template <typename T>
class AvlTree
{
public:
    AvlTree<T>(){}
    ~AvlTree<T>(){}
    AvlNode<T> *root;
    //插入結點
    void Insert(AvlNode<T> *&t, T x);
    //刪除結點
    bool Delete(AvlNode<T> *&t, T x);
    //查詢是否存在給定值的結點
    bool Contains(AvlNode<T> *t, const T x) const;
    //中序遍歷
    void InorderTraversal(AvlNode<T> *t);
    //前序遍歷
    void PreorderTraversal(AvlNode<T> *t);
    //最小值結點
    AvlNode<T> *FindMin(AvlNode<T> *t) const;
    //最大值結點
    AvlNode<T> *FindMax(AvlNode<T> *t) const;
private:
    //求樹的高度
    int GetHeight(AvlNode<T> *t);
    //單旋轉 左
    AvlNode<T> *LL(AvlNode<T> *t);
    //單旋轉 右
    AvlNode<T> *RR(AvlNode<T> *t);
    //雙旋轉 右左
    AvlNode<T> *LR(AvlNode<T> *t);
    //雙旋轉 左右
    AvlNode<T> *RL(AvlNode<T> *t);
};

template <typename T>
AvlNode<T> * AvlTree<T>::FindMax(AvlNode<T> *t) const
{
    if (t == NULL)
        return NULL;
    if (t->right == NULL)
        return t;
    return FindMax(t->right);
}

template <typename T>
AvlNode<T> * AvlTree<T>::FindMin(AvlNode<T> *t) const
{
    if (t == NULL)
        return NULL;
    if (t->left == NULL)
        return t;
    return FindMin(t->left);
}


template <typename T>
int AvlTree<T>::GetHeight(AvlNode<T> *t)
{
    if (t == NULL)
        return -1;
    else
        return t->height;
}


//單旋轉
//左左插入導致的不平衡
template <typename T>
AvlNode<T> * AvlTree<T>::LL(AvlNode<T> *t)
{
    AvlNode<T> *q = t->left;
    t->left = q->right;
    q->right = t;
    t = q;
    t->height = max(GetHeight(t->left), GetHeight(t->right)) + 1;
    q->height = max(GetHeight(q->left), GetHeight(q->right)) + 1;
    return q;
}

//單旋轉
//右右插入導致的不平衡
template <typename T>
AvlNode<T> * AvlTree<T>::RR(AvlNode<T> *t)
{
    AvlNode<T> *q = t->right;
    t->right = q->left;
    q->left = t;
    t = q;
    t->height = max(GetHeight(t->left), GetHeight(t->right)) + 1;
    q->height = max(GetHeight(q->left), GetHeight(q->right)) + 1;
    return q;
}

//雙旋轉
//插入點位於t的左兒子的右子樹
template <typename T>
AvlNode<T> * AvlTree<T>::LR(AvlNode<T> *t)
{
    //雙旋轉可以通過兩次單旋轉實現
    //對t的左結點進行RR旋轉,再對根節點進行LL旋轉
    RR(t->left);
    return LL(t);
}

//雙旋轉
//插入點位於t的右兒子的左子樹
template <typename T>
AvlNode<T> * AvlTree<T>::RL(AvlNode<T> *t)
{
    LL(t->right);
    return RR(t);
}


template <typename T>
void AvlTree<T>::Insert(AvlNode<T> *&t, T x)
{
    if (t == NULL)
        t = new AvlNode<T>(x);
    else if (x < t->data)
    {
        Insert(t->left, x);
        //判斷平衡情況
        if (GetHeight(t->left) - GetHeight(t->right) > 1)
        {
            //分兩種情況 左左或左右

            if (x < t->left->data)//左左
                t = LL(t);
            else                  //左右
                t = LR(t);
        }
    }
    else if (x > t->data)
    {
        Insert(t->right, x);
        if (GetHeight(t->right) - GetHeight(t->left) > 1)
        {
            if (x > t->right->data)
                t = RR(t);
            else
                t = RL(t);
        }
    }
    else
        ;//資料重複
    t->height = max(GetHeight(t->left), GetHeight(t->right)) + 1;
}

template <typename T>
bool AvlTree<T>::Delete(AvlNode<T> *&t, T x)
{
    //t為空 未找到要刪除的結點
    if (t == NULL)
        return false;
    //找到了要刪除的結點
    else if (t->data == x)
    {
        //左右子樹都非空
        if (t->left != NULL && t->right != NULL)
        {//在高度更大的那個子樹上進行刪除操作

            //左子樹高度大,刪除左子樹中值最大的結點,將其賦給根結點
            if (GetHeight(t->left) > GetHeight(t->right))
            {
                t->data = FindMax(t->left)->data;
                Delete(t->left, t->data);
            }
            else//右子樹高度更大,刪除右子樹中值最小的結點,將其賦給根結點
            {
                t->data = FindMin(t->right)->data;
                Delete(t->right, t->data);
            }
        }
        else
        {//左右子樹有一個不為空,直接用需要刪除的結點的子結點替換即可
            AvlNode<T> *old = t;
            t = t->left ? t->left: t->right;//t賦值為不空的子結點
            delete old;
        }
    }
    else if (x < t->data)//要刪除的結點在左子樹上
    {
        //遞迴刪除左子樹上的結點
        Delete(t->left, x);
        //判斷是否仍然滿足平衡條件
        if (GetHeight(t->right) - GetHeight(t->left) > 1)
        {
            if (GetHeight(t->right->left) > GetHeight(t->right->right))
            {
                //RL雙旋轉
                t = RL(t);
            }
            else
            {//RR單旋轉
                t = RR(t);
            }
        }
        else//滿足平衡條件 調整高度資訊
        {
            t->height = max(GetHeight(t->left), GetHeight(t->right)) + 1;
        }
    }
    else//要刪除的結點在右子樹上
    {
        //遞迴刪除右子樹結點
        Delete(t->right, x);
        //判斷平衡情況
        if (GetHeight(t->left) - GetHeight(t->right) > 1)
        {
            if (GetHeight(t->left->right) > GetHeight(t->left->left))
            {
                //LR雙旋轉
                t = LR(t);
            }
            else
            {
                //LL單旋轉
                t = LL(t);
            }
        }
        else//滿足平衡性 調整高度
        {
            t->height = max(GetHeight(t->left), GetHeight(t->right)) + 1;
        }
    }

    return true;
}

//查詢結點
template <typename T>
bool AvlTree<T>::Contains(AvlNode<T> *t, const T x) const
{
    if (t == NULL)
        return false;
    if (x < t->data)
        return Contains(t->left, x);
    else if (x > t->data)
        return Contains(t->right, x);
    else
        return true;
}

//中序遍歷
template <typename T>
void AvlTree<T>::InorderTraversal(AvlNode<T> *t)
{
    if (t)
    {
        InorderTraversal(t->left);
        cout << t->data << ' ';
        InorderTraversal(t->right);
    }
}

//前序遍歷
template <typename T>
void AvlTree<T>::PreorderTraversal(AvlNode<T> *t)
{
    if (t)
    {
        cout << t->data << ' ';
        PreorderTraversal(t->left);
        PreorderTraversal(t->right);
    }
}

main.cpp

#include "AvlTree.h"

int main()
{
    AvlTree<int> tree;
    int value;
    int tmp;
    cout << "請輸入整數建立二元樹(-1結束):" << endl;
    while (cin >> value)
    {
        if (value == -1)
            break;
        tree.Insert(tree.root,value);
    }
    cout << "中序遍歷";
    tree.InorderTraversal(tree.root);
    cout << "n前序遍歷:";
    tree.PreorderTraversal(tree.root);
    cout << "n請輸入要查詢的結點:";
    cin >> tmp;
    if (tree.Contains(tree.root, tmp))
        cout << "已查詢到" << endl;
    else
        cout << "值為" << tmp << "的結點不存在" << endl;
    cout << "請輸入要刪除的結點:";
    cin >> tmp;
    tree.Delete(tree.root, tmp);
    cout << "刪除後的中序遍歷:";
    tree.InorderTraversal(tree.root);
    cout << "n刪除後的前序遍歷:";
    tree.PreorderTraversal(tree.root);
}

測試結果

以上就是詳解如何用c++實現平衡二元樹的詳細內容,更多關於c++平衡二元樹的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com