資料結構之高度平衡搜尋樹AVL樹(含經典面試題----判斷一棵樹是否是AVL樹)
什麼是AVL樹
AVL樹簡介
AVL樹又稱為高度平衡的二叉搜尋樹,目的在於儘量降低二叉樹的高度,減少平均搜尋長度。
滿足二叉搜尋樹的性質
類比二叉搜尋樹,先將樹的結構確定下來,下面處理滿足AVL樹獨有的性質即可。
滿足AVL樹的性質
- 左子樹和右子樹高度差的絕對值不超過1。
- 樹中的每一個左子樹和每一個右子樹都是AVL樹。
- 每一個節點都有一個平衡因子,AVL樹中任意節點的平衡因子可取值為-1,1,0 。
AVL樹插入、刪除、查詢效率問題
一顆AVL樹有N個節點,高度可以保持在log2N,插入、刪除、查詢的時間複雜度也是O(log2N).
AVL樹的插入
插入一個元素,只需要找到該元素合適的位置,然後插入進去(不能插入相同元素),此時還只是滿足二叉搜尋樹的性質,但是AVL樹是二叉搜尋樹的升級版,所以還要保證樹是平衡的。那就要求每次插入元素後檢查是否會影響樹的高度,即是否影響樹的平衡性,此時插入完畢,如果影響樹的高度就要做出調整。
平衡因子的更新規則
1、左子樹插入,那麼該節點對應的父親節點的bf-=1;
2、右子樹插入節點,該節點對應父親節點的bf+=1;
更新後的情況作如下分析:
情況1:當插入一個節點後當前節點的父節點的平衡因子變為0,說明當前樹是平衡的,不會對上層節點產生影響,所以結束更新直接返回。
情況2:假如插入節點之後該節點的父節點平衡因子變為1或者-1,此時說明之前父節點的平衡因子是0,那就破壞了平衡,那就會對上層節點的平衡性產生影響。此時就需要不斷迴圈向上層更新,只要上層是1或者-1,迴圈就無法停下,直到父節點為根節點為止,才算插入完畢。
情況3:(迴圈向上層更新父節點出現的)如果父節點的平衡因子變成-2 或者2,那麼就要進行旋轉,更新節點的bf值,調整樹的高度。
情況4:節點的平衡因子為-3 或者3,這種情況是不可能出現的,如果有說明原來的樹就不是AVL樹。我們要保證每個樹再插入之前都是一顆AVL樹。
bool Insert(const K& key){
if (_root == NULL){
_root = new Node(key);
return true;
}
Node* cur = _root;
Node* father = NULL;
while (cur){
if (cur-> _key == key){
return false;
}
else if (cur->_key < key){
father = cur;
cur = cur->_right;
}
else{
father = cur;
cur = cur->_left;
}
}
//此時說明已經確定要插入的位置
cur = new Node(key);
if (father->_key < cur->_key){
father->_right = cur;
cur->_parent = father;
}
else(father->_key > cur->_key){
father->_left = cur;
cur->_parent = father;
}
//插入完成後要記得更新平衡因子
while (father){
if (father->_left == cur){
father->_bf -= 1;
}
else (father->_right == cur){
father->_bf += 1;
}
//根據更新後的平衡因子判斷樹是否平衡,如果不平衡進行旋轉調整
if (father->_bf == 0){
return true;
}
else if (father->_bf == -1 || father->_bf == 1){
cur = father;
father = father->_parent;
//此時還不確定是否是不平衡的,要進一步向上追溯看上面父節點的bf
//值是否為-2或者2,如果是就要旋轉,如果不是就不需要旋轉
}
else if (father->_bf == -2 || father->_bf == 2){
if (father->_bf == 2 && cur->_bf == 1){
RotateL(father);
return true;
}
if (father->_bf == -2 && cur->_bf == -1){
RotateR(father);
return true;
}
if (father->_bf == 2 && cur->_bf == -1){
RotateRL(father);
return true;
}
if (father->_bf == -2 && cur->_bf == 1){
RotateLR(father);
return true;
}
}
else{
cout << "平衡因子異常" << endl;
}
}
}
解決情況3的旋轉方法實現(旋轉的分類)
1、左單旋:parent->bf=2;subR->bf=1
思路:
1、將parent節點和subRL節點連結起來(parent->_right=subRL);
2、如果ppNode為空,那麼_root=subR。
3、ppNode不為空時,判斷parent是ppNode的左孩子還是右孩子,如果是左孩子,那麼ppNode->_left=subR;反之ppNode->_right=subR。
4、最後一步將subR和parent連線起來(subR->_left=parent,parent->_parent=subR)。
void RotateL(Node* parent){
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
Node*PpNode = parent->_parent;
parent->_right = SubRL;
if (SubRL != NULL){
SubRL->_parent = parent;
}
if (PpNode == NULL){
_root = SubR;
SubR->_parent = PpNode;
}
else{
if (PpNode->_left == parent){
PpNode->_left = SubR;
SubR->_parent = PpNode;
}
else{
PpNode->_right = SubR;
SubR->_parent = PpNode;
}
}
SubR->_left = parent;
parent->_parent = SubR;
parent->_bf = 0;
SubR->_bf = 0;
}
右單旋:parent->bf=-2;subL->bf=-1
思路:
1、將subLR連到parent的左邊(sub小於subLR 小於parent)。
2、記錄父親的上層節點ppNode。然後判斷ppNode是否為空,是則說明父節點是根節點,只需要將根節點更新成subL即可。
3、ppNode不為空 ,此時只要直到原來的parent節點是ppNode節點的左邊還是右邊就可以確定subL是ppNode節點的左邊還是右邊。
4、最後一步就是將subL和parent連線起來,subL->_right=parent。
void RotateR(Node* parent){
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
Node* PpNode = parent->_parent;
parent->_left = SubLR;
if (SubLR){
SubLR->_parent = parent;
}
if (PpNode == NULL){
_root = SubL;
SubL->_parent = NULL;
}
else{
if (PpNode->_left == parent){
PpNode->_left = SubL;
}
else{
PpNode->_right = SubL;
}
SubL->_parent = PpNode;
}
SubL->_right = parent;
parent->_parent = SubL;
SubL->_bf = 0;
parent->_bf = 0;
}
左右雙旋:parent->bf=-2;subL->bf=1
思路:
由於在subLR的右邊插入了新節點,那麼首先對以subLR為父節點進行左單旋(RotateL(subL)),完成左旋之後在以parent為父節點進行右單旋(RotateR(parent))。
在更新平衡因子時符合以下規律:
原來的subLR | 1(新插入節點在subLR的右邊) | 0 | -1(新插入節點在subLR的左邊) |
---|---|---|---|
更新後的parent | 0 | 0 | 1 |
更新後的subL | 1 | 0 | 0 |
更新後的subLR | 0 | 0 | 0 |
void RotateLR(Node* parent){
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
int bf = SubLR->_bf;
RotateL(SubL);
RotateR(parent);
//更新平衡因子
if (bf == 0){
parent->_bf = 0;
SubL->_bf = 0;
}
else if (bf == 1){
parent->_bf = 0;
SubL->_bf = -1;
}
else{
parent->_bf = 1;
SubL->_bf =0;
}
SubLR->_bf = 0;
}
右左雙旋:parent->bf=2;subR->bf=-1;
思路:
由於在subRL的左邊插入了新節點,那麼首先對以subR為父節點進行右單旋(RotateR(subR)),完成左旋之後在以parent為父節點進行左單旋(RotateL(parent))。
在更新平衡因子時符合以下規律:
原來的subRL | 1(新插入節點在subRL的右邊) | 0 | -1(新插入節點在subRL的左邊) |
---|---|---|---|
更新後的parent | -1 | 0 | 0 |
更新後的subR | 0 | 0 | 1 |
更新後的subRL | 0 | 0 | 0 |
void RotateRL(Node* parent){
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
int bf = SubRL->_bf;
RotateR(subR);
RotateL(parent);
//更新平衡因子
if (bf == 0){
parent->_bf = 0;
SubR->_bf = 0;
SubRL->_bf = 0;
}
else if (bf == 1){
parent->_bf = -1;
SubR->_bf = 0;
SubRL->_bf = 0;
}
else if(bf==-1){
parent->_bf =0;
SubR->_bf = 1;
SubRL->_bf = 0;
}
else{
cout << "引數有誤" << endl;
}
}
經典面試題——判斷一棵樹是否是AVL樹
思路1:遞迴的判斷左右子樹是不是AVL樹,同時判斷某一個子樹是否是AVL樹的時候又得遞迴求一遍其左右子樹的高度,通過高度差來判斷對應子樹是否是AVL樹,這樣一來時間複雜度就是O(N^2)
在你答出上面的思路時候,面試官常常會讓優化時間複雜度!!!
優化思路1的時間複雜度為O(N):判斷一棵樹是否是AVL樹,同時借用一個輸出型引數height帶出左右子樹的高度,利用高度差的絕對值小於2,得出結論。
(不採用遞迴檢視平衡因子的方式是由於有的時候平衡因子更新出錯,導致誤判)
bool IsAVLtree(){
int height = 0;
return _IsAVLtree(_root, height);
}
bool _IsAVLtree(Node* root, int& height){
if (root == NULL){
height = 0;
return true;
}
int lheight = 0;
if (_IsAVLtree(root->_left, lheight) == false){
return false;
}
int rheight = 0;
if (_IsAVLtree(root->_right, rheight) == false){
return false;
}
if (abs(lheight - rheight) !=root->_bf){
cout << "平衡因子有誤\n";
}
height = lheight > rheight ? lheight + 1 : rheight + 1;
return abs(lheight - rheight) < 2;
}
完整程式碼:
#include<iostream>
using namespace std;
template<class K>
struct TreeNode{
public:
TreeNode(const K& key)
:_parent(NULL)
, _left(NULL)
, _right(NULL)
, _key(key)
, _bf(0)
{}
TreeNode<K>* _parent;
TreeNode<K>* _left;
TreeNode<K>* _right;
K _key;
int _bf;
};
template< class K >
class AVLTree{
public:
typedef TreeNode<K> Node;
AVLTree()
:_root(NULL)
{}
bool Insert(const K& key){
if (_root == NULL){
_root = new Node(key);
return true;
}
Node* cur = _root;
Node* father = NULL;
while (cur){
if (cur->_key == key){
return false;
}
else if (cur->_key < key){
father = cur;
cur = cur->_right;
}
else{
father = cur;
cur = cur->_left;
}
}
//此時說明已經確定要插入的位置
cur = new Node(key);
if (father->_key < cur->_key){
father->_right = cur;
cur->_parent = father;
}
else(father->_key > cur->_key){
father->_left = cur;
cur->_parent = father;
}
//插入完成後要記得更新平衡因子
while (father){
if (father->_left == cur){
father->_bf -= 1;
}
else (father->_right == cur){
father->_bf += 1;
}
//根據更新後的平衡因子判斷樹是否平衡,如果不平衡進行旋轉調整
if (father->_bf == 0){
return true;
}
else if (father->_bf == -1 || father->_bf == 1){
cur = father;
father = father->_parent;
//此時還不確定是否是不平衡的,要進一步向上追溯看上面父節點的bf
//值是否為-2或者2,如果是就要旋轉,如果不是就不需要旋轉
}
else if (father->_bf == -2 || father->_bf == 2){
if (father->_bf == 2 && cur->_bf == 1){
RotateL(father);
return true;
}
if (father->_bf == -2 && cur->_bf == -1){
RotateR(father);
return true;
}
if (father->_bf == 2 && cur->_bf == -1){
RotateRL(father);
return true;
}
if (father->_bf == -2 && cur->_bf == 1){
RotateLR(father);
return true;
}
}
else{
cout << "平衡因子異常" << endl;
}
}
}
//左單旋
void RotateL(Node* parent){
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
Node*PpNode = parent->_parent;
parent->_right = SubRL;
if (SubRL != NULL){
SubRL->_parent = parent;
}
if (PpNode == NULL){
_root = SubR;
SubR->_parent = PpNode;
}
else{
if (PpNode->_left == parent){
PpNode->_left = SubR;
SubR->_parent = PpNode;
}
else{
PpNode->_right = SubR;
SubR->_parent = PpNode;
}
}
SubR->_left = parent;
parent->_parent = SubR;
parent->_bf = 0;
SubR->_bf = 0;
}
//右單旋
void RotateR(Node* parent){
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
Node* PpNode = parent->_parent;
parent->_left = SubLR;
if (SubLR){
SubLR->_parent = parent;
}
if (PpNode == NULL){
_root = SubL;
SubL->_parent = NULL;
}
else{
if (PpNode->_left == parent){
PpNode->_left = SubL;
}
else{
PpNode->_right = SubL;
}
SubL->_parent = PpNode;
}
SubL->_right = parent;
parent->_parent = SubL;
SubL->_bf = 0;
parent->_bf = 0;
}
//左右雙旋
void RotateLR(Node* parent){
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
int bf = SubLR->_bf;
RotateL(SubL);
RotateR(parent);
//更新平衡因子
if (bf == 0){
parent->_bf = 0;
SubL->_bf = 0;
}
else if (bf == 1){
parent->_bf = 0;
SubL->_bf = -1;
}
else{
parent->_bf = 1;
SubL->_bf =0;
}
SubLR->_bf = 0;
}
//右左雙旋
void RotateRL(Node* parent){
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
int bf = SubRL->_bf;
RotateR(subR);
RotateL(parent);
//更新平衡因子
if (bf == 0){
parent->_bf = 0;
SubR->_bf = 0;
SubRL->_bf = 0;
}
else if (bf == 1){
parent->_bf = -1;
SubR->_bf = 0;
SubRL->_bf = 0;
}
else if(bf==-1){
parent->_bf =0;
SubR->_bf = 1;
SubRL->_bf = 0;
}
else{
cout << "引數有誤" << endl;
}
}
void InOrder(){
_InOrder(_root);
cout <<endl;
}
void _InOrder(Node* root){
if (root == NULL){
return;
}
_InOrder(_root->_left);
cout << _root->_key << " ";
_InOrder(_root->_right);
}
void Destroy(){
if (_root == NULL){
return;
}
Destroy(_root->_left);
Destroy(_root->_right);
delete _root;
_root = NULL;
}
//經典面試題-----判斷一棵樹是否為平衡樹
bool IsAVLtree(){
int height = 0;
return _IsAVLtree(_root, height);
}
bool _IsAVLtree(Node* root, int& height){
if (root == NULL){
height = 0;
return true;
}
int lheight = 0;
if (_IsAVLtree(root->_left, lheight) == false){
return false;
}
int rheight = 0;
if (_IsAVLtree(root->_right, rheight) == false){
return false;
}
if (abs(lheight - rheight) !=root->_bf){
cout << "平衡因子有誤\n";
}
height = lheight > rheight ? lheight + 1 : rheight + 1;
return abs(lheight - rheight) < 2;
}
private:
Node* _root;
};
int main(){
AVLTree<int> tree;
tree.Insert(1);
tree.Insert(2);
tree.Insert(3);
tree.Insert(4);
tree.InOrder();
cout << tree.IsAVLtree() << endl;
system("pause");
return 0;
}