二叉搜索樹給我們了一個搜索一個點的方法,同時,也將二叉樹中的節點排序了,但是,對於一些特殊的二叉樹來說,使用二叉搜索樹會很費空間,比如說:
左單支的這種情況,你說是二叉樹,還不如說是單鏈表呢,還節省了一大波的空間呢(右指針域),同樣的,對於右單支的情況也是如此,那麼現在我們就要想能不能避免這個問題。
可以,一個平衡因子就可以搞定,加了平衡因子,那麼這顆二叉搜索樹就是AVLTree了
那麼現在我們就來分析一下AVLTree,首先還是看一下平衡因子吧:平衡因子的實質就是右子樹的高度減去左子樹的高度的差值(當然你也可以用左減去右),既然是平衡因子,那麼就有一個範圍,這裏的範圍就是:-1,0,1。每一個結點的平衡因子都是這三個值中的一個,不是,那麼就不平衡。那麼爲什麼會是這三個呢?
下面我們來分析一下,首先0就不用說了,表示樹的左右子樹高度相等的,當然是平衡的,那麼1和-1呢,這其實都是一個類型的,我們之說一個就行了,就按照-1來說吧,我們舉一個最簡單的例子。如果一個樹只有兩個節點,那麼會出現什麼結果呢?
這就是-1的結果,因此,所有,結點的右子樹的高度減去左子樹的高度的差值的絕對值如果大於1,那麼他就不是平衡樹。
瞭解了這些之後,我們就來構建一顆AVLTree吧
首先還是構建一個樹的結點,其實二叉搜索樹都已經寫過了,這個都很簡單了,這就比二叉搜索樹多了一個平衡因子而已嘛。
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode(const K& key, const V& value)
: _pLeft(NULL)
, _pRight(NULL)
, _pParent(NULL)
, _key(key)
, _value(value)
, _bf(0)
{}
AVLTreeNode<K, V> *_pLeft; //左子樹
AVLTreeNode<K, V> *_pRight; // 右子樹
AVLTreeNode<K, V> *_pParent; //雙親
K _key;
V _value;
int _bf; // 平衡因子:right-left
};
然後我們按照一定的規則來插入元素,其實這些規則都是二叉搜索樹的,無非就是當你的Key小於當前節點的Key的時候,從左邊遍歷,大於就從右邊遍歷,找到之後就插入,不過這裏在插入之後要判斷是否滿足平衡樹的條件,如果不滿足的話,那麼需要調平。
那麼我們就重點來看一下怎樣調平的吧:
首先插入進去之後平衡因子會變化,那麼都有哪些結點的平衡因子變化了呢,我們不妨仔細看看,
這張圖中紅色的線鏈接的那個結點是新插入的結點,在插入之前結點:30、40、50的平衡因子都是0,但是插入之後,他們就變成了-1,他們還有一個共同點,那就是,他們都是新插入節點的祖先結點,也就是說,新插入一個結點,那麼從根結點到此節點這條鏈的所有節點的平衡因子都變化。
至於說,如何變化的,顯然如果是其左子樹,那麼-1,右子樹+1
最後就是調平了,我們二叉樹中的一條鏈上的結點的平衡因子變化了,那麼就得看一下變化之後是不是還滿足AVLTree的條件。
總體來說還是得分情況:
1、如果此節點的平衡因子變成0,那麼直接就不用調了。
2、如果此節點是1或-1的話,還是平衡樹,那麼繼續向上邊遍歷,看是否有超過1的
3、如果此節點的平衡因子的絕對值超過1,顯然不滿足平衡樹的條件,這就需要我們做一下旋轉處理了。
旋轉大致分爲四種:左單旋——右右,右單旋——左左,左右雙旋——左右,右左雙旋——右左。
那麼我們來一個一個看,首先來看一下左單旋:
右右的意思是:在此節點右子樹的右側插入一個節點。
如圖所示,其中65或80這兩個結點都滿足這個旋轉條件,爲了方便起見,我們只取一個,來看一下它的旋轉處理:
我們將55這個結點的連接到50的右,同時將50連接到60的左。不過,在做這些之前,我們需要將一些節點先保存起來,這樣,變化之後,我們再將50和60這兩個結點的平衡因子都置爲0。
//左單旋——右右
void _RotateL(Node* parent)
{
Node* subR = parent->_pRight;
Node* subRL = subR->_pLeft;//有可能不存在
parent->_pRight = subRL;
if (subRL)
subRL->_pParent = parent;
subR->_pLeft = parent;
Node* gparent = parent->_pParent;//保存parent的雙親
parent->_pParent = subR;
subR->_pParent = gparent;
if (gparent == NULL)//parent是根結點
_pRoot = subR;
else if (gparent->_pLeft == parent)
gparent->_pLeft = subR;
else
gparent->_pRight = subR;
//旋轉之後將其清零——結合圖理解
parent->_bf = 0;
subR->_bf = 0;
}
右單旋——左左
左左的意思是:在此節點的左子樹的左側,其實如果你理解了左單旋,你會發現,右單旋就和左單旋互換一個左右即可。
這裏的20和35都是新插入的結點,同樣的 ,我們也取出一個作爲例子來看它的旋轉
這個理念和做單選的一樣,在這裏做出一個建議,不要去嘗試記憶兩個,而要試着理解一個,那麼另一個就像鏡子裏的自己,顯而易見的。
雙旋也就是兩個單旋的組合,而左右雙旋和右左雙旋的區別就是左單旋和右單旋的順序問題。
左右單旋:先進行左單旋,再進行右單旋
這就是一個可以進行左右單旋的例子,那麼來看一下它的旋轉吧
同樣的右左雙旋的理念也是這樣的:先進行右旋,在進行左旋。
那麼接下來我們就來看一下實現的過程吧,這個過程我debug的好多次,其中有各種的小毛病,註釋上面都有,所以,看到的都要引以爲戒。
AVLTree創建成功之後,我們得測驗一下吧,就是判斷這個二叉樹是不是平衡樹,按照二叉樹的定義來,我們只需判斷根結點的平衡因子是不是滿足條件即可,然後再遞歸左子樹和右子樹。
不過,這裏有一個需要注意的地方,就是平衡因子的計算是不是正確的,這個就是我剛纔說的小毛病,所以,在判斷平衡因子之前,我們再來計算一下平衡因子,如果和結點中的平衡因子不同,那麼就不用在看其是否滿足條件了。
// 檢測二叉樹是否爲平衡樹?
bool _IsBalanceTree(Node* pRoot)
{
if (pRoot == NULL)
return true;
//求出左右子樹的高度
size_t left = _Height(pRoot->_pLeft);
size_t right = _Height(pRoot->_pRight);
//如果平衡因子計算錯誤 或 平衡因子大於1 那麼不是平衡樹
if ((right - left) != pRoot->_bf || pRoot->_bf > 1)
return false;
return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot->_pRight);
}
下面是全部的代碼,這裏的測試用例很好,基本上涵蓋了所有的旋轉問題。
#include<iostream>
using namespace std;
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode(const K& key, const V& value)
: _pLeft(NULL)
, _pRight(NULL)
, _pParent(NULL)
, _key(key)
, _value(value)
, _bf(0)
{}
AVLTreeNode<K, V> *_pLeft; //左子樹
AVLTreeNode<K, V> *_pRight; // 右子樹
AVLTreeNode<K, V> *_pParent; //雙親
K _key;
V _value;
int _bf; // 平衡因子:right-left
};
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
AVLTree()
: _pRoot(NULL)
{}
bool Insert(const K& key, const V& value)
{
if (_pRoot == NULL)//如果樹爲空,直接插入
{
_pRoot = new Node(key, value);
return true;
}
//找插入位置
Node* pCur = _pRoot;
Node* parent = NULL;
while (pCur)
{
if (key < pCur->_key)
{
parent = pCur;
pCur = pCur->_pLeft;
}
else if (key>pCur->_key)
{
parent = pCur;
pCur = pCur->_pRight;
}
else
return false;
}
//插入
pCur = new Node(key, value);//不要忘
if (key < parent->_key)
parent->_pLeft = pCur;
else
parent->_pRight = pCur;
pCur->_pParent = parent;//插入之後將pCur的雙親的指針指向parent
while (parent != NULL)
{
//平衡樹可能不平衡了,重新調整
if (parent->_pLeft == pCur)
parent->_bf--;//不管pCur的兄弟結點是否存在,直接--,即可
else
parent->_bf++;
//分情況討論
if (parent->_bf == 0)//層數沒有增加
return true;
else if (parent->_bf == 1 || parent->_bf == -1)//層數加一,此時這個子樹還是平衡的,繼續向上調整
{
pCur = parent;
parent = parent->_pParent;
}
else// 2||-2 已經不平衡了,需要進行旋轉處理
{
if (parent->_bf == 2)
{
if (pCur->_bf == 1)
_RotateL(parent);//右右——左單旋
else
_RotateRL(parent);//右左——右左雙旋
}
else
{
if (pCur->_bf == -1)
_RotateR(parent);//左左——右單旋
else
_RotateLR(parent);//左右——左右雙旋
}
//插入只是影響樹的局部,調整之後,樹平衡
break;
}
}
}
void InOrder()
{
cout << "InOrder: ";
_InOrder(_pRoot);
cout << endl;
}
size_t Height()
{
return _Height(_pRoot);
}
bool IsBalanceTree()
{
return _IsBalanceTree(_pRoot);
}
private:
// 檢測二叉樹是否爲平衡樹?
bool _IsBalanceTree(Node* pRoot)
{
if (pRoot == NULL)
return true;
//求出左右子樹的高度
size_t left = _Height(pRoot->_pLeft);
size_t right = _Height(pRoot->_pRight);
//如果平衡因子計算錯誤 或 平衡因子大於1 那麼不是平衡樹
if ((right - left) != pRoot->_bf || pRoot->_bf > 1)
return false;
return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot->_pRight);
}
size_t _Height(Node* pRoot)
{
if (pRoot == NULL)
return 0;
if (pRoot->_pLeft == NULL&&pRoot->_pRight == NULL)
return 1;
size_t left = _Height(pRoot->_pLeft);
size_t right = _Height(pRoot->_pRight);
return left > right ? left + 1 : right + 1;
}
void _InOrder(Node* pRoot)
{
if (pRoot)
{
_InOrder(pRoot->_pLeft);
cout << pRoot->_key << " ";
_InOrder(pRoot->_pRight);
}
}
//左單旋——右右
void _RotateL(Node* parent)
{
Node* subR = parent->_pRight;
Node* subRL = subR->_pLeft;//有可能不存在
parent->_pRight = subRL;
if (subRL)
subRL->_pParent = parent;
subR->_pLeft = parent;
Node* gparent = parent->_pParent;//保存parent的雙親
parent->_pParent = subR;
subR->_pParent = gparent;
if (gparent == NULL)//parent是根結點
_pRoot = subR;
else if (gparent->_pLeft == parent)
gparent->_pLeft = subR;
else
gparent->_pRight = subR;
//旋轉之後將其清零——結合圖理解
parent->_bf = 0;
subR->_bf = 0;
}
//右單旋——左左(在較高左子樹的左插入結點)
void _RotateR(Node* parent)
{
Node* subL = parent->_pLeft;
Node* subLR = subL->_pRight;
parent->_pLeft = subLR;
if (subLR)
subLR->_pParent = parent;
subL->_pRight = parent;
Node* gparent = parent->_pParent;
parent->_pParent = subL;
subL->_pParent = gparent;//總共三個,一個都不能忘
if (gparent == NULL)
_pRoot = subL;
else if (gparent->_pLeft == parent)
gparent->_pLeft = subL;
else
gparent->_pRight = subL;
parent->_bf = 0;
subL->_bf = 0;
}
//左右雙旋——左右
void _RotateLR(Node* parent)
{
Node* subL = parent->_pLeft;
Node* subLR = subL->_pRight;
int bf = subLR->_bf;
_RotateL(subL);
_RotateR(parent);
if (-1 == bf)
parent->_bf = 1;
else if (1 == bf)
subL->_bf = -1;
else//只有三個結點的時候
return;
}
//右左雙旋——右左
void _RotateRL(Node* parent)//debug
{
Node* subR = parent->_pRight;
Node* subRL = subR->_pLeft;
int bf = subRL->_bf;
_RotateR(subR);
_RotateL(parent);
if (1 == bf)
parent->_bf = -1;
else if (-1 == bf)
subR->_bf = 1;
else
return;
}
private:
Node* _pRoot;
};
void FunTest()
{
int array[] = {16, 3, 7, 11, 9, 26, 18, 14, 15};
AVLTree<int, int> t;
for (size_t idx = 0; idx < sizeof(array) / sizeof(array[0]); ++idx)
t.Insert(array[idx], idx);
t.InOrder();
if (t.IsBalanceTree())
{
cout << "是AVL樹" << endl;
}
else
{
cout << "不是AVL樹" << endl;
}
}
int main()
{
FunTest();
return 0;
}