一、前言
《二叉查找樹全面詳細介紹》中講解了二叉樹操作:搜索(查找)、遍歷、插入、刪除。《二叉樹遍歷詳解(遞歸遍歷、非遞歸棧遍歷,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();
}
執行結果: