二叉樹平衡(AVL樹)

一、前言

《二叉查找樹全面詳細介紹》中講解了二叉樹操作:搜索(查找)、遍歷、插入、刪除。《二叉樹遍歷詳解(遞歸遍歷、非遞歸棧遍歷,Morris遍歷)》詳細講解了二叉樹遍歷的幾種方法。《二叉樹平衡(有序數組創建)》和《二叉樹平衡(DSW算法)》介紹了兩種構建平衡二叉樹的方法(完全平衡二叉樹)。

前兩篇討論的算法可以從全局重新平衡樹;每個節點都可能參與樹的重新平衡:或者從節點中移動數據,或者重新設置指針的值。但是,當插入或刪除元素時,將隻影響樹的一部分,此時樹的重新平衡可以只在局部執行。Adel'son-Vel'ski和Landis提出了一種經典方法,用這種方法修改的樹就以他們的名字來命名: AVL樹。

AVL樹(最初叫做可容許樹)要求每個節點左右子樹的高度差最大爲1。

 

二、AVL樹實現

平衡二叉搜索樹(Self-balancing binary search tree)又被稱爲AVL樹(有別於AVL算法),且具有以下性質:它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。

2.1 平衡因子

某結點的左子樹與右子樹的高度(深度)差即爲該結點的平衡因子(BF,Balance Factor)。平衡二叉樹上所有結點的平衡因子只可能是 -1,0 或 1。如果某一結點的平衡因子絕對值大於1則說明此樹不是平衡二叉樹,需要進行調整。爲了方便計算每一結點的平衡因子我們可以爲每個節點賦予height這一屬性,表示此節點的高度。

2.2 AVL節點定義

template<class T>
class AvlNode
{
public:
    T m_el;
    AvlNode<T>* m_left;
    AvlNode<T>* m_right;
    int m_height;
    AvlNode(const T& e, AvlNode<T>* l = nullptr, AvlNode<T>* r = nullptr, int h = 0)
    {
        m_el = e;
        m_left = l;
        m_right = r;
        m_height = h;
    }
};

2.3 插入平衡調整

在插入一個元素之後,當樹的平衡被破壞就需要對樹結構進行調整。樹結構調整我們可以對樹進行左旋轉或者右旋轉。當插入一個節點之後破壞了樹的平衡有四種情況:

1)待插入元素3,將元素插入到節點右節點的右邊(右右插入),如下圖,我們只需要對其進行一次左旋轉即可。回顧一下《二叉樹平衡(DSW算法)》實現平衡的過程,先將一顆樹轉換成只有右節點的樹,然後對其多次左旋轉就可以達到平衡二叉樹的要求。

2)待插入元素1,將元素插入到節點左節點的左邊(左左插入),如下圖,我們只需要對其進行一次右旋轉即可。延伸思考一下,《二叉樹平衡(DSW算法)》是不是可以有兩種實現方法,一:先轉換爲只有右節點的樹,然後對其左旋轉;二:先轉換爲只有左節點的樹,然後對其右旋轉。

3)待插入元素2,將元素插入到節點右節點的左邊(右左插入),如下圖,需要先進行一次右旋轉,再進行一次左旋轉。

4)待插入元素2,將元素插入到節點左節點的右邊(左右插入),如下圖,需要先進行一次左旋轉,再進行一次右旋轉。

 

三、總結

在構建平衡二叉樹的過程中,當插入一個節點之後,新插入的節點破壞樹l平衡此時需要對樹進行旋轉。旋轉調整樹的結構是從插入節點往上進行判斷(用到的是遞歸)樹的高度,如果高度相差爲2,說明該節點以下的樹(包含該節點)不是平衡二叉樹需要進行調整,需要用上面四種調整中的一種,通過樹插入的方向進行確定。

 

四、編碼實現

//==========================================================================
/**
* @file    : AvlTree.h
* @blogs   : 
* @author  : niebingyu
* @title   : AVL樹
* @purpose : 構建AVL樹
*/
//==========================================================================
#pragma once
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

template<class T>
class AvlNode
{
public:
    T m_el;
    AvlNode<T>* m_left;
    AvlNode<T>* m_right;
    int m_height;
    AvlNode(const T& e, AvlNode<T>* l = nullptr, AvlNode<T>* r = nullptr, int h = 0)
    {
        m_el = e;
        m_left = l;
        m_right = r;
        m_height = h;
    }
};

template<typename T>
class AvlTree: public BSTNode<T>
{
public:
    AvlTree() { m_root = NULL; }
	~AvlTree() { clear(); };

    void clear()
    {
        clear(m_root);
        m_root = nullptr;
    }

    // 插入值爲el的結點
    void insert(const T& el) { insert(el, m_root); };
    // 前序遍歷
    void preorder() { preorder(m_root); };
    // 中序遍歷
    void inorder() { inorder(m_root); };    
    
protected:
    void clear(AvlNode<T>* t);
    // 前序遍歷
    void preorder(AvlNode<T>*);  
    // 中序遍歷
    void inorder(AvlNode<T>*);   
    // 獲得當前結點t的高度
    int height(AvlNode<T>*) const;  
    // 在t處。插入值爲x的結點
    void insert(const T&, AvlNode<T>*&);
    // 將當前結點進行右單旋轉,用於左左插入
    void rotateRight(AvlNode<T>*&);
    // 將當前結點進行左單旋轉,用於右右插入
    void rotateLeft(AvlNode<T>*&);
    // 將當前結點的左節點進行左旋轉,再對當前節點進行右旋轉,用於左右插入
    void rotateLeftRight(AvlNode<T>*&);
    // 將當前結點的右節點進行右旋轉,再對當前節點進行左旋轉,用於右左插入
    void rotateRightLeft(AvlNode<T>*&);
    // 訪問節點
    virtual void visit(AvlNode<T>* p) { cout << p->m_el << ' '; } 

private:
    AvlNode<T>* m_root;
};

