1. 程式人生 > >資料結構之高度平衡搜尋樹AVL樹(含經典面試題----判斷一棵樹是否是AVL樹)

資料結構之高度平衡搜尋樹AVL樹(含經典面試題----判斷一棵樹是否是AVL樹)

什麼是AVL樹

AVL樹簡介

AVL樹又稱為高度平衡的二叉搜尋樹,目的在於儘量降低二叉樹的高度,減少平均搜尋長度。

滿足二叉搜尋樹的性質

類比二叉搜尋樹,先將樹的結構確定下來,下面處理滿足AVL樹獨有的性質即可。

滿足AVL樹的性質

  1. 左子樹和右子樹高度差的絕對值不超過1。
  2. 樹中的每一個左子樹和每一個右子樹都是AVL樹。
  3. 每一個節點都有一個平衡因子,AVL樹中任意節點的平衡因子可取值為-1,1,0 。

AVL樹插入、刪除、查詢效率問題

一顆AVL樹有N個節點,高度可以保持在log2N,插入、刪除、查詢的時間複雜度也是O(log2N).

AVL樹的插入

插入一個元素,只需要找到該元素合適的位置,然後插入進去(不能插入相同元素),此時還只是滿足二叉搜尋樹的性質,但是AVL樹是二叉搜尋樹的升級版,所以還要保證樹是平衡的。那就要求每次插入元素後檢查是否會影響樹的高度,即是否影響樹的平衡性,此時插入完畢,如果影響樹的高度就要做出調整。

平衡因子的更新規則
1、左子樹插入,那麼該節點對應的父親節點的bf-=1;
2、右子樹插入節點,該節點對應父親節點的bf+=1;

更新後的情況作如下分析:

情況1:當插入一個節點後當前節點的父節點的平衡因子變為0,說明當前樹是平衡的,不會對上層節點產生影響,所以結束更新直接返回。

情況2:假如插入節點之後該節點的父節點平衡因子變為1或者-1,此時說明之前父節點的平衡因子是0,那就破壞了平衡,那就會對上層節點的平衡性產生影響。此時就需要不斷迴圈向上層更新,只要上層是1或者-1,迴圈就無法停下,直到父節點為根節點為止,才算插入完畢。

情況3:(迴圈向上層更新父節點出現的)如果父節點的平衡因子變成-2 或者2,那麼就要進行旋轉,更新節點的bf值,調整樹的高度。

情況4:節點的平衡因子為-3 或者3,這種情況是不可能出現的,如果有說明原來的樹就不是AVL樹。我們要保證每個樹再插入之前都是一顆AVL樹。

