一、AVL樹的概念
1.二叉搜索樹
二叉搜索樹(BST,Binary Search Tree),也稱二叉排序樹或二叉查找樹。
二叉搜索樹:一棵二叉樹,可以為空;如果不為空,滿足以下性質:
- 非空左子樹的所有鍵值小于其根結點的鍵值。
- 非空右子樹的所有鍵值大于其根結點的鍵值。
- 左、右子樹都是二叉搜索樹。
二叉搜索樹的性能:
- 最優情況下,二叉搜索樹為完全二叉樹(或者接近完全二叉樹),其平均比較次數為:logN
- 最差情況下,二叉搜索樹退化為單支樹(或者類似單支),其平均比較次數為:N
在最壞情況下,二叉搜索樹的性能就失去了,為了在最壞情況下也能發揮二叉搜索樹的性能,VAL樹和紅黑樹的出現就解決了這個問題。
2.AVL樹
平衡二叉樹 全稱叫做平衡二叉搜索(排序)樹,簡稱 AVL樹。AVL 是大學教授G.M. Adelson-Velsky 和E.M. Landis名稱的縮寫,他們提出的平衡二叉樹的概念,為了紀念他們,將平衡二叉樹稱為 AVL樹。
方法:當向二叉搜索樹中插入新結點后,如果能保證每個結點的左右子樹高度之差的絕對值不超過1(需要對樹中的結點進行調整),即可降低樹的高度,從而減少平均搜索長度。
AVL樹可以是一棵空樹,也可以是具有以下性質的一棵二叉搜索樹:
- 樹的左右子樹都是AVL樹。
- 樹的左右子樹高度之差(簡稱平衡因子)的絕對值不超過1。
如果一棵二叉搜索樹的高度是平衡的,它就是AVL樹。如果它有n個結點,其高度可保持在LogN,搜索時間復雜度也是LogN。
二、AVL樹的定義
這里實現的是KV模型的AVL樹,因為set和map的底層是KV模型。
template<class K, class V>
struct AVLTreeNode
{//三叉鏈AVLTreeNode<K, V>* _left; //左子樹AVLTreeNode<K, V>* _right; //右子樹AVLTreeNode<K, V>* _parent; //自己的父親//存儲的鍵值對pair<K, V> _kv;//平衡因子(balance factor)int _bf; //右子樹高度-左子樹高度//構造函數AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}
};
三、AVL樹的插入
AVL樹插入結點時有以下三個步驟:
- 按照二叉搜索樹的插入方法(如果要插入的值比當前節點小,就往左子樹找;如果比當前節點大,就往右子樹找),找到待插入位置。
- 找到待插入位置后,將待插入結點插入到樹中。
- 更新平衡因子,如果出現不平衡,則需要進行旋轉。
由于一個結點的平衡因子是否需要更新,是取決于該結點的左右子樹的高度是否發生了變化,因此插入一個結點后,該結點的祖先結點的平衡因子可能需要更新。
?我們插入結點后需要倒著往上更新平衡因子,更新規則如下:
- 新增結點在parent的右邊,parent的平衡因子+ +?
- 新增結點在parent的左邊,parent的平衡因子? ??
每更新完一個結點的平衡因子后,都需要進行以下判斷:
- 如果parent的平衡因子等于-1或者1,表明還需要繼續往上更新平衡因子。
- 如果parent的平衡因子等于0,表明無需繼續往上更新平衡因子了。
- 如果parent的平衡因子等于-2或者2,表明此時以parent結點為根結點的子樹已經不平衡了,需要進行旋轉處理。
1.左單旋
新節點插入較高的右子樹的右側(右右):左單旋
我們可以看到,插入9以后,parent的平衡因子已經發生改變,所以需要向左旋轉。
左單旋的步驟如下:
- 讓subR的左子樹作為parent的右子樹。
- 讓parent作為subR的左子樹。
- 讓subR作為整個子樹的根。
- 更新平衡因子。
/左單旋
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent; //parent的父親節點//1、建立subR和parent之間的關系parent->_parent = subR;subR->_left = parent;//2、建立parent和subRL之間的關系parent->_right = subRL;if (subRL)subRL->_parent = parent;//3、建立parentParent和subR之間的關系if (parentParent == nullptr) //說明parent是根節點,不需要再向上調整{_root = subR;subR->_parent = nullptr; //subR的_parent指向需改變}else{if (parent == parentParent->_left) //說明parent不是根節點,并且是他的父節點的左子樹{parentParent->_left = subR;}else //parent == parentParent->_right //說明parent不是根節點,并且是他的父節點的右子樹{parentParent->_right = subR;}subR->_parent = parentParent;}//4、更新平衡因子subR->_bf = parent->_bf = 0;
}
2.右單旋
新節點插入較高的左子樹的左側(左左):右單旋
我們可以看到,插入1以后,parent的平衡因子已經發生改變,所以需要向右旋轉。
右單旋的步驟如下:
- 讓subL的右子樹作為parent的左子樹。
- 讓parent作為subL的右子樹。
- 讓subL作為整個子樹的根。
- 更新平衡因子。
//右單旋
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentParent = parent->_parent; //parent的父親節點//1、建立subL和parent之間的關系subL->_right = parent;parent->_parent = subL;//2、建立parent和subLR之間的關系parent->_left = subLR;if (subLR)subLR->_parent = parent;//3、建立parentParent和subL之間的關系if (parentParent == nullptr) //說明parent是根節點,不需要再向上調整{_root = subL;_root->_parent = nullptr;}else{if (parent == parentParent->_left) //說明parent不是根節點,并且是他的父節點的左子樹{parentParent->_left = subL;}else //parent == parentParent->_right //說明parent不是根節點,并且是他的父節點的右子樹{parentParent->_right = subL;}subL->_parent = parentParent;}//4、更新平衡因子subL->_bf = parent->_bf = 0;
}
3.左右雙旋
新節點插入較高左子樹的右側(左右):先左單旋再右單旋
左右雙旋的步驟如下:
- 以subL為旋轉點進行左單旋。
- 以parent為旋轉點進行右單旋。
- 更新平衡因子。
subLR的平衡因子又分為三種情況:
- 當subLR原始平衡因子是-1時,左右雙旋后parent、subL、subLR的平衡因子分別更新為1、0、0。
- 當subLR原始平衡因子是1時,左右雙旋后parent、subL、subLR的平衡因子分別更新為0、-1、0。
- 當subLR原始平衡因子是0時,左右雙旋后parent、subL、subLR的平衡因子分別更新為0、0、0。
//左右雙旋
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf; //subLR不可能為nullptr,因為subL的平衡因子是1//1、以subL為旋轉點進行左單旋RotateL(subL);//2、以parent為旋轉點進行右單旋RotateR(parent);//3、更新平衡因子if (bf == 1){subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}else if (bf == -1){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 0){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false); //在旋轉前樹的平衡因子就有問題}
}
4.右左雙旋
新節點插入較高右子樹的左側(左右):先右單旋再左單旋
右左雙旋的步驟如下:
- 以subR為旋轉點進行右單旋。
- 以parent為旋轉點進行左單旋。
- 更新平衡因子。
subRL的平衡因子又分為三種情況:
- 當subRL原始平衡因子是1時,左右雙旋后parent、subR、subRL的平衡因子分別更新為-1、0、0。
- 當subRL原始平衡因子是-1時,左右雙旋后parent、subR、subRL的平衡因子分別更新為0、1、0。
- 當subRL原始平衡因子是0時,左右雙旋后parent、subR、subRL的平衡因子分別更新為0、0、0。
//右左雙旋
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;//1、以subR為軸進行右單旋RotateR(subR);//2、以parent為軸進行左單旋RotateL(parent);//3、更新平衡因子if (bf == 1){subRL->_bf = 0;parent->_bf = -1;subR->_bf = 0;}else if (bf == -1){subRL->_bf = 0;parent->_bf = 0;subR->_bf = 1;}else if (bf == 0){subRL->_bf = 0;parent->_bf = 0;subR->_bf = 0;}else{assert(false); //在旋轉前樹的平衡因子就有問題}
}
5.整體代碼
//插入函數
bool Insert(const pair<K, V>& kv)
{if (_root == nullptr) //若AVL樹為空樹,則插入結點直接作為根結點{_root = new Node(kv);return true;}//1、按照二叉搜索樹的插入方法,找到待插入位置Node* cur = _root;Node* parent = nullptr;while (cur){if (kv.first < cur->_kv.first) //待插入結點的key值小于當前結點的key值{//往該結點的左子樹走parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first) //待插入結點的key值大于當前結點的key值{//往該結點的右子樹走parent = cur;cur = cur->_right;}else //待插入結點的key值等于當前結點的key值{//插入失敗(不允許key值冗余)return false;}}//2、將待插入結點插入到樹中cur = new Node(kv); //根據所給值構造一個新結點if (kv.first < parent->_kv.first) //新結點的key值小于parent的key值{//插入到parent的左邊parent->_left = cur;cur->_parent = parent;}else //新結點的key值大于parent的key值{//插入到parent的右邊parent->_right = cur;cur->_parent = parent;}//3、更新平衡因子,如果出現不平衡,則需要進行旋轉while (cur != _root) //最壞一路更新到根結點{if (cur == parent->_left) //parent的左子樹增高{parent->_bf--; //parent的平衡因子--}else if (cur == parent->_right) //parent的右子樹增高{parent->_bf++; //parent的平衡因子++}//判斷是否更新結束或需要進行旋轉if (parent->_bf == 0) //更新結束(新增結點把parent左右子樹矮的那一邊增高了,此時左右高度一致){break; //parent樹的高度沒有發生變化,不會影響其父結點及以上結點的平衡因子}else if (parent->_bf == -1 || parent->_bf == 1) //需要繼續往上更新平衡因子{//parent樹的高度變化,會影響其父結點的平衡因子,需要繼續往上更新平衡因子cur = parent;parent = parent->_parent;}else if (parent->_bf == -2 || parent->_bf == 2) //需要進行旋轉(此時parent樹已經不平衡了){if (parent->_bf == -2){if (cur->_bf == -1){RotateR(parent); //右單旋}else //cur->_bf == 1{RotateLR(parent); //左右雙旋}}else //parent->_bf == 2{if (cur->_bf == -1){RotateRL(parent); //右左雙旋}else //cur->_bf == 1{RotateL(parent); //左單旋}}break; //旋轉后就一定平衡了,無需繼續往上更新平衡因子(旋轉后樹高度變為插入之前了)}else{assert(false); //在插入前樹的平衡因子就有問題}}return true; //插入成功
}
四、AVL樹的驗證
因為AVL樹的本質是二叉搜索樹,左子樹<根節點<右子樹,所以用中序遍歷的方法即可判斷。
但中序有序只能證明是二叉搜索樹,要證明二叉樹是AVL樹還需驗證二叉樹的平衡性,在該過程中我們可以順便檢查每個結點當中平衡因子是否正確。
采用后序遍歷,變量步驟如下:
- 從葉子結點處開始計算每課子樹的高度。(每棵子樹的高度 = 左右子樹中高度的較大值 + 1)
- 先判斷左子樹是否是平衡二叉樹。
- 再判斷右子樹是否是平衡二叉樹。
- 若左右子樹均為平衡二叉樹,則返回當前子樹的高度給上一層,繼續判斷上一層的子樹是否是平衡二叉樹,直到判斷到根為止。(若判斷過程中,某一棵子樹不是平衡二叉樹,則該樹也就不是平衡二叉樹了)
//判斷是否為AVL樹
bool IsAVLTree()
{int hight = 0; //輸出型參數return _IsBalanced(_root, hight);
}
//檢測二叉樹是否平衡
bool _IsBalanced(Node* root, int& hight)
{if (root == nullptr) //空樹是平衡二叉樹{hight = 0; //空樹的高度為0return true;}//先判斷左子樹int leftHight = 0;if (_IsBalanced(root->_left, leftHight) == false)return false;//再判斷右子樹int rightHight = 0;if (_IsBalanced(root->_right, rightHight) == false)return false;//檢查該結點的平衡因子if (rightHight - leftHight != root->_bf){cout << "平衡因子設置異常:" << root->_kv.first << endl;}//把左右子樹的高度中的較大值+1作為當前樹的高度返回給上一層hight = max(leftHight, rightHight) + 1;return abs(rightHight - leftHight) < 2; //平衡二叉樹的條件
}