//在結點t的後面插入值爲x的結點
template<typename T>
void AvlTree<T>::insert(const T& el, AvlNode<T>*& t)
{
    if (t == NULL)	//當前結點爲空
        t = new AvlNode<T>(el, NULL, NULL);
    else if (el < t->m_el)
    {
        insert(el, t->m_left);
        if (height(t->m_left) - height(t->m_right) == 2)
        {
            if (el < t->m_left->m_el)	//右旋轉,左左插入
                rotateRight(t);
            else
                rotateLeftRight(t);     //先左旋轉再右旋轉,左右插入
        }
    }
    else if (el > t->m_el)
    {
        insert(el, t->m_right);
        if (height(t->m_right) - height(t->m_left) == 2) 
        {
            if (el > t->m_right->m_el)	 //左旋轉,右右插入
                rotateLeft(t);
            else
                rotateRightLeft(t);     //先右旋轉再左旋轉,右左插入
        }
    }

    //假設x的值和當前結點的值同樣,則忽略。也能夠向之前二叉查找樹一樣給每一個結點再加一個num成員變量。
    t->m_height = max(height(t->m_left), height(t->m_right)) + 1;//更新結點t的高度
}

// 將當前結點進行右單旋轉,用於左左插入
template<typename T>
void AvlTree<T>::rotateRight(AvlNode<T>*& k2)
{
    AvlNode<T>* k1 = k2->m_left;
    k2->m_left = k1->m_right;
    k1->m_right = k2;

    k2->m_height = max(height(k2->m_left), height(k2->m_right)) + 1;
    k1->m_height = max(height(k1->m_left), k2->m_height) + 1;

    k2 = k1;
}

// 將當前結點進行左單旋轉,用於右右插入
template<typename T>
void AvlTree<T>::rotateLeft(AvlNode<T>*& k1)
{
    AvlNode<T>* k2 = k1->m_right;
    k1->m_right = k2->m_left;
    k2->m_left = k1;

    k1->m_height = max(height(k1->m_left), height(k1->m_right)) + 1;
    k2->m_height = max(height(k2->m_right), k1->m_height) + 1;

    k1 = k2;
}

// 將當前結點的左節點進行左旋轉,再對當前節點進行右旋轉,用於左右插入
template<typename T>
void AvlTree<T>::rotateLeftRight(AvlNode<T>*& k3)
{
    rotateLeft(k3->m_left);
    rotateRight(k3);
}

// 將當前結點的右節點進行右旋轉,再對當前節點進行左旋轉,用於右左插入
template<typename T>
void AvlTree<T>::rotateRightLeft(AvlNode<T>*& k1)
{
    rotateRight(k1->m_right);
    rotateLeft(k1);
}

// 獲得當前結點t的高度
template<typename T>
int AvlTree<T>::height(AvlNode<T>* t) const
{
    return (t == NULL) ? -1 : t->m_height;
}

// 前序遍歷
template<typename T>
void AvlTree<T>::preorder(AvlNode<T>* t)
{
    if (t != NULL) 
    {
        visit(t);
        preorder(t->m_right);
        preorder(t->m_left);
    }
}

// 中序遍歷
template<typename T>
void AvlTree<T>::inorder(AvlNode<T>* t)
{
    if (t != NULL) 
    {
        inorder(t->m_left);
        visit(t);
        inorder(t->m_right);
    }
}

// 釋放t指針指向的結點
template<typename T>
void AvlTree<T>::clear(AvlNode<T>* t)
{
    if (t != NULL) 
    {
        clear(t->m_left);
        clear(t->m_right);
        delete t;
    }
}

測試用例:

//==========================================================================
/**
* @file    : AvlTreeTest.h
* @blogs   : 
* @author  : niebingyu
* @title   : 測試AVL平衡二叉樹
* @purpose : 測試AVL平衡二叉樹
*
*/
//==========================================================================
#pragma once
#include "AvlTree.h"
using namespace std;

#define NAMESPACE_AVLTREETEST namespace NAME_AVLTREETEST {
#define NAMESPACE_AVLTREETESTEND }
NAMESPACE_AVLTREETEST
void test(const char* testName, vector<int> data)
{
    cout << "測試用例:" << testName << ", 構造AVL樹" << endl;
    cout << "插入數據:";
    for (int i = 0; i < (int)data.size(); ++i)
        cout << data[i] << " ";
    cout << endl;

    AvlTree<int> tree;
    for (int i = 0; i < (int)data.size(); ++i)
        tree.insert(data[i]);

    cout << "前序遍歷:";
    tree.preorder();
    cout << "\n中序遍歷:";
    tree.inorder();
    cout << endl;
    cout << endl;
}

// 測試用例
void Test1()
{
    vector<int> data = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 };
    test("Test1", data);
}

void Test2()
{
    vector<int> data = { 10,20,15 };
    test("Test1", data);
}
NAMESPACE_AVLTREETESTEND

void AvlTreeTest_Test()
{
    NAME_AVLTREETEST::Test1();
    NAME_AVLTREETEST::Test2();
}

執行結果:

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章