前言
作者:小蝸牛向前沖
名言:我可以接受失敗,但我不能接受放棄
??如果覺的博主的文章還不錯的話,還請
點贊,收藏,關注👀支持博主。如果發現有問題的地方歡迎?大家在評論區指正
目錄
一、AVL樹基本知識
1、概念
2、節點定義
3、插入
二、AVL樹的旋轉
1、右單旋
2、左單旋
?3、左右雙旋
4、?右左雙旋
三、AVL樹的測試?
1、測試的補充代碼
2、測試?
?本期學習目標:清楚什么是AVL樹,模擬實現AVL樹,理解四種旋轉模型。?
一、AVL樹基本知識
1、概念
? ? ? ?二叉搜索樹雖可以縮短查找的效率,但如果數據有序或接近有序二叉搜索樹將退化為單支樹,查 找元素相當于在順序表中搜索元素,效率低下。因此,兩位俄羅斯的數學家G.M.Adelson-Velskii 和E.M.Landis在1962年 發明了一種解決上述問題的方法:當向二叉搜索樹中插入新結點后,如果能保證每個結點的左右 子樹高度之差的絕對值不超過1(需要對樹中的結點進行調整),即可降低樹的高度,從而減少平均 搜索長度。
一棵AVL樹或者是空樹,或者是具有以下性質的二叉搜索樹:
- 它的左右子樹都是AVL樹
- 左右子樹高度之差(簡稱平衡因子)的絕對值不超過1(-1/0/1)
?
2、節點定義
template<class k,class v>
struct AVLTreeNode
{pair<k, v>_kv;AVLTreeNode<k, v>* _left;AVLTreeNode<k, v>* _right;AVLTreeNode<k, v>* _parent;int _bf;//balance factor//帶參數的構造函數AVLTreeNode(const pair<k,v>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};
這里我們定義了三叉鏈來定義節點,最為特殊的是我們相對于二叉樹,我們多了一個平衡?因子,這是維持AVL特性的關鍵,下面我們將圍繞此展開對AVL樹的構建。
注意:平衡因子 =?右樹的高度-左樹的高度
3、插入
AVL樹就是在二叉搜索樹的基礎上引入了平衡因子,因此AVL樹也可以看成是二叉搜索樹。那么 AVL樹的插入過程可以分為兩步:
1. 按照二叉搜索樹的方式插入新節點
2. 調整節點的平衡因子
對于插入最為重要的是平衡因子的更新,下面我們將討論更新平衡因子情況:
是否要在更新平衡因子,要根據子樹的高度:
1、如果parent->_bf==0,者說明以前的parent->_bf==-1或者parent->_bf==1
即是以前是一邊高一邊低,現在是插入到矮的一邊,樹的高度不變,不更新2、如果parent->_bf==-1或者parent->_bf==-1,者以前parent->_bf==0
即是以前樹是均衡的,現在插入讓一邊高了
子樹的高度變了,要向上更新3 、如果parent->_bf==-2或者parent->_bf==2,者以前parent->_bf==-1或者parent->_bf==1
現在樹嚴重不平衡,讓樹旋轉維持結構
//插入
bool Insert(const pair<k, v>& kv)
{if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;//找插入位置while (cur){//插入元素大于比較元素if (cur->_kv.first < kv.first){parent = cur;//繼續往右樹走cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;//繼續往左樹走cur = cur->_left;}else//插入元素于樹中元素相等,不插入{return false;}}cur = new Node(kv);//鏈接節點if (parent->_kv.first > kv.first){parent->_left = cur;//更新parentcur->_parent = parent;}else{parent->_right = cur;//更新parentcur->_parent = parent;}//更新平衡因子while (parent)//parent為空,就更新到了根{//新增在樹節點左邊,parent->bf--//新增在樹節點右邊,parent->bf++if (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}//是否要在更新平衡因子,要根據子樹的高度://1、如果parent->_bf==0,者說明以前的parent->_bf==-1或者parent->_bf==1//即是以前是一邊高一邊低,現在是插入到矮的一邊,樹的高度不變,不更新//2、如果parent->_bf==-1或者parent->_bf==-1,者以前parent->_bf==0//即是以前樹是均衡的,現在插入讓一邊高了//子樹的高度變了,要向上更新//3 、如果parent->_bf==-2或者parent->_bf==2,者以前parent->_bf==-1或者parent->_bf==1//現在樹嚴重不平衡,讓樹旋轉維持結構//旋轉://1、讓子樹的高度差不差過1//2、旋轉過程中也要保存搜索樹結構//3、邊更新平衡因子//4、讓這課樹的高度保存和之前一樣(旋轉結束,不影響上層結構)if (parent->_bf == 0){break;}else if (parent->_bf == -1 || parent->_bf == 1){cur = parent;parent = parent->_parent;}//旋轉else if (parent->_bf == -2 || parent->_bf == 2){//左單旋轉if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}//右單旋else if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}//左右雙旋else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}//右左雙旋else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}else{assert(false);}//旋轉完成,平衡因子已經更新跳出循環break;}else{assert(false);}}
}
二、AVL樹的旋轉
如果parent->_bf==-2或者parent->_bf==2,者以前parent->_bf==-1或者parent->_bf==1
現在樹嚴重不平衡,讓樹旋轉維持結構:
旋轉的要求:
- 讓子樹的高度差不差過1
- 旋轉過程中也要保存搜索樹結構
- 邊更新平衡因子
- 讓這課樹的高度保存和之前一樣(旋轉結束,不影響上層結構)
旋轉的分類:?
- 新節點插入較高左子樹的左側—左左:右單旋
- 新節點插入較高右子樹的右側—右右:左單旋
- 新節點插入較高左子樹的右側—左右:先左單旋再右單旋
- 新節點插入較高右子樹的左側—右左:先右單旋再左單旋
1、右單旋
對于可能出現右旋轉的情況的子樹是多樣的
?這里我們可以根據需要進行右單旋轉抽像圖進行理解
?
代碼實現:?
//右單旋
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;//b做60的右parent->_left = subLR;if (subLR){subLR->_parent = parent;}Node* ppNode = parent->_parent;//60做30的右subL->_right = parent;parent->_parent = subL;//60就是以前的根節點if (ppNode == nullptr){_root = subL;subL->_parent = ppNode;}else{//上層父節點的左邊是子樹的parentif (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}//更新平衡因子parent->_bf = subL->_bf = 0;
}
2、左單旋
?
代碼實現:
?
void RotateL(Node * parent)
{Node* subR = parent->_right;//父節點的右子樹Node* subRL = subR->_left;//右樹的左樹//讓60左邊鏈接到30的右邊parent->_right = subRL;if (subRL){subRL->_parent = parent;}Node* ppNode = parent->_parent;//讓30變成60的左邊subR->_left = parent;parent->_parent = subR;//subR就是根節點if (ppNode == nullptr){_root = subR;_root->_parent = nullptr;}else{//上層父節點的左邊是子樹的parentif (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}//子樹父節點和上層父節點鏈接subR->_parent = ppNode;}//更新平衡因子parent->_bf = subR->_bf = 0;
}
?3、左右雙旋
對于雙旋轉來說:節點新增的位置不同,平衡因子最終也會不同,這里我們要進行分類討論:
對于雙旋轉來說,最為重要的平衡因子的更新。?
?代碼實現:
//左右雙旋
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;//記錄subLR的平衡因子int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);//根據不同情況更新平衡因子if (bf == 1)//在c點處新增(在subLR的右子樹新增){subLR->_bf = 0;parent->_bf = 0;subL->_bf = -1;}else if(bf == -1) // 在b點處新增(在subLR的左子樹新增){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 0) //自己就是增點{subLR->_bf = 0;parent->_bf = 0;subL->_bf = 0;}else{assert(false);}
}
4、?右左雙旋
這里同樣也要進行分類討論:
?
代碼實現:?
//右左雙旋
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;//記錄subLR的平衡因子int bf = subRL->_bf;RotateR (parent->_right);RotateL(parent);//根據不同情況更新平衡因子if (bf == 1)//在c點處新增(在subLR的右子樹新增){subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if (bf == -1) // 在b點處新增(在subLR的左子樹新增){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 0) //自己就是增點{subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else{assert(false);}
}
三、AVL樹的測試?
為了測試我們模擬實現的AVL樹是否成功,還需要進行檢查
1、測試的補充代碼
樹的高度:
int Height()
{return _Height(_root);
}
//求樹的高度
int _Height(Node* root)
{//樹高度為0if (root == nullptr){return 0;}//遞歸求左樹的高度int Lh = _Height(root->_left);//遞歸求右樹的高度int Rh = _Height(root->_right);return Lh > Rh ? Lh + 1 : Rh + 1;
}
檢查平衡因子
//檢測平衡因子bool _IsBalance(Node* root){if (root == nullptr){return true;}int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);if (rightHeight - leftHeight != root->_bf){cout << root->_bf << endl;cout << rightHeight - leftHeight << endl;cout << root->_kv.first << "平衡因子異常" << endl;return false;}return abs(rightHeight - leftHeight) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}
中序遍歷
void InOrder()//這是為了解決在外面調用,不好傳根的問題{_InOrder(_root);}//中序遍歷void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}
2、測試?
完整代碼:
#pragma once
#include<time.h>
#include<assert.h>template<class k,class v>
struct AVLTreeNode
{pair<k, v>_kv;AVLTreeNode<k, v>* _left;AVLTreeNode<k, v>* _right;AVLTreeNode<k, v>* _parent;int _bf;//balance factor//帶參數的構造函數AVLTreeNode(const pair<k,v>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};
template<class k, class v>
struct AVLTree
{typedef AVLTreeNode<k,v> Node;
public://插入bool Insert(const pair<k, v>& kv){if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;//找插入位置while (cur){//插入元素大于比較元素if (cur->_kv.first < kv.first){parent = cur;//繼續往右樹走cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;//繼續往左樹走cur = cur->_left;}else//插入元素于樹中元素相等,不插入{return false;}}cur = new Node(kv);//鏈接節點if (parent->_kv.first > kv.first){parent->_left = cur;//更新parentcur->_parent = parent;}else{parent->_right = cur;//更新parentcur->_parent = parent;}//更新平衡因子while (parent)//parent為空,就更新到了根{//新增在樹節點左邊,parent->bf--//新增在樹節點右邊,parent->bf++if (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}//是否要在更新平衡因子,要根據子樹的高度://1、如果parent->_bf==0,者說明以前的parent->_bf==-1或者parent->_bf==1//即是以前是一邊高一邊低,現在是插入到矮的一邊,樹的高度不變,不更新//2、如果parent->_bf==-1或者parent->_bf==-1,者以前parent->_bf==0//即是以前樹是均衡的,現在插入讓一邊高了//子樹的高度變了,要向上更新//3 、如果parent->_bf==-2或者parent->_bf==2,者以前parent->_bf==-1或者parent->_bf==1//現在樹嚴重不平衡,讓樹旋轉維持結構//旋轉:if (parent->_bf == 0){break;}else if (parent->_bf == -1 || parent->_bf == 1){cur = parent;parent = parent->_parent;}//旋轉else if (parent->_bf == -2 || parent->_bf == 2){//左單旋轉if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}//右單旋else if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}//左右雙旋else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}//右左雙旋else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}else{assert(false);}//旋轉完成,平衡因子已經更新跳出循環break;}else{assert(false);}}}void RotateL(Node * parent){Node* subR = parent->_right;//父節點的右子樹Node* subRL = subR->_left;//右樹的左樹//讓60左邊鏈接到30的右邊parent->_right = subRL;if (subRL){subRL->_parent = parent;}Node* ppNode = parent->_parent;//讓30變成60的左邊subR->_left = parent;parent->_parent = subR;//subR就是根節點if (ppNode == nullptr){_root = subR;_root->_parent = nullptr;}else{//上層父節點的左邊是子樹的parentif (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}//子樹父節點和上層父節點鏈接subR->_parent = ppNode;}//更新平衡因子parent->_bf = subR->_bf = 0;}//右單旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;//b做60的右parent->_left = subLR;if (subLR){subLR->_parent = parent;}Node* ppNode = parent->_parent;//60做30的右subL->_right = parent;parent->_parent = subL;//60就是以前的根節點if (ppNode == nullptr){_root = subL;subL->_parent = ppNode;}else{//上層父節點的左邊是子樹的parentif (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}//更新平衡因子parent->_bf = subL->_bf = 0;}//左右雙旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;//記錄subLR的平衡因子int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);//根據不同情況更新平衡因子if (bf == 1)//在c點處新增(在subLR的右子樹新增){subLR->_bf = 0;parent->_bf = 0;subL->_bf = -1;}else if(bf == -1) // 在b點處新增(在subLR的左子樹新增){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 0) //自己就是增點{subLR->_bf = 0;parent->_bf = 0;subL->_bf = 0;}else{assert(false);}}//右左雙旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;//記錄subLR的平衡因子int bf = subRL->_bf;RotateR (parent->_right);RotateL(parent);//根據不同情況更新平衡因子if (bf == 1)//在c點處新增(在subLR的右子樹新增){subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if (bf == -1) // 在b點處新增(在subLR的左子樹新增){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 0) //自己就是增點{subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else{assert(false);}}int Height(){return _Height(_root);}//求樹的高度int _Height(Node* root){//樹高度為0if (root == nullptr){return 0;}//遞歸求左樹的高度int Lh = _Height(root->_left);//遞歸求右樹的高度int Rh = _Height(root->_right);return Lh > Rh ? Lh + 1 : Rh + 1;}bool IsAVLTree(){return _IsBalance(_root);}//檢測平衡因子bool _IsBalance(Node* root){if (root == nullptr){return true;}int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);if (rightHeight - leftHeight != root->_bf){cout << root->_bf << endl;cout << rightHeight - leftHeight << endl;cout << root->_kv.first << "平衡因子異常" << endl;return false;}return abs(rightHeight - leftHeight) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}void InOrder()//這是為了解決在外面調用,不好傳根的問題{_InOrder(_root);}//中序遍歷void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}private:Node* _root = nullptr;
};void TestAVLTree1()
{//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };/*int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };*/int a[] = { 30,60,90 };AVLTree<int, int> t;for (auto e : a){t.Insert(make_pair(e, e));}t.InOrder();cout << t.IsAVLTree() << endl;
}
void TestAVLTree2()
{srand(time(0));const size_t N = 100000;AVLTree<int, int> t;for (size_t i = 0; i < N; ++i){size_t x = rand();t.Insert(make_pair(x, x));/*cout << t.IsAVLTree() << endl;*/}cout << t.IsAVLTree() << endl;
}
這里我們分別進行簡單?TestAVLTree1()和用生成隨機數字生成的數字進行測試TestAVLTree2()
如果成功就會打印1.