Cây AVL là một loại cây tìm kiếm nhị phân cân bằng. Do cách giới thiệu về AVL trong nhiều giáo trình thuật toán thường rất dài dòng, khiến nhiều người có ấn tượng rằng cây AVL phức tạp và không thực dụng. Tuy nhiên, trên thực tế, nguyên lý của cây AVL rất đơn giản và việc cài đặt cũng không hề phức tạp.
Tính chất
Cây nhị phân rỗng là một cây AVL.
Nếu T là một cây AVL, thì các cây con bên trái và bên phải của nó cũng là cây AVL, và \(|h(ls) - h(rs)| \leq 1\), trong đó \(h\) là chiều cao của các cây con.
Chiều cao của cây là \(O(\log n)\).
Hệ số cân bằng: Chiều cao cây con bên phải - Chiều cao cây con bên trái.
Chứng minh chiều cao cây
Gọi \(f_n\) là số nút tối thiểu mà một cây AVL có chiều cao \(n\) chứa, ta có:
Theo cách giải phương trình sai phân tuyến tính không thuần nhất với hệ số hằng, \(\{f_n+1\}\) là một dãy Fibonacci. Công thức tổng quát của \(f_n\) là:
Do đó chiều cao của cây AVL là \(O(\log f_n)\), với \(f_n\) là số lượng nút.
Quá trình
Chèn nút
Tương tự như trong BST (Cây tìm kiếm nhị phân), trước tiên thực hiện một lần tìm kiếm thất bại để xác định vị trí chèn. Sau khi chèn nút, dựa vào hệ số cân bằng để quyết định có cần điều chỉnh hay không.
Xóa nút
Xóa nút tương tự như BST, đổi chỗ nút cần xóa với nút kế nhiệm (successor) rồi tiến hành xóa.
Việc xóa có thể dẫn đến thay đổi chiều cao cây và hệ số cân bằng, lúc này cần điều chỉnh dọc theo con đường từ nút bị xóa lên đến gốc.
Duy trì sự cân bằng
Sau khi chèn hoặc xóa nút, tính chất 2 của cây AVL có thể bị phá vỡ. Do đó, cần duy trì cây dọc theo con đường từ nút vừa chèn/xóa đến gốc. Nếu tại một nút nào đó tính chất 2 không còn được thỏa mãn, vì chúng ta chỉ chèn/xóa một nút nên ảnh hưởng đến chiều cao cây không quá 1, do đó giá trị tuyệt đối của hệ số cân bằng tại nút đó tối đa là 2. Do tính đối xứng, ở đây chúng ta chỉ thảo luận trường hợp chiều cao cây con bên trái lớn hơn cây con bên phải là 2, tức là trong hình dưới đây \(h(B)-h(E)=2\). Lúc này, cần thảo luận hai trường hợp dựa trên mối quan hệ giữa \(h(A)\) và \(h(C)\). Cần lưu ý rằng, vì chúng ta duy trì sự cân bằng từ dưới lên trên, nên đối với tất cả các hậu duệ của nút D, tính chất 2 vẫn được thỏa mãn.
Trường hợp 1: Chiều cao nút A không nhỏ hơn chiều cao nút C
Trong đó \(h(C)\geq x\) là vì nút B thỏa mãn tính chất 2, nên chênh lệch giữa \(h(C)\) và \(h(A)\) không quá 1. Lúc này chúng ta thực hiện một thao tác xoay phải tại nút D (thao tác xoay giống như các loại cây tìm kiếm nhị phân cân bằng khác), như hình dưới đây.
Rõ ràng chiều cao các nút A, C, E không thay đổi, và ta có:
Lúc này trước tiên thực hiện một thao tác xoay trái tại nút B, sau đó thực hiện một thao tác xoay phải tại nút D, như hình dưới đây.
Rõ ràng chiều cao các nút A, E không thay đổi, và con bên phải mới của B cùng con bên trái mới của D lần lượt là con trái và con phải ban đầu của C, ta có:
/** * @brief An AVLTree-based map implementation * @details The map is sorted according to the natural ordering of its * keys or by a {@code Compare} function provided; This implementation * provides guaranteed log(n) time cost for the contains, get, insert * and remove operations. */#ifndef AVLTREE_MAP_HPP#define AVLTREE_MAP_HPP#include<cassert>#include<cstddef>#include<cstdint>#include<functional>#include<memory>#include<stack>#include<utility>#include<vector>/** * An AVLTree-based map implementation * https://en.wikipedia.org/wiki/AVL_tree * @tparam Key the type of keys maintained by this map * @tparam Value the type of mapped values * @tparam Compare */template<typenameKey,typenameValue,typenameCompare=std::less<Key>>classAvlTreeMap{private:usingUSize=size_t;usingFactor=int64_t;Comparecompare=Compare();public:structEntry{Keykey;Valuevalue;booloperator==(constEntry&rhs)constnoexcept{returnthis->key==rhs.key&&this->value==rhs.value;}booloperator!=(constEntry&rhs)constnoexcept{returnthis->key!=rhs.key||this->value!=rhs.value;}};private:structNode{usingPtr=std::shared_ptr<Node>;usingProvider=conststd::function<Ptr(void)>&;usingConsumer=conststd::function<void(constPtr&)>&;Keykey;Valuevalue{};Ptrleft=nullptr;Ptrright=nullptr;USizeheight=1;explicitNode(Keyk):key(std::move(k)){}explicitNode(Keyk,Valuev):key(std::move(k)),value(std::move(v)){}~Node()=default;inlineboolisLeaf()constnoexcept{returnthis->left==nullptr&&this->right==nullptr;}inlinevoidupdateHeight()noexcept{if(this->isLeaf()){this->height=1;}elseif(this->left==nullptr){this->height=this->right->height+1;}elseif(this->right==nullptr){this->height=this->left->height+1;}else{this->height=std::max(left->height,right->height)+1;}}inlineFactorfactor()constnoexcept{if(this->isLeaf()){return0;}elseif(this->left==nullptr){return(Factor)this->right->height;}elseif(this->right==nullptr){return(Factor)-this->left->height;}else{return(Factor)(this->right->height-this->left->height);}}inlineEntryentry()const{returnEntry{key,value};}staticPtrfrom(constKey&k){returnstd::make_shared<Node>(Node(k));}staticPtrfrom(constKey&k,constValue&v){returnstd::make_shared<Node>(Node(k,v));}};usingNodePtr=typenameNode::Ptr;usingConstNodePtr=constNodePtr&;usingNodeProvider=typenameNode::Provider;usingNodeConsumer=typenameNode::Consumer;NodePtrroot=nullptr;USizecount=0;usingK=constKey&;usingV=constValue&;public:usingEntryList=std::vector<Entry>;usingKeyValueConsumer=conststd::function<void(K,V)>&;usingMutKeyValueConsumer=conststd::function<void(K,Value&)>&;usingKeyValueFilter=conststd::function<bool(K,V)>&;classNoSuchMappingException:protectedstd::exception{private:constchar*message;public:explicitNoSuchMappingException(constchar*msg):message(msg){}constchar*what()constnoexceptoverride{returnmessage;}};AvlTreeMap()noexcept=default;/** * Returns the number of entries in this map. * @return size_t */inlineUSizesize()constnoexcept{returnthis->count;}/** * Returns true if this collection contains no elements. * @return bool */inlineboolempty()constnoexcept{returnthis->count==0;}/** * Removes all of the elements from this map. */voidclear()noexcept{this->root=nullptr;this->count=0;}/** * Returns the value to which the specified key is mapped; If this map * contains no mapping for the key, a {@code NoSuchMappingException} will * be thrown. * @param key * @return AvlTreeMap<Key, Value>::Value * @throws NoSuchMappingException */Valueget(Kkey)const{if(this->root==nullptr){throwNoSuchMappingException("Invalid key");}else{NodePtrnode=this->getNode(this->root,key);if(node!=nullptr){returnnode->value;}else{throwNoSuchMappingException("Invalid key");}}}/** * Returns the value to which the specified key is mapped; If this map * contains no mapping for the key, a new mapping with a default value * will be inserted. * @param key * @return AvlTreeMap<Key, Value>::Value & */Value&getOrDefault(Kkey){if(this->root==nullptr){this->root=Node::from(key);this->count+=1;returnthis->root->value;}else{returnthis->getNodeOrProvide(this->root,key,[&key](){returnNode::from(key);})->value;}}/** * Returns true if this map contains a mapping for the specified key. * @param key * @return bool */boolcontains(Kkey)const{returnthis->getNode(this->root,key)!=nullptr;}/** * Associates the specified value with the specified key in this map. * @param key * @param value */voidinsert(Kkey,Vvalue){if(this->root==nullptr){this->root=Node::from(key,value);this->count+=1;}else{this->insert(this->root,key,value);}}/** * If the specified key is not already associated with a value, associates * it with the given value and returns true, else returns false. * @param key * @param value * @return bool */boolinsertIfAbsent(Kkey,Vvalue){USizesizeBeforeInsertion=this->size();if(this->root==nullptr){this->root=Node::from(key,value);this->count+=1;}else{this->insert(this->root,key,value,false);}returnthis->size()>sizeBeforeInsertion;}/** * If the specified key is not already associated with a value, associates * it with the given value and returns the value, else returns the associated * value. * @param key * @param value * @return */Value&getOrInsert(Kkey,Vvalue){if(this->root==nullptr){this->root=Node::from(key,value);this->count+=1;returnroot->value;}else{NodePtrnode=getNodeOrProvide(this->root,key,[&](){returnNode::from(key,value);});returnnode->value;}}Valueoperator[](Kkey)const{returnthis->get(key);}Value&operator[](Kkey){returnthis->getOrDefault(key);}/** * Removes the mapping for a key from this map if it is present; * Returns true if the mapping is present else returns false * @param key the key of the mapping * @return bool */boolremove(Kkey){if(this->root==nullptr){returnfalse;}else{returnthis->remove(this->root,key,[](ConstNodePtr){});}}/** * Removes the mapping for a key from this map if it is present and returns * the value which is mapped to the key; If this map contains no mapping for * the key, a {@code NoSuchMappingException} will be thrown. * @param key * @return AvlTreeMap<Key, Value>::Value * @throws NoSuchMappingException */ValuegetAndRemove(Kkey){Valueresult;NodeConsumeraction=[&](ConstNodePtrnode){result=node->value;};if(root==nullptr){throwNoSuchMappingException("Invalid key");}else{if(remove(this->root,key,action)){returnresult;}else{throwNoSuchMappingException("Invalid key");}}}/** * Gets the entry corresponding to the specified key; if no such entry * exists, returns the entry for the least key greater than the specified * key; if no such entry exists (i.e., the greatest key in the Tree is less * than the specified key), a {@code NoSuchMappingException} will be thrown. * @param key * @return AvlTreeMap<Key, Value>::Entry * @throws NoSuchMappingException */EntrygetCeilingEntry(Kkey)const{if(this->root==nullptr){throwNoSuchMappingException("No ceiling entry in this map");}NodePtrnode=this->root;std::stack<NodePtr>ancestors;while(node!=nullptr){if(key==node->key){returnnode->entry();}if(compare(key,node->key)){/* key < node->key */if(node->left!=nullptr){ancestors.push(node);node=node->left;}else{returnnode->entry();}}else{/* key > node->key */if(node->right!=nullptr){ancestors.push(node);node=node->right;}else{if(ancestors.empty()){throwNoSuchMappingException("No ceiling entry in this map");}NodePtrparent=ancestors.top();ancestors.pop();while(node==parent->right){node=parent;if(!ancestors.empty()){parent=ancestors.top();ancestors.pop();}else{throwNoSuchMappingException("No ceiling entry in this map");}}returnparent->entry();}}}throwNoSuchMappingException("No ceiling entry in this map");}/** * Gets the entry corresponding to the specified key; if no such entry exists, * returns the entry for the greatest key less than the specified key; * if no such entry exists, a {@code NoSuchMappingException} will be thrown. * @param key * @return AvlTreeMap<Key, Value>::Entry * @throws NoSuchMappingException */EntrygetFloorEntry(Kkey)const{if(this->root==nullptr){throwNoSuchMappingException("No floor entry exists in this map");}NodePtrnode=this->root;std::stack<NodePtr>ancestors;while(node!=nullptr){if(key==node->key){returnnode->entry();}if(compare(key,node->key)){/* key < node->key */if(node->left!=nullptr){ancestors.push(node);node=node->left;}else{if(ancestors.empty()){throwNoSuchMappingException("No floor entry exists in this map");}NodePtrparent=ancestors.top();ancestors.pop();while(node==parent->left){node=parent;if(!ancestors.empty()){parent=ancestors.top();ancestors.pop();}else{throwNoSuchMappingException("No floor entry exists in this map");}}returnparent->entry();}}else{/* key > node->key */if(node->right!=nullptr){ancestors.push(node);node=node->right;}else{returnnode->entry();}}}throwNoSuchMappingException("No floor entry exists in this map");}/** * Gets the entry for the least key greater than the specified * key; if no such entry exists, returns the entry for the least * key greater than the specified key; if no such entry exists, * a {@code NoSuchMappingException} will be thrown. * @param key * @return AvlTreeMap<Key, Value>::Entry * @throws NoSuchMappingException */EntrygetHigherEntry(Kkey){if(this->root==nullptr){throwNoSuchMappingException("No higher entry exists in this map");}NodePtrnode=this->root;std::stack<NodePtr>ancestors;while(node!=nullptr){if(compare(key,node->key)){/* key < node->key */if(node->left!=nullptr){ancestors.push(node);node=node->left;}else{returnnode->entry();}}else{/* key >= node->key */if(node->right!=nullptr){ancestors.push(node);node=node->right;}else{if(ancestors.empty()){throwNoSuchMappingException("No higher entry exists in this map");}NodePtrparent=ancestors.top();ancestors.pop();while(node==parent->right){node=parent;if(!ancestors.empty()){parent=ancestors.top();ancestors.pop();}else{throwNoSuchMappingException("No higher entry exists in this map");}}returnparent->entry();}}}throwNoSuchMappingException("No higher entry exists in this map");}/** * Returns the entry for the greatest key less than the specified key; if * no such entry exists (i.e., the least key in the Tree is greater than * the specified key), a {@code NoSuchMappingException} will be thrown. * @param key * @return AvlTreeMap<Key, Value>::Entry * @throws NoSuchMappingException */EntrygetLowerEntry(Kkey)const{if(this->root==nullptr){throwNoSuchMappingException("No lower entry exists in this map");}NodePtrnode=this->root;std::stack<NodePtr>ancestors;while(node!=nullptr){if(compare(key,node->key)||key==node->key){/* key <= node->key */if(node->left!=nullptr){ancestors.push(node);node=node->left;}else{if(ancestors.empty()){throwNoSuchMappingException("No lower entry exists in this map");}NodePtrparent=ancestors.top();ancestors.pop();while(node==parent->left){node=parent;if(!ancestors.empty()){parent=ancestors.top();ancestors.pop();}else{throwNoSuchMappingException("No lower entry exists in this map");}}returnparent->entry();}}else{/* key > node->key */if(node->right!=nullptr){ancestors.push(node);node=node->right;}else{returnnode->entry();}}}throwNoSuchMappingException("No lower entry exists in this map");}/** * Remove all entries that satisfy the filter condition. * @param filter */voidremoveAll(KeyValueFilterfilter){std::vector<Key>keys;this->inorderTraversal([&](ConstNodePtrnode){if(filter(node->key,node->value)){keys.push_back(node->key);}});for(constKey&key:keys){this->remove(key);}}/** * Performs the given action for each key and value entry in this map. * The value is immutable for the action. * @param action */voidforEach(KeyValueConsumeraction)const{this->inorderTraversal([&](ConstNodePtrnode){action(node->key,node->value);});}/** * Performs the given action for each key and value entry in this map. * The value is mutable for the action. * @param action */voidforEachMut(MutKeyValueConsumeraction){this->inorderTraversal([&](ConstNodePtrnode){action(node->key,node->value);});}/** * Returns a list containing all of the entries in this map. * @return AvlTreeMap<Key, Value>::EntryList */EntryListtoEntryList()const{EntryListentryList;this->inorderTraversal([&](ConstNodePtrnode){entryList.push_back(node->entry());});returnentryList;}private:staticNodePtrrotateLeft(ConstNodePtrnode){// clang-format off// | |// N S// / \ l-rotate(N) / \ // L S ==========> N R// / \ / \ // M R L MNodePtrsuccessor=node->right;// clang-format onnode->right=successor->left;successor->left=node;node->updateHeight();successor->updateHeight();returnsuccessor;}staticNodePtrrotateRight(ConstNodePtrnode){// clang-format off// | |// N S// / \ r-rotate(N) / \ // S R ==========> L N// / \ / \ // L M M RNodePtrsuccessor=node->left;// clang-format onnode->left=successor->right;successor->right=node;node->updateHeight();successor->updateHeight();returnsuccessor;}staticvoidswapNode(NodePtr&lhs,NodePtr&rhs){std::swap(lhs->key,rhs->key);std::swap(lhs->value,rhs->value);std::swap(lhs,rhs);}staticvoidfixBalance(NodePtr&node){if(node->factor()<-1){if(node->left->factor()<0){// clang-format off// Left-Left Case// |// C |// / r-rotate(C) B// B ==========> / \ // / A C// A// clang-format onnode=rotateRight(node);}else{// clang-format off// Left-Right Case// | |// C C |// / l-rotate(A) / r-rotate(C) B// A ==========> B ==========> / \ // \ / A C// B A// clang-format onnode->left=rotateLeft(node->left);node=rotateRight(node);}}elseif(node->factor()>1){if(node->right->factor()>0){// clang-format off// Right-Right Case// |// C |// \ l-rotate(C) B// B ==========> / \ // \ A C// A// clang-format onnode=rotateLeft(node);}else{// clang-format off// Right-Left Case// | |// A A |// \ r-rotate(C) \ l-rotate(A) B// C ==========> B ==========> / \ // / \ A C// B C// clang-format onnode->right=rotateRight(node->right);node=rotateLeft(node);}}}NodePtrgetNodeOrProvide(NodePtr&node,Kkey,NodeProviderprovide){assert(node!=nullptr);if(key==node->key){returnnode;}assert(key!=node->key);NodePtrresult;if(compare(key,node->key)){/* key < node->key */if(node->left==nullptr){result=node->left=provide();this->count+=1;node->updateHeight();}else{result=getNodeOrProvide(node->left,key,provide);node->updateHeight();fixBalance(node);}}else{/* key > node->key */if(node->right==nullptr){result=node->right=provide();this->count+=1;node->updateHeight();}else{result=getNodeOrProvide(node->right,key,provide);node->updateHeight();fixBalance(node);}}returnresult;}NodePtrgetNode(ConstNodePtrnode,Kkey)const{assert(node!=nullptr);if(key==node->key){returnnode;}if(compare(key,node->key)){/* key < node->key */returnnode->left==nullptr?nullptr:getNode(node->left,key);}else{/* key > node->key */returnnode->right==nullptr?nullptr:getNode(node->right,key);}}voidinsert(NodePtr&node,Kkey,Vvalue,boolreplace=true){assert(node!=nullptr);if(key==node->key){if(replace){node->value=value;}return;}assert(key!=node->key);if(compare(key,node->key)){/* key < node->key */if(node->left==nullptr){node->left=Node::from(key,value);this->count+=1;node->updateHeight();}else{insert(node->left,key,value,replace);node->updateHeight();fixBalance(node);}}else{/* key > node->key */if(node->right==nullptr){node->right=Node::from(key,value);this->count+=1;node->updateHeight();}else{insert(node->right,key,value,replace);node->updateHeight();fixBalance(node);}}}boolremove(NodePtr&node,Kkey,NodeConsumeraction){assert(node!=nullptr);if(key!=node->key){if(compare(key,node->key)){/* key < node->key */NodePtr&left=node->left;if(left!=nullptr&&remove(left,key,action)){node->updateHeight();fixBalance(node);returntrue;}else{returnfalse;}}else{/* key > node->key */NodePtr&right=node->right;if(right!=nullptr&&remove(right,key,action)){node->updateHeight();fixBalance(node);returntrue;}else{returnfalse;}}}assert(key==node->key);action(node);if(node->isLeaf()){// Case 1: no childnode=nullptr;}elseif(node->right==nullptr){// clang-format off// Case 2: left child only// P// | remove(N) P// N ========> |// / L// L// clang-format onnode=node->left;node->updateHeight();}elseif(node->left==nullptr){// clang-format off// Case 3: right child only// P// | remove(N) P// N ========> |// \ R// R// clang-format onnode=node->right;node->updateHeight();}elseif(node->right->left==nullptr){// clang-format off// Case 4: both left and right child, right child has no left child// | |// N remove(N) R// / \ ========> /// L R L// clang-format onNodePtrright=node->right;swapNode(node,right);right->right=node->right;node=right;node->updateHeight();fixBalance(node);}else{// clang-format off// Case 5: both left and right child, right child is not a leaf// Step 1. find the node N with the smallest key// and its parent P on the right subtree// Step 2. swap S and N// Step 3. remove node N like Case 1 or Case 3// Step 4. update height for P// | |// N S |// / \ / \ S// L .. swap(N, S) L .. remove(N) / \ // | =========> | ========> L ..// P P |// / \ / \ P// S .. N .. / \ // \ \ R ..// R R// clang-format on// Step 1NodePtrsuccessor=node->right;NodePtrparent=node;while(successor->left!=nullptr){parent=successor;successor=parent->left;}// Step 2swapNode(node,successor);// Step 3parent->left=node->right;// Restore nodenode=successor;// Step 4parent->updateHeight();}this->count-=1;returntrue;}voidinorderTraversal(NodeConsumeraction)const{if(this->root==nullptr){return;}std::stack<NodePtr>stack;NodePtrnode=this->root;while(node!=nullptr||!stack.empty()){while(node!=nullptr){stack.push(node);node=node->left;}if(!stack.empty()){node=stack.top();stack.pop();action(node);node=node->right;}}}};#endif // AVLTREE_MAP_HPP
Last updated on this page:, Update history Found an error? Want to help improve? Edit this page on GitHub! Contributors to this page:OI-wiki All content on this page is provided under the terms of the CC BY-SA 4.0 and SATA license, additional terms may apply