文章目錄
前言
以前種過AVL樹,爲何要再寫呢?依舊是由於我忘了,重刷一遍唄。node
平衡二叉搜索樹(AVL樹)
二叉搜索樹必定程度上能夠提升搜索效率,可是當原序列有序,例如序列A = {1,2,3,4,5,6},構造二叉搜索樹如圖。依據此序列構造的二叉搜索樹爲右斜樹,同時二叉樹退化成單鏈表,搜索效率下降爲O(n)。ios
以下圖:
web
在此二叉搜索樹中查找元素6須要查找6次。二叉搜索樹的查找效率取決於樹的高度,所以保持樹的高度最小,便可保證樹的查找效率。一樣的序列A,改成下圖方式存儲,查找元素6時只需比較3次,查找效率提高一倍。數據結構
能夠看出當節點數目必定,保持樹的左右兩端保持平衡,樹的查找效率最高。這種左右子樹的高度相差不超過1的樹爲平衡二叉樹。svg
AVL樹的節點數據結構
和上面使用的那個普通結構略有不一樣。函數
class TreeNode{ public: //這幾個數據放作公有的,方便操做 int depth; //深度,這裏計算每一個結點的深度,經過深度的比較可得出是否平衡 TreeNode* parent; //該結點的父節點,方便操做 int val; TreeNode* left; TreeNode* right; TreeNode(int x) : val(x), depth(0), left(NULL), right(NULL) { } TreeNode() : val(0), depth(0), left(NULL), right(NULL) { } };
在原始數據上建立AVL樹
個人代碼嘗試:
(先對原始數據進行排序,而後再填充二叉搜索樹,使用遞歸的方式。)url
#include<iostream> #include<vector> using namespace std; void createTree(vector<int>& vec, TreeNode* root, int begin, int end) { //若是隻剩一個鍵 if (begin == end) { root->val = vec[begin]; return; } int mid_sz = (begin+end)/2; root->val = vec[mid_sz]; if (mid_sz - 1 >= begin) { root->left = new TreeNode(0); createTree(vec, root->left, begin, mid_sz - 1); } root->right = new TreeNode(0); createTree(vec, root->right,mid_sz + 1,end); } void PreOrderTraverse(TreeNode* root) { if (NULL == root) return; cout << root->val; PreOrderTraverse(root->left); PreOrderTraverse(root->right); } int main() { TreeNode* roott = new TreeNode(0); vector<int> vec = { 0,1,2,3,4,5,6,7}; createTree(vec,roott,0,vec.size()-1); PreOrderTraverse(roott); }
調整樹的節點使平衡的操做:旋轉
LL (右旋):在左葉的左側插入數據
圖解過程:
spa
代碼實現:
//在左葉的左側插入數據 TreeNode* LL(TreeNode* root) { TreeNode* x = root->left; //即將返回的節點是y的左子節點(就是那個B) TreeNode* temp = x->right; //先把y的右子節點取出來(就是那個E) x->right = root; //把y放進x的右子節點(把A放到B的右節點) root->left = temp; //把前面預存的放到y的左子節點(把E放到A的右節點) return x; //(返回那個B) } int main() { TreeNode* roott = new TreeNode(0); vector<int> vec = { 0,1,2,3,4,5,6,7}; createTree(vec,roott,0,vec.size()-1); roott = LL(roott); PreOrderTraverse(roott); }
RR(左旋):在右子葉的右側插入數據
圖解過程:
.net
右旋其實就是上面左旋的鏡像過程,因此不難,直接仿寫上面左旋的過程便可:3d
代碼實現
TreeNode* RR(TreeNode* root) { TreeNode* x = root->right; //即將返回的節點是y的右子節點 TreeNode* temp = x->left; //先把x的左子節點取出來 x->left = root; //把y放進x的左子節點 root->right = temp; //把前面預存的放到y的右子節點 return x; } int main() { TreeNode* roott = new TreeNode(0); vector<int> vec = { 0,1,2,3,4,5,6,7}; createTree(vec,roott,0,vec.size()-1); roott = RR(roott); PreOrderTraverse(roott); }
後面的部分,就比較抽象了。
LR(左右旋):在左葉節點的右側插入數據
咱們將這種狀況抽象出來,獲得下圖:
咱們須要對節點y進行平衡的維護。步驟以下圖所示(第三個圖裏面x和z的位置換一下。):
代碼實現
TreeNode* LR(TreeNode* root) { root->left = RR(root->left); root = LL(root); return root; } //簡單明瞭啊
RL(右左旋):在右葉節點的左側插入數據
咱們將這種狀況抽象出來,獲得下圖:
咱們須要對節點y進行平衡的維護。步驟以下圖所示(第三個圖裏面x和z的位置換一下。):
第二個圖中y的左孩子爲T1
(被水印遮住的部分爲:T1,T2,T3,T4)
代碼實現
TreeNode* RL(TreeNode* root) { root->right = LL(root->right); root = RR(root); return root; } //簡單明瞭啊
新節點的插入
在這裏須要先補兩個函數,雖然可能如今看不懂,可是配上調用函數的上下文就懂了。
計算平衡因子
int getBalanceFactor(TreeNode* node){ if(node==NULL){ return 0; } return get_depth(node->left)-getHeight(node->right); }
int get_depth(TreeNode* node){ if(node==NULL){ return 0; } return node->depth; }
對getBalanceFactor
函數返回值的分析:
-
若是剛插入的葉子節點的爺爺節點的返回值大於0
- 若是剛插入的葉子節點的父節點的返回值大於0:(LL)
- 若是剛插入的葉子節點的父節點的返回值小於0:(LR)
-
若是剛插入的葉子節點的爺爺節點的返回值小於0
- 若是剛插入的葉子節點的父節點的返回值大於0:(RL)
- 若是剛插入的葉子節點的父節點的返回值小於0:(RR)
正式插入新節點
TreeNode* Insert_Node(TreeNode* root, int val) { //先將節點插入 if (NULL == root) return new TreeNode(val); else { if (val < root->val) root->left = Insert_Node(root->left, val); else root->right = Insert_Node(root->right, val); } //計算平衡因子 int balanceFactor = getBalanceFactor(root); //判斷是否該旋轉,該如何旋轉 if (balanceFactor > 1) { //左子樹有事兒 balanceFactor = getBalanceFactor(root->left); if (balanceFactor == 1) //插左邊了 return LL(root); else if (balanceFactor == -1) //插右邊了 return RR(root); else { cout << "罕見故障" << endl; } } else if (balanceFactor < -1) { //右子樹有事兒 balanceFactor = getBalanceFactor(root->right); if (balanceFactor == 1) //插左邊了 return RL(root); else if(balanceFactor == -1) //插右邊了 return RR(root); else { cout << "罕見故障" << endl; } } return root; } int main() { TreeNode* roott = new TreeNode(0); vector<int> vec = { 0,1,2,3,4,5,6,7}; createTree(vec,roott,0,vec.size()-1); roott = Insert_Node(roott,8); PreOrderTraverse(roott); }
現有節點刪除
代碼裏的註釋把整個過程寫的已經很詳盡了。
//刪除節點 TreeNode* DelSerchNode(TreeNode* node, int e) { if (node == NULL) return NULL; TreeNode* retNode; if (e < node->val) { node->left = DelSerchNode(node->left, e); retNode = node; } else if (e > node->val) { node->right = DelSerchNode(node->right, e); retNode = node; } else { // 待刪除節點左子樹爲空的狀況 if (node->left == NULL) { TreeNode* rightNode = node->right; node->right = NULL; retNode = rightNode; } // 待刪除節點右子樹爲空的狀況 else if (node->right == NULL) { TreeNode* leftNode = node->left; node->left = NULL; retNode = leftNode; } else { // 待刪除節點左右子樹均不爲空的狀況 // 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點 // 用這個節點頂替待刪除節點的位置 TreeNode* temp = node; while (NULL != temp->left) { temp = temp->left; } node->val = temp->val; node->left = NULL; //temp = NULL; //這還刪不掉了。。。。這指針還真是頑強 delete temp; retNode = node; } } if (retNode == NULL) return NULL; //計算平衡因子 int balanceFactor = getBalanceFactor(retNode); //判斷是否該旋轉,該如何旋轉 if (balanceFactor > 1) { //左子樹有事兒 balanceFactor = getBalanceFactor(retNode->left); if (balanceFactor == 1) //插左邊了 return LL(retNode); else if (balanceFactor == -1) //插右邊了 return RR(retNode); else { cout << "罕見故障" << endl; } } else if (balanceFactor < -1) { //右子樹有事兒 balanceFactor = getBalanceFactor(retNode->right); if (balanceFactor == 1) //插左邊了 return RL(retNode); else if (balanceFactor == -1) //插右邊了 return RR(retNode); else { cout << "罕見故障" << endl; } } return retNode; } int main() { TreeNode* roott = new TreeNode(0); vector<int> vec = { 0,1,2,3,4,5,6,7}; createTree(vec,roott,0,vec.size()-1); roott = DelSerchNode(roott,5); PreOrderTraverse(roott);
先到這裏吧。
本文同步分享在 博客「看,將來」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。