bool Insert(const K& key){
        if (_root == NULL){
            _root = new Node(key);
            return true;
        }
        Node* cur = _root;
        Node* father = NULL;
        while (cur){
            if (cur->
_key == key){ return false; } else if (cur->_key < key){ father = cur; cur = cur->_right; } else{ father = cur; cur = cur->_left; } } //此時說明已經確定要插入的位置 cur = new Node(key); if (father->_key < cur->_key){ father->_right = cur; cur->_parent = father; } else(father->_key > cur->_key){ father->_left = cur; cur->_parent = father; } //插入完成後要記得更新平衡因子 while (father){ if (father->_left == cur){ father->_bf -= 1; } else (father->_right == cur){ father->_bf += 1; } //根據更新後的平衡因子判斷樹是否平衡,如果不平衡進行旋轉調整 if (father->_bf == 0){ return true; } else if (father->_bf == -1 || father->_bf == 1){ cur = father; father = father->_parent; //此時還不確定是否是不平衡的,要進一步向上追溯看上面父節點的bf //值是否為-2或者2,如果是就要旋轉,如果不是就不需要旋轉 } else if (father->_bf == -2 || father->_bf == 2){ if (father->_bf == 2 && cur->_bf == 1){ RotateL(father); return true; } if (father->_bf == -2 && cur->_bf == -1){ RotateR(father); return true; } if (father->_bf == 2 && cur->_bf == -1){ RotateRL(father); return true; } if (father->_bf == -2 && cur->_bf == 1){ RotateLR(father); return true; } } else{ cout << "平衡因子異常" << endl; } } }

解決情況3的旋轉方法實現(旋轉的分類)

1、左單旋:parent->bf=2;subR->bf=1

這裡寫圖片描述

思路:
1、將parent節點和subRL節點連結起來(parent->_right=subRL);
2、如果ppNode為空,那麼_root=subR。
3、ppNode不為空時,判斷parent是ppNode的左孩子還是右孩子,如果是左孩子,那麼ppNode->_left=subR;反之ppNode->_right=subR。
4、最後一步將subR和parent連線起來(subR->_left=parent,parent->_parent=subR)。

void RotateL(Node* parent){
        Node* SubR = parent->_right;
        Node* SubRL = SubR->_left;
        Node*PpNode = parent->_parent;
        parent->_right = SubRL;
        if (SubRL != NULL){
            SubRL->_parent = parent;
        }
        if (PpNode == NULL){
            _root = SubR;
            SubR->_parent = PpNode;
        }
        else{
            if (PpNode->_left == parent){
                PpNode->_left = SubR;
                SubR->_parent = PpNode;
            }
            else{
                PpNode->_right = SubR;
                SubR->_parent = PpNode;
            }
        }
        SubR->_left = parent;
        parent->_parent = SubR;
        parent->_bf = 0;
        SubR->_bf = 0;
    }

右單旋:parent->bf=-2;subL->bf=-1

這裡寫圖片描述

思路:
1、將subLR連到parent的左邊(sub小於subLR 小於parent)。
2、記錄父親的上層節點ppNode。然後判斷ppNode是否為空,是則說明父節點是根節點,只需要將根節點更新成subL即可。
3、ppNode不為空 ,此時只要直到原來的parent節點是ppNode節點的左邊還是右邊就可以確定subL是ppNode節點的左邊還是右邊。
4、最後一步就是將subL和parent連線起來,subL->_right=parent。

void RotateR(Node* parent){
        Node* SubL = parent->_left;
        Node* SubLR = SubL->_right;
        Node* PpNode = parent->_parent;
        parent->_left = SubLR;
        if (SubLR){
            SubLR->_parent = parent;
        }
        if (PpNode == NULL){
            _root = SubL;
            SubL->_parent = NULL;
        }
        else{
            if (PpNode->_left == parent){
                PpNode->_left = SubL;
            }
            else{
                PpNode->_right = SubL;
            }
            SubL->_parent = PpNode;
        }
        SubL->_right = parent;
        parent->_parent = SubL;
        SubL->_bf = 0;
        parent->_bf = 0;
    }

左右雙旋:parent->bf=-2;subL->bf=1

這裡寫圖片描述
這裡寫圖片描述

思路:
由於在subLR的右邊插入了新節點,那麼首先對以subLR為父節點進行左單旋(RotateL(subL)),完成左旋之後在以parent為父節點進行右單旋(RotateR(parent))。

在更新平衡因子時符合以下規律:

原來的subLR 1(新插入節點在subLR的右邊) 0 -1(新插入節點在subLR的左邊)
更新後的parent 0 0 1
更新後的subL 1 0 0
更新後的subLR 0 0 0
void RotateLR(Node* parent){
        Node* SubL = parent->_left;
        Node* SubLR = SubL->_right;
        int bf = SubLR->_bf;
        RotateL(SubL);
        RotateR(parent);
        //更新平衡因子
        if (bf == 0){
            parent->_bf = 0;
            SubL->_bf = 0;
        }
        else if (bf == 1){
            parent->_bf = 0;
            SubL->_bf = -1;
        }
        else{
            parent->_bf = 1;
            SubL->_bf =0;
        }
        SubLR->_bf = 0;
    }

右左雙旋:parent->bf=2;subR->bf=-1;

這裡寫圖片描述
這裡寫圖片描述

思路:
由於在subRL的左邊插入了新節點,那麼首先對以subR為父節點進行右單旋(RotateR(subR)),完成左旋之後在以parent為父節點進行左單旋(RotateL(parent))。

在更新平衡因子時符合以下規律:

原來的subRL 1(新插入節點在subRL的右邊) 0 -1(新插入節點在subRL的左邊)
更新後的parent -1 0 0
更新後的subR 0 0 1
更新後的subRL 0 0 0
void RotateRL(Node* parent){
        Node* SubR = parent->_right;
        Node* SubRL = SubR->_left;
        int bf = SubRL->_bf;
        RotateR(subR);
        RotateL(parent);
        //更新平衡因子
        if (bf == 0){
            parent->_bf = 0;
            SubR->_bf = 0;
            SubRL->_bf = 0;
        }
        else 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{
            cout << "引數有誤" << endl;
        }
    }

經典面試題——判斷一棵樹是否是AVL樹

思路1:遞迴的判斷左右子樹是不是AVL樹,同時判斷某一個子樹是否是AVL樹的時候又得遞迴求一遍其左右子樹的高度,通過高度差來判斷對應子樹是否是AVL樹,這樣一來時間複雜度就是O(N^2)

在你答出上面的思路時候,面試官常常會讓優化時間複雜度!!!
優化思路1的時間複雜度為O(N):判斷一棵樹是否是AVL樹,同時借用一個輸出型引數height帶出左右子樹的高度,利用高度差的絕對值小於2,得出結論。
(不採用遞迴檢視平衡因子的方式是由於有的時候平衡因子更新出錯,導致誤判)

bool IsAVLtree(){
        int height = 0;
        return _IsAVLtree(_root, height);
    }
    bool _IsAVLtree(Node* root, int& height){
        if (root == NULL){
            height = 0;
            return true;
        }
        int lheight = 0;
        if (_IsAVLtree(root->_left, lheight) == false){
            return false;
        }
        int rheight = 0;
        if (_IsAVLtree(root->_right, rheight) == false){
            return false;
        }
        if (abs(lheight - rheight) !=root->_bf){
            cout << "平衡因子有誤\n";
        }
        height = lheight > rheight ? lheight + 1 : rheight + 1;
        return abs(lheight - rheight) < 2;
    }

完整程式碼:

#include<iostream>
using namespace std;

template<class K>
struct TreeNode{
public:
    TreeNode(const K& key)
        :_parent(NULL)
        , _left(NULL)
        , _right(NULL)
        , _key(key)
        , _bf(0)
    {}
    TreeNode<K>* _parent;
    TreeNode<K>* _left;
    TreeNode<K>* _right;
     K _key;
    int _bf;
};
template< class K >

class AVLTree{
public:
    typedef TreeNode<K> Node;
    AVLTree()
        :_root(NULL)
    {}
    bool Insert(const K& key){
        if (_root == NULL){
            _root = new Node(key);
            return true;
        }
        Node* cur = _root;
        Node* father = NULL;
        while (cur){
            if (cur->_key == key){
                return false;
            }
            else if (cur->_key < key){
                father = cur;
                cur = cur->_right;
            }
            else{
                father = cur;
                cur = cur->_left;
            }
        }
        //此時說明已經確定要插入的位置
        cur = new Node(key);
        if (father->_key < cur->_key){
            father->_right = cur;
            cur->_parent = father;
        }
        else(father->_key > cur->_key){
            father->_left = cur;
            cur->_parent = father;
        }
        //插入完成後要記得更新平衡因子
        while (father){
            if (father->_left == cur){
                father->_bf -= 1;
            }
            else (father->_right == cur){
                father->_bf += 1;
            }
            //根據更新後的平衡因子判斷樹是否平衡,如果不平衡進行旋轉調整
            if (father->_bf == 0){
                return true;
            }
            else if (father->_bf == -1 || father->_bf == 1){
                cur = father;
                father = father->_parent;
                //此時還不確定是否是不平衡的,要進一步向上追溯看上面父節點的bf
                //值是否為-2或者2,如果是就要旋轉,如果不是就不需要旋轉
            }
            else if (father->_bf == -2 || father->_bf == 2){
                if (father->_bf == 2 && cur->_bf == 1){
                    RotateL(father);
                    return true;
                }
                if (father->_bf == -2 && cur->_bf == -1){
                    RotateR(father);
                    return true;
                }
                if (father->_bf == 2 && cur->_bf == -1){
                    RotateRL(father);
                    return true;
                }
                if (father->_bf == -2 && cur->_bf == 1){
                    RotateLR(father);
                    return true;
                }
            }
            else{
                cout << "平衡因子異常" << endl;
            }
        }
    }

    //左單旋
    void RotateL(Node* parent){
        Node* SubR = parent->_right;
        Node* SubRL = SubR->_left;
        Node*PpNode = parent->_parent;
        parent->_right = SubRL;
        if (SubRL != NULL){
            SubRL->_parent = parent;
        }
        if (PpNode == NULL){
            _root = SubR;
            SubR->_parent = PpNode;
        }
        else{
            if (PpNode->_left == parent){
                PpNode->_left = SubR;
                SubR->_parent = PpNode;
            }
            else{
                PpNode->_right = SubR;
                SubR->_parent = PpNode;
            }
        }
        SubR->_left = parent;
        parent->_parent = SubR;
        parent->_bf = 0;
        SubR->_bf = 0;
    }
    //右單旋
    void RotateR(Node* parent){
        Node* SubL = parent->_left;
        Node* SubLR = SubL->_right;
        Node* PpNode = parent->_parent;
        parent->_left = SubLR;
        if (SubLR){
            SubLR->_parent = parent;
        }
        if (PpNode == NULL){
            _root = SubL;
            SubL->_parent = NULL;
        }
        else{
            if (PpNode->_left == parent){
                PpNode->_left = SubL;
            }
            else{
                PpNode->_right = SubL;
            }
            SubL->_parent = PpNode;
        }
        SubL->_right = parent;
        parent->_parent = SubL;
        SubL->_bf = 0;
        parent->_bf = 0;
    }
    //左右雙旋
    void RotateLR(Node* parent){
        Node* SubL = parent->_left;
        Node* SubLR = SubL->_right;
        int bf = SubLR->_bf;
        RotateL(SubL);
        RotateR(parent);
        //更新平衡因子
        if (bf == 0){
            parent->_bf = 0;
            SubL->_bf = 0;
        }
        else if (bf == 1){
            parent->_bf = 0;
            SubL->_bf = -1;
        }
        else{
            parent->_bf = 1;
            SubL->_bf =0;
        }
        SubLR->_bf = 0;
    }
    //右左雙旋
    void RotateRL(Node* parent){
        Node* SubR = parent->_right;
        Node* SubRL = SubR->_left;
        int bf = SubRL->_bf;
        RotateR(subR);
        RotateL(parent);
        //更新平衡因子
        if (bf == 0){
            parent->_bf = 0;
            SubR->_bf = 0;
            SubRL->_bf = 0;
        }
        else 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{
            cout << "引數有誤" << endl;
        }
    }
    void InOrder(){
        _InOrder(_root);
        cout <<endl;
    }
    void _InOrder(Node* root){
        if (root == NULL){
            return;
        }
        _InOrder(_root->_left);
        cout << _root->_key << " ";
        _InOrder(_root->_right);
    }
    void Destroy(){
        if (_root == NULL){
            return;
        }
        Destroy(_root->_left);
        Destroy(_root->_right);
        delete _root;
        _root = NULL;
    }
    //經典面試題-----判斷一棵樹是否為平衡樹
    bool IsAVLtree(){
        int height = 0;
        return _IsAVLtree(_root, height);
    }
    bool _IsAVLtree(Node* root, int& height){
        if (root == NULL){
            height = 0;
            return true;
        }
        int lheight = 0;
        if (_IsAVLtree(root->_left, lheight) == false){
            return false;
        }
        int rheight = 0;
        if (_IsAVLtree(root->_right, rheight) == false){
            return false;
        }
        if (abs(lheight - rheight) !=root->_bf){
            cout << "平衡因子有誤\n";
        }
        height = lheight > rheight ? lheight + 1 : rheight + 1;
        return abs(lheight - rheight) < 2;
    }
private:
    Node* _root;
};
int main(){
    AVLTree<int> tree;
    tree.Insert(1);
    tree.Insert(2);
    tree.Insert(3);
    tree.Insert(4);
    tree.InOrder();
    cout << tree.IsAVLtree() << endl;
    system("pause");
    return 0;
}