1. 程式人生 > >AVL樹和平衡二叉樹 平衡因子 右旋轉LL 左旋轉RR LR RL

AVL樹和平衡二叉樹 平衡因子 右旋轉LL 左旋轉RR LR RL

  前言

  今天要介紹幾種高階資料結構AVL樹,介紹之前AVL,會先說明平衡二叉樹,並將樹的學習路線進行總結,並介紹維持平衡的方法:右旋轉、左旋轉。

  一、樹學習路線

  1、路線總結

  總結了一下樹的學習路線,如下圖:

  

  2、說明

  上面這個圖要從上往下進行一步一步學習;首先,從二叉樹開始學習,要對樹的一些概念有一些基本瞭解,如樹的左孩子和右孩子等,然後對樹的遍歷方法:先序、中序和後序遍歷都熟練掌握,有精力再把層序遍歷掌握;

  接下來,大部分的樹,都是在二叉樹的基礎上加了許多特性而形成的,所以二叉樹是基礎,如二叉搜尋樹,任意一個節點都比左子樹大,都比右子樹小,主要用於解決查詢問題,對二分查詢法有一個基本瞭解,還有一個特性,二分搜尋樹的中序遍歷:資料就從從小到大進行排序了。

  AVL樹,是今天要講的話題,下面會詳細進行講解。

  紅黑樹應該是大名鼎鼎了,都應該聽過了,之後我會專門介紹。

  trie樹就不再是二叉樹了,是多叉樹了,之後也會講解。

  二、平衡二叉樹

  1、定義

  平衡二叉樹,首先是二叉樹,並且對於任意一個節點,左子樹和右子樹的高度差不能超過1。

  2、意義

  有了二分查詢樹為什麼還要平衡二叉樹呢?這篇對二分查詢樹進行了詳細介紹,並對先序、中序和後序進行了明確說明,可以參考:https://www.cnblogs.com/liudw-0215/p/9835691.html,因為二叉樹有一個弊端就是會退化為連結串列就是隻有左子樹或右子樹有節點

,這樣查詢效率就會變低了。所以,就需要“平衡”這個概念了。

  3、平衡因子

  先畫個圖,進行說明,不是平衡二叉樹,只是為了說明問題,如下圖:

  

  說明:如上圖,樹的高度從葉子節點開始,並且葉子節點高度是1;平衡因子就是用左子樹高度減去右子樹高度,例如:4這個節點,左子樹2的高度為1,右子樹沒有則為0,所以4這個節點的平衡因子為1。

  三、AVL樹

  1、定義

    AVL樹是自平衡二分搜尋樹,既具有平衡性和二分性。

   2、構建AVL樹類

    是在二分搜尋樹的基礎上進行修改並維持“平衡”這個特性的,首先,來看下AVL樹的類,如下:

