日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

高级数据结构与算法 | AVL树 (高度平衡树)

發布時間:2024/4/11 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 高级数据结构与算法 | AVL树 (高度平衡树) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • AVL樹
    • 實現思路
      • 數據結構
      • 查找
        • 平衡因子
      • 旋轉
        • 右旋
        • 左旋
        • 右左雙旋
        • 左右雙旋
      • 插入
      • 刪除
      • AVL樹的驗證
        • 中序遍歷
        • 平衡判斷
      • AVL樹的性能
    • 完整代碼實現


AVL樹

AVL樹是最先發明的自平衡二叉查找樹。在AVL樹中任何節點的兩個子樹的高度最大差別為1,所以它也被稱為高度平衡樹。增加和刪除可能需要通過一次或多次樹旋轉來重新平衡這個樹。AVL樹得名于它的發明者G.
M. Adelson-Velsky和E. M. Landis

AVL樹其實就是在二叉搜索樹的基礎上,引入了平衡因子的概念,通過旋轉來調整平衡因子,使得二叉樹始終平衡,效率更高。

特點

  • 本身首先是一棵二叉搜索樹。
  • 帶有平衡條件:每個結點的左右子樹的高度之差的絕對值(平衡因子)最多為1。
  • 二叉搜索樹的實現的博客之前寫過,所以這里就直接對以前的代碼進行改造。
    數據結構 : 二叉搜索樹的原理以及實現(C++)


    實現思路

    數據結構

    這里不僅需要左右子樹,因為涉及到了大量的平衡因子調節,所以還需要保存父節點的指針,要用到三叉鏈的結構。

    struct AVLTreeNode{AVLTreeNode(const std::pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;std::pair<K, V> _kv; //鍵值對int _bf; //平衡因子};

    查找

    AVL樹本質還是二叉搜索樹,所以查找部分可以直接復用,不需要修改。

    直接從根節點出發,比根節點大則查找右子樹,比根節點小則查找左子樹,相同則返回。如果遍歷完還沒找到,則說明不存在此樹中,返回nullptr

    Node* Find(const K& key) {//根據二叉搜索樹的性質,從根節點出發,比根節點大則查找右子樹,比根節點小則查找左子樹Node* cur = _root;while (cur){//比根節點大則查找右子樹if (key > cur->_kv.first){cur = cur->_right;}//比根節點小則查找左子樹else if (key < cur->_kv.first){cur = cur->_left;}//相同則返回else{return cur;}}//遍歷完則說明查找不到,返回falsereturn nullptr; }

    要講插入和刪除之前,就必須得講他們兩個的核心步驟,旋轉和平衡因子的調節。AVL樹正是通過這兩個步驟來實現其高度平衡的特性。

    平衡因子

    平衡因子,其實就是左右子樹的高度差。AVL樹通過控制高度差不超過2,來實現平衡。

    通常認為在右邊插入節點時,平衡因子+1,左邊插入時平衡因子減一。
    例如:

    右子樹插入一個90,根節點平衡因子+1

    當某節點平衡因子為0時,說明他的左右子樹平衡
    當平衡因子為1或者-1時,說明左右子樹存在高度差,其父節點可能存在不平衡,需要向上繼續判斷。
    當平衡因子為2或者-2時,說明此時不平衡,需要旋轉處理。

    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){//如果右邊不平衡,并且子節點也是右邊偏重,則左單旋if (cur->_bf == 1){RotateL(parent);}//如果右邊不平衡,而子節點是左邊偏重,此時需要先轉換為上面的狀態,先右單旋再左單旋。但是不能直接右單旋再左單旋,還需要根據情況處理平衡因子else{RotateRL(parent);}}else{//左邊不平衡,并且子節點也是左邊偏重,右單旋if (cur->_bf == -1){RotateR(parent);}//同上,左右雙旋else{RotateLR(parent);}}//旋轉完后恢復平衡,更新結束。break; }

    旋轉

    旋轉分為四種情景,簡單點總結的話就是如果節點呈直線則單旋,折線則雙旋,下面一一分析。

    首先討論直線狀態的單旋。

    直線狀態,也就是某一邊不平衡,并且其那一邊的子節點也是朝那一邊偏重。
    例如這個圖,左邊不平衡,子節點30也是左邊偏重,則單旋。

    右旋

    例如這種情況,當左邊不平衡,并且節點呈直線時(左節點的左邊偏重),說明需要右旋處理。

    此時節點60的平衡因子為-2,說明此時60的位置不平衡,需要旋轉,由于是左邊偏重,則需要將60向右旋轉來恢復平衡。

    這就是最簡單的右旋。但是通常情況下,這些節點還有各自的子樹。

    還是按照上面的旋轉方法,但是要在保持原有結構的情況下稍微處理。

    對于節點60,如果要將其右旋,就需要讓他變成30的右節點,但是30的右節點本來就有數據,所以此時就需要將30的右節點40放到別的位置。那么應該放到哪個位置呢?很簡單,因為我們需要將60右旋,所以原本是他左節點的30變成了他的父節點,而他左節點的位置空了出來,所以就可以將40放到60的左邊。這時再將60整個變為30的右節點即可。并且因為此時原本的父節點60變為了30的子節點,所以還要處理其與祖父節點的關系。

    旋轉結束后,調整30和60的平衡因子為0

    總結一下:
    右旋主要分為3個步驟

  • 讓不平衡的結點parent的左子樹變為其原本左子樹subL的右節點subLR
  • 讓parent變為subL的右子樹
  • 調整新的父節點subL與祖父節點的關系,并調整旋轉后的平衡因子
  • //右旋 void RotateR(Node* parent) {Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;//如果subLR存在,則讓他的父節點指向parent。if (subLR){subLR->_parent = parent;}subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;//兩種情況//如果parent為根節點,則讓subL成為新的根節點if (parent == _root){_root = subL;subL->_parent = nullptr;}//如果不是根節點,則改變subL與其祖父節點的指向關系else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}//左旋完成后平衡,平衡因子歸零。subL->_bf = parent->_bf = 0; }

    左旋

    左旋的思路和右旋一樣,只是將順序翻了過來。
    例如這種情況,當右邊不平衡,并且節點呈直線時(右節點的右邊偏重),說明需要左旋處理。


    這就是最簡單的左旋。

    下面看看復雜的左旋

    還是同樣的思路。只是這次把左右調換
    左旋的步驟:

  • 讓不平衡的結點parent的右子樹變為其原本右子樹subR的右節點subRL
  • 讓parent變為subL的左子樹
  • 調整新的父節點subL與祖父節點的關系,并調整旋轉后的平衡因子
  • //左旋 void RotateL(Node* parent) {Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL){subRL->_parent = parent;}subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}subR->_bf = parent->_bf = 0; }

    下面討論雙旋的情景,雙旋發生在折線狀態時。
    例如

    當某邊不平衡,并且對于方向的子樹,其反方向偏重時,需要雙旋。
    例如上圖,左邊不平衡,而左子樹則是右邊偏重。此時呈折線狀態。

    右左雙旋


    繼續剛剛那個圖,可以看到,他呈折線狀態,與前面單旋的狀態都不同。所以此時我們可以換個思路,可以先對其進行一次單旋,將其轉換成之前的直線狀態,再進行一次單旋即可平衡。

    因為其右子樹是左邊偏重,所以對其右子樹先進行一次右旋。

    可以看到,此時就恢復成了原本直線的狀態,此時因為30節點右邊不平衡,所以再進行一次左旋即可完成。

    但是這里并不能直接進行一次右單旋然后左單旋,這里有兩種情況,如果40插入在了60的左子樹,則是上面那種情況,調整完后subR平衡因子為1,parent和subRL為0。

    但是如果將70插入在了60的右子樹,則又是另一種情況。
    下面畫圖

    如果40插入在了60的右子樹,則調整完后parent平衡因子為-1,subR和subRL為0。
    總結一下:
    右左雙旋的步驟如下

  • 首先因為不平衡那個方向的子樹的反方向偏重,呈折現狀態,所以需要對其右旋轉,讓樹恢復到直線狀態
  • 直線狀態時就和一開始的單旋思路一樣,按照單旋處理
  • 調節平衡因子,根據subRL一開始的平衡因子進行調節,有兩種情況,為-1時subR結束后為1,為1時parent結束后為-1。
  • //右左雙旋 void RotateRL(Node* parent) {Node* subR = parent->_right;Node* subRL = subR->_left;//這里需要保存subRL的平衡因子,來調節旋轉完后的平衡因子int bf = subRL->_bf;//先右單旋將折線結構轉換為直線結構,也就是前面單旋就可以解決的問題。RotateR(subR);//然后再左單旋即可RotateL(parent);//根據subRL的bf來調節旋轉后的平衡因子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{parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;} }

    同上思路,就不多說了,直接反過來方向就行。

    左右雙旋

    //左右雙旋 void RotateLR(Node* parent) {Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(parent);if (bf == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else{parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;} }

    插入

    插入分為三個步驟

  • 按照二叉搜索樹的規則找到合適的位置插入
  • 更新插入后的平衡因子
  • 根據平衡因子來選擇是否進行旋轉調節。
  • 思路前面已經講了,這里直接復用代碼就行,具體步驟也寫在注釋里

    bool Insert(const std::pair<K, V>& kv) {//按照二叉搜索樹的規則先找到位置if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}//插入節點cur = new Node(kv);//判斷插入位置if (cur->_kv.first > parent->_kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//更新平衡因子while (parent){//更新父節點的平衡因子if (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}//判斷更新后父節點是否平衡//平衡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){//如果右邊不平衡,并且子節點也是右邊偏重,則左單旋if (cur->_bf == 1){RotateL(parent);}//如果右邊不平衡,而子節點是左邊偏重,此時需要先轉換為上面的狀態,先右單旋再左單旋。但是不能直接右單旋再左單旋,還需要根據情況處理平衡因子else{RotateRL(parent);}}else{//左邊不平衡,并且子節點也是左邊偏重,右單旋if (cur->_bf == -1){RotateR(parent);}//同上,左右雙旋else{RotateLR(parent);}}//旋轉完后恢復平衡,更新結束。break;}}return true; }

    刪除

    AVL樹的刪除極為復雜,數據結構,算法導論這些書里僅僅只是提及了思路而沒有實現。我自己實現了發現也大概要100多行,考慮的情況極為復雜。

    雖然代碼極為復雜,但是思路還是很簡單,分為以下幾步。

  • 按照二叉搜索樹的規則刪除
  • 更新平衡因子,并且進行旋轉來調整(最壞情況下可能會一直調整到根節點)。
  • 這里就直接復用上面平衡因子更新的代碼以及之前博客實現的二叉搜索樹的刪除(博客鏈接在開始處),將其合并處理即可。

    思路之前寫過,就不再單獨提出來說,直接寫在注釋里。

    bool erase(const K& key) {//刪除直接按照二叉搜索樹的規則刪除,然后再進行平衡因子的更新即可Node* cur = _root;Node* parent = cur;/*刪除有三種情況,一種是刪除葉子節點,可以直接刪除第二種情況,如果刪除的節點只有一個子樹,那么刪除這個節點后,就讓父節點指向他的這一子樹前兩種情況可以合并處理第三種情況則是左右子樹都不為空,此時選擇一個來節點來替換他后,再刪除,就可以不破壞原有結構如果要保持原有結構不變化,那么選擇的節點必須要和刪除節點在中序遍歷中是連續的,而滿足的只有兩個節點,一個是其左子樹的最大值,一個是其右子樹的最小值。*///刪除部分while (cur){//找到刪除的位置if (key > cur->_kv.first){parent = cur;cur = cur->_right;}else if (key < cur->_kv.first){parent = cur;cur = cur->_left;}else{//前兩種情況合并處理,如果當前結點只有一個子樹,則讓父節點指向他的子樹//處理只有右子樹時 if (cur->_left == nullptr){//如果當前節點為根節點,則讓右子樹成為新的根節點if (cur == _root){_root = cur->_left;}else{//判斷當前節點是他父節點的哪一個子樹if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}delete cur;}//處理只有左子樹時 else if (cur->_right == nullptr){//如果當前節點為根節點,則讓左子樹成為新的根節點if (cur == _root){_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}delete cur;}//處理左右子樹都不為空時,選取左子樹的最右節點或者右子樹的最左節點else{//這里我選取的是左子樹的最右節點Node* LeftMax = cur->_left;Node* LeftMaxParent = cur;//找到左子樹的最右節點while (LeftMax->_right){LeftMaxParent = LeftMax;LeftMax = LeftMax->_right;}//替換節點std::swap(cur->_kv, LeftMax->_kv);//判斷當前節點是他父節點的哪一個子樹, 因為已經是最右子樹了,所以這個節點的右子樹為空,但是左子樹可能還有數據,所以讓父節點指向他的左子樹//并且刪除最右節點if (LeftMax == LeftMaxParent->_left){LeftMaxParent->_left = LeftMax->_left;}else{LeftMaxParent->_right = LeftMax->_left;}delete LeftMax;}//刪除成功,中斷break;}}//查找不到if (cur == nullptr)return false;//更新平衡因子while (parent){//更新父節點的平衡因子,注意這里和插入是反過來的,因為是刪除if (cur == parent->_left){parent->_bf++;}else{parent->_bf--;}//判斷更新后父節點是否平衡//平衡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){//如果右邊不平衡,并且子節點也是右邊偏重,則左單旋if (cur->_bf == 1){RotateL(parent);}//如果右邊不平衡,而子節點是左邊偏重,此時需要先轉換為上面的狀態,先右單旋再左單旋。但是不能直接右單旋再左單旋,還需要根據情況處理平衡因子else{RotateRL(parent);}}else{//左邊不平衡,并且子節點也是左邊偏重,右單旋if (cur->_bf == -1){RotateR(parent);}//同上,左右雙旋else{RotateLR(parent);}}//旋轉完后恢復平衡,更新結束。break;}}return true; }

    AVL樹的驗證

    如要驗證AVL樹,有兩個方法。
    一是判斷其是否具有二叉搜索樹的特性,可以通過中序遍歷來看看是否有序來判斷。
    二是通過判斷他所有的子樹是否兩邊高度平衡,來判斷其是否具有平衡的特性。

    中序遍歷

    void _InOrderTravel(Node* root) const{if (root == nullptr)return;_InOrderTravel(root->_left);std::cout << root->_kv.first << ':' << root->_kv.second << std::endl;_InOrderTravel(root->_right);}void InOrderTravel() const{_InOrderTravel(_root);}

    測試代碼

    int main() {lee::AVLTree<int, string> tree;tree.Insert(make_pair(3, "php"));tree.Insert(make_pair(1, "c++"));tree.Insert(make_pair(2, "c#"));tree.Insert(make_pair(7, "go"));tree.Insert(make_pair(11, "js"));tree.Insert(make_pair(19, "lua"));tree.Insert(make_pair(5, "sql"));tree.Insert(make_pair(8, "java"));tree.Insert(make_pair(4, "python"));tree.InOrderTravel();return 0; }


    這一部分測試是沒有問題的。


    平衡判斷

    int countHeight(Node* root) const{if (root == nullptr)return 0;int leftHeight = countHeight(root->_left);int rightHeight = countHeight(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}bool _IsBalance(Node* root) const{if (root == nullptr)return true;int leftHeight = countHeight(root->_left);int rightHeight = countHeight(root->_right);return abs(leftHeight - rightHeight) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}bool IsBalance() const{return _IsBalance(_root);}

    測試代碼

    int main() {lee::AVLTree<int, string> tree;tree.Insert(make_pair(3, "php"));tree.Insert(make_pair(1, "c++"));tree.Insert(make_pair(2, "c#"));tree.Insert(make_pair(7, "go"));tree.Insert(make_pair(11, "js"));tree.Insert(make_pair(19, "lua"));tree.Insert(make_pair(5, "sql"));tree.Insert(make_pair(8, "java"));tree.Insert(make_pair(4, "python"));cout << tree.IsBalance();return 0; }


    可以看到,這個特性也滿足。說明這棵樹的實現沒有問題。


    AVL樹的性能

    AVL樹是一棵絕對平衡的二叉搜索樹,其要求每個節點的左右子樹高度差的絕對值都不超過1,這樣可以保證
    查詢時高效的時間復雜度,即 log2(N)。但是如果要對AVL樹做一些結構修改的操作,性能非常低下,比如:
    插入時要維護其絕對平衡,旋轉的次數比較多,更差的是在刪除時,有可能一直要讓旋轉持續到根的位置。
    因此:如果需要一種查詢高效且有序的數據結構,而且數據的個數為靜態的(即不會改變),可以考慮AVL樹,
    但一個結構經常修改,就不太適合。


    完整代碼實現

    #pragma once #include<iostream>namespace lee {template<class K, class V>struct AVLTreeNode{AVLTreeNode(const std::pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;std::pair<K, V> _kv;int _bf;};template<class K, class V>class AVLTree{public:typedef AVLTreeNode<K, V> Node;AVLTree() : _root(nullptr){}~AVLTree(){destory(_root);}//右旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;//如果subLR存在,則讓他的父節點指向parent。if (subLR){subLR->_parent = parent;}subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;//兩種情況//如果parent為根節點,則讓subL成為新的根節點if (parent == _root){_root = subL;subL->_parent = nullptr;}//如果不是根節點,則改變subL與其祖父節點的指向關系else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}//左旋完成后平衡,平衡因子歸零。subL->_bf = parent->_bf = 0;}//左旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL){subRL->_parent = parent;}subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}subR->_bf = parent->_bf = 0;}//右左雙旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;//這里需要保存subRL的平衡因子,來調節旋轉完后的平衡因子int bf = subRL->_bf;//先右單旋將折線結構轉換為直線結構,也就是前面單旋就可以解決的問題。RotateR(subR);//然后再左單旋即可RotateL(parent);//根據subRL的bf來調節旋轉后的平衡因子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{parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}}//左右雙旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(parent);if (bf == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else{parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}}bool Insert(const std::pair<K, V>& kv){//按照二叉搜索樹的規則先找到位置if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}//插入節點cur = new Node(kv);//判斷插入位置if (cur->_kv.first > parent->_kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//更新平衡因子while (parent){//更新父節點的平衡因子if (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}//判斷更新后父節點是否平衡//平衡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){//如果右邊不平衡,并且子節點也是右邊偏重,則左單旋if (cur->_bf == 1){RotateL(parent);}//如果右邊不平衡,而子節點是左邊偏重,此時需要先轉換為上面的狀態,先右單旋再左單旋。但是不能直接右單旋再左單旋,還需要根據情況處理平衡因子else{RotateRL(parent);}}else{//左邊不平衡,并且子節點也是左邊偏重,右單旋if (cur->_bf == -1){RotateR(parent);}//同上,左右雙旋else{RotateLR(parent);}}//旋轉完后恢復平衡,更新結束。break;}}return true;}Node* Find(const K& key){//根據二叉搜索樹的性質,從根節點出發,比根節點大則查找右子樹,比根節點小則查找左子樹Node* cur = _root;while (cur){//比根節點大則查找右子樹if (key > cur->_kv.first){cur = cur->_right;}//比根節點小則查找左子樹else if (key < cur->_kv.first){cur = cur->_left;}//相同則返回else{return cur;}}//遍歷完則說明查找不到,返回falsereturn nullptr;}bool erase(const K& key){//刪除直接按照二叉搜索樹的規則刪除,然后再進行平衡因子的更新即可Node* cur = _root;Node* parent = cur;/*刪除有三種情況,一種是刪除葉子節點,可以直接刪除第二種情況,如果刪除的節點只有一個子樹,那么刪除這個節點后,就讓父節點指向他的這一子樹前兩種情況可以合并處理第三種情況則是左右子樹都不為空,此時選擇一個來節點來替換他后,再刪除,就可以不破壞原有結構如果要保持原有結構不變化,那么選擇的節點必須要和刪除節點在中序遍歷中是連續的,而滿足的只有兩個節點,一個是其左子樹的最大值,一個是其右子樹的最小值。*///刪除部分while (cur){//找到刪除的位置if (key > cur->_kv.first){parent = cur;cur = cur->_right;}else if (key < cur->_kv.first){parent = cur;cur = cur->_left;}else{//前兩種情況合并處理,如果當前結點只有一個子樹,則讓父節點指向他的子樹//處理只有右子樹時 if (cur->_left == nullptr){//如果當前節點為根節點,則讓右子樹成為新的根節點if (cur == _root){_root = cur->_left;}else{//判斷當前節點是他父節點的哪一個子樹if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}delete cur;}//處理只有左子樹時 else if (cur->_right == nullptr){//如果當前節點為根節點,則讓左子樹成為新的根節點if (cur == _root){_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}delete cur;}//處理左右子樹都不為空時,選取左子樹的最右節點或者右子樹的最左節點else{//這里我選取的是左子樹的最右節點Node* LeftMax = cur->_left;Node* LeftMaxParent = cur;//找到左子樹的最右節點while (LeftMax->_right){LeftMaxParent = LeftMax;LeftMax = LeftMax->_right;}//替換節點std::swap(cur->_kv, LeftMax->_kv);//判斷當前節點是他父節點的哪一個子樹, 因為已經是最右子樹了,所以這個節點的右子樹為空,但是左子樹可能還有數據,所以讓父節點指向他的左子樹//并且刪除最右節點if (LeftMax == LeftMaxParent->_left){LeftMaxParent->_left = LeftMax->_left;}else{LeftMaxParent->_right = LeftMax->_left;}delete LeftMax;}//刪除成功,中斷break;}}//查找不到if (cur == nullptr)return false;//更新平衡因子while (parent){//更新父節點的平衡因子if (cur == parent->_left){parent->_bf++;}else{parent->_bf--;}//判斷更新后父節點是否平衡//平衡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){//如果右邊不平衡,并且子節點也是右邊偏重,則左單旋if (cur->_bf == 1){RotateL(parent);}//如果右邊不平衡,而子節點是左邊偏重,此時需要先轉換為上面的狀態,先右單旋再左單旋。但是不能直接右單旋再左單旋,還需要根據情況處理平衡因子else{RotateRL(parent);}}else{//左邊不平衡,并且子節點也是左邊偏重,右單旋if (cur->_bf == -1){RotateR(parent);}//同上,左右雙旋else{RotateLR(parent);}}//旋轉完后恢復平衡,更新結束。break;}}return true;}void _InOrderTravel(Node* root) const{if (root == nullptr)return;_InOrderTravel(root->_left);std::cout << root->_kv.first << ':' << root->_kv.second << std::endl;_InOrderTravel(root->_right);}void InOrderTravel() const{_InOrderTravel(_root);}int countHeight(Node* root) const{if (root == nullptr)return 0;int leftHeight = countHeight(root->_left);int rightHeight = countHeight(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}bool _IsBalance(Node* root) const{if (root == nullptr)return true;int leftHeight = countHeight(root->_left);int rightHeight = countHeight(root->_right);return abs(leftHeight - rightHeight) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}bool IsBalance() const{return _IsBalance(_root);}void destory(Node*& root){Node* node = root;if (!root)return;destory(node->_left);destory(node->_right);delete node;node = nullptr;}private:Node* _root;}; };

    總結

    以上是生活随笔為你收集整理的高级数据结构与算法 | AVL树 (高度平衡树)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。