#ifndef AVLTREE_AVLTREE_H
#define AVLTREE_AVLTREE_H #include <algorithm> #include <iostream> #include <vector> template<typename Key, typename Value> class AVLTree { private: struct Node { Key key; Value value; Node *left; Node *right; int height; //用於標註高度,計算平衡因子 Node(Key key, Value value) { this->key = key; this->value = value; this->left = this->right = nullptr; height = 1; } Node(Node *node) { this->key = node->key; this->value = node->value; this->left = node->left; this->right = node->right; this->height = node->height; } }; Node *root; int size; public: AVLTree() { root = nullptr; size = 0; } ~AVLTree() { destroy(root); } int getSize() { return size; } int isEmpty() { return size == 0; } int getHeight(Node *node) { //獲取高度 if (node == nullptr) { return 0; } return node->height; } int getBalanceFactor(Node *node) { //獲取平衡因子 if (node == nullptr) { return 0; } return getHeight(node->left) - getHeight(node->right); } bool isBST() { std::vector<Key> keys; inOrder(root, keys); for (int i = 1; i < keys.size(); ++i) { if (keys.at(i - 1) < keys.at(i)) { return false; } } return true; } bool isBalanced() { return isBalanced(root); } void add(Key key, Value value) { root = add(root, key, value); } bool contains(Key key) { return getNode(root, key) != nullptr; } Value *get(Key key) { Node *node = getNode(root, key); return node == nullptr ? nullptr : &(node->value); } void set(Key key, Value newValue) { Node *node = getNode(root, key); if (node != nullptr) { node->value = newValue; } } // 從二叉樹中刪除鍵值為key的節點 Value *remove(Key key) { Node *node = getNode(root, key); if (node != nullptr) { root = remove(root, key); return &(node->value); } return nullptr; } private: // 向以node為根的二叉搜尋樹中,插入節點(key, value) // 返回插入新節點後的二叉搜尋樹的根 Node *add(Node *node, Key key, Value value) { if (node == nullptr) { size++; return new Node(key, value); } if (key == node->key) { node->value = value; } else if (key < node->key) { node->left = add(node->left, key, value); } else { node->right = add(node->right, key, value); } node->height = 1 + std::max(getHeight(node->left), getHeight(node->right)); int balanceFactor = getBalanceFactor(node); if (std::abs(balanceFactor) > 1) { std::cout << "unbalanced : " << balanceFactor; } return node; } // 在以node為根的二叉搜尋樹中查詢key所對應的Node Node *getNode(Node *node, Key key) { if (node == nullptr) { return nullptr; } if (key == node->key) { return node; } else if (key < node->key) { return getNode(node->left, key); } else { return getNode(node->right, key); } } void destroy(Node *node) { if (node != nullptr) { destroy(node->left); destroy(node->right); delete node; size--; } } // 在以node為根的二叉搜尋樹中,返回最小鍵值的節點 Node *minimum(Node *node) { if (node->left == nullptr) return node; return minimum(node->left); } // 在以node為根的二叉搜尋樹中,返回最大鍵值的節點 Node *maximum(Node *node) { if (node->right == nullptr) return node; return maximum(node->right); } // 刪除掉以node為根的二分搜尋樹中的最小節點 // 返回刪除節點後新的二分搜尋樹的根 Node *removeMin(Node *node) { if (node->left == nullptr) { Node *rightNode = node->right; delete node; size--; return rightNode; } node->left = removeMin(node->left); return node; } // 刪除掉以node為根的二分搜尋樹中的最大節點 // 返回刪除節點後新的二分搜尋樹的根 Node *removeMax(Node *node) { if (node->right == nullptr) { Node *leftNode = node->left; delete node; size--; return leftNode; } node->right = removeMax(node->right); return node; } // 刪除掉以node為根的二分搜尋樹中鍵值為key的節點 // 返回刪除節點後新的二分搜尋樹的根 Node *remove(Node *node, Key key) { if (node == nullptr) { return nullptr; } if (key < node->key) { node->left = remove(node->left, key); return node; } else if (key > node->key) { node->right = remove(node->right, key); return node; } else { if (node->left == nullptr) { Node *rightNode = node->right; delete node; size--; return rightNode; } if (node->right == nullptr) { Node *leftNode = node->left; delete node; size--; return leftNode; } Node *successor = new Node(minimum(node->right)); //Node *precursor = new Node(maximum(node->right)); size++; successor->right = removeMin(node->right); successor->left = node->left; //precursor->left = removeMax(node->left); //precursor->right = node->right; delete node; size--; return successor; //return precursor; } } void inOrder(Node *node, std::vector<Key> keys) { if (node == nullptr) { return; } inOrder(node->left, keys); keys.push_back(node->key); inOrder(node->right, keys); } bool isBalanced(Node *node) { if (node == nullptr) { return true; } int balanceFactor = getBalanceFactor(node); if (std::abs(balanceFactor) > 1) { return false; } return isBalanced(node->left) && isBalanced(node->right); } }; #endif //AVLTREE_AVLTREE_H
View Code

  增加height屬性,用於記錄每個節點的高度,並計算平衡因子;

  3、獲取節點高度

  把height屬性返回就可以了:

int getHeight(Node *node) {    //獲取高度
        if (node == nullptr) {
            return 0;
        }
        return node->height;
    }

  4、獲取平衡因子

  將左子樹高度減去右子樹高度即可,但注意:不要區絕對值,因為之後的旋轉要判斷左子樹還是右子樹的高度高,程式碼如下:

int getBalanceFactor(Node *node) {    //獲取平衡因子
        if (node == nullptr) {
            return 0;
        }
        return getHeight(node->left) - getHeight(node->right);
    }

  5、判斷是不是平衡二叉樹

  平衡因子大於1就不是平衡二叉樹了,程式碼如下:

bool isBalanced(Node *node) {
        if (node == nullptr) {
            return true;
        }

        int balanceFactor = getBalanceFactor(node);
        if (std::abs(balanceFactor) > 1) {
            return false;
        }

        return isBalanced(node->left) && isBalanced(node->right);
    }

bool isBalanced() {
        return isBalanced(root);
    }

  四、AVL樹的旋轉

   1、什麼時維護平衡?

  如下圖,假如原來沒有2這個節點,那麼樹是平衡二叉樹,但插入2之後,就不再平衡了,這時就需要維護平衡了,大體上有4種情況需要維護平衡,來說明這一種。

    

    2、右旋轉 LL

     將其中的部分節點抽離出來,如下圖:

  

  說明:主要分為兩步:

  第一步:將T3儲存,然後將y以及孩子節點旋轉到x的右孩子位置,相對於x,y是順時針向右旋轉的,所以叫右旋轉;

  第二步:將T3移到y的左孩子位置

  最後,形成的二叉樹符合二分和平衡兩個性質,所以還是平衡二叉樹。

  3、右旋轉程式碼實現

  上圖應該已經講解的很明白了吧,程式碼如下:

Node *rightRotate(Node *y) {
        Node *x = y->left;    //存x
        Node *tmp = x->right;    //將x的右孩子備份
        x->right = y;    //將y右旋轉到x的右孩子
        y->left = tmp;    //將x的右孩子移到y的左側
        y->height = std::max(getHeight(y->left), getHeight(y->right)) + 1;    //修改y高度,注意要先修改y的高度
        x->height = std::max(getHeight(x->left), getHeight(x->right)) + 1;    //修改x的高度
        return x;
    }

  4、左旋轉 RR

   左旋轉和右旋轉很相似,只是方向不同,如下圖:

  

  說明:相對於x,y是逆時針向左旋轉,所以是左旋轉

   5、左旋轉程式碼實現

  左旋轉程式碼跟右旋轉很相似,程式碼如下:

Node *leftRotate(Node *y){
        Node *x = y->right;
        Node *tmp = x->left;
        x->left = y;
        y->right = tmp;
        y->height = std::max(getHeight(y->left), getHeight(y->right)) + 1;
        x->height = std::max(getHeight(x->left), getHeight(x->right)) + 1;
        return x;
    }

  6、LR

  還有兩種情況需要討論,LL代表“左左”,LR代表“左右”,如下圖:

  

  說明:藉助左旋轉將LR轉為LL,再對LL進行右旋轉就OK了,所以理解左、右旋轉是基礎!

  7、LR程式碼實現

  程式碼如下:

if (balanceFactor > 1 && getBalanceFactor(node->left) < 0) {    //LR
            node->left = leftRotate(node->left);
            return rightRotate(node);
        }

  8、RL

  最後一種情況RL,如下圖:

  

  9、RL程式碼實現

  程式碼如下:

if (balanceFactor < -1 && getBalanceFactor(node->right) > 0) {    //RL
            node->right = rightRotate(node->right);
            return leftRotate(node);
        }

  總結

  AVL樹和平衡二叉樹就比較難了,主要理解右旋轉和左旋轉,對之後理解紅黑樹有巨大作用!