文章目錄
- 前言
- 平衡二叉樹
- 1 簡介
- 2 旋轉
- 2.1 左旋
- 2.2 右旋
- 2.3 何時旋轉
- 3 插入節點
- 4 刪除節點
- 5 代碼
- 參考資料
- 寫在最后
前言
本系列專注更新基本數據結構,現有以下文章:
【算法與數據結構】數組.
【算法與數據結構】鏈表.
【算法與數據結構】哈希表.
【算法與數據結構】二叉查找樹
【算法與數據結構】平衡二叉樹
平衡二叉樹
1 簡介
平衡二叉樹是為了解決二叉查找樹中樹退化成鏈這一問題而提出的。平衡二叉樹在插入、刪除某一節點之后通過左旋或右旋操作保持動態平衡,從而避免節點失衡(最壞的情況是樹退化成鏈)。
二叉樹某節點是否失衡由該節點的 失衡因子 來衡量,失衡因子為左子樹節點高度與右子節點高度之差。
當二叉樹中某節點的左、右子樹高度差不超過1,則表示該節點平衡,高度差不超過 1 對應平衡因子的值為 ±1
和 0
。
左旋與右旋,由于插入、刪除節點導致二叉樹中出現不平衡節點,此時可以通過左旋或右旋操作調整節點位置以達到二叉查找樹的動態平衡。平衡二叉樹也是一棵二叉查找樹,它的查找、插入、刪除都與二叉查找樹的操作相同,只是在插入、刪除新的節點之后需要通過一些旋轉操作來保持其平衡性。
2 旋轉
2.1 左旋
左旋,顧名思義,指的是向左旋轉節點,或者是逆時針旋轉節點。在圖 2-1 中,由于插入了一個新的節點到平衡二叉樹中,根節點 3 平衡因子變為 0-2=-2
,說明此時根節點已經失衡了。為了保持這棵二叉查找樹的平衡性,需要左旋根節點。

實現代碼
// 左旋操作
Node* leftRotate(Node* x) {Node* y = x->right;Node* T2 = y->left;y->left = x;x->right = T2;x->height = std::max(height(x->left), height(x->right)) + 1;y->height = std::max(height(y->left), height(y->right)) + 1;return y;
}
上述代碼是左旋的具體操作,傳入的一個節點指針 x
,表示從該節點開始進行左旋,最終返回的是左旋操作后該子樹新的根節點。
以圖 2-1 為例,傳入的節點為 3,首先記錄 3 的右子節點 4,記作 y;接著記錄 y 的左子節點空節點,記作 T2;接下來將 4 的左鏈接連到 3 上,3 的右鏈接連到空節點上。
最后,還需要更新節點 x
和 y
的高度,為后面計算平衡因子做準備。
2.2 右旋
右旋,指的是向右旋轉節點,或者是順時針旋轉節點。在圖 2-2 中,由于插入了一個新的節點到平衡二叉樹中,根節點 9 平衡因子變為 2-0=2
,說明此時根節點已經失衡了。為了保持這棵二叉查找樹的平衡性,需要右旋根節點。

實現代碼
// 右旋操作
Node* rightRotate(Node* y) {Node* x = y->left;Node* T2 = x->right;x->right = y;y->left = T2;y->height = std::max(height(y->left), height(y->right)) + 1;x->height = std::max(height(x->left), height(x->right)) + 1;return x;
}
右旋操作與左旋操作類似,具體實現見代碼。
2.3 何時旋轉
什么時候需要進行旋轉操作,當然是失衡的時候。以下列出的是失衡的四種情況,以及為了保持平衡需要做出的旋轉決策:
- LL型,在當前節點的左子節點的左子節點插入新的節點后,當前節點失衡,此時為了保持平衡需要右旋當前節點。
- RR型,在當前節點的右子節點的右子節點插入新的節點后,當前節點失衡,此時為了保持平衡需要左旋當前節點。
- LR型,在當前節點的左子節點的右子節點插入新的節點后,當前節點失衡,此時為了保持平衡需要先左旋當前節點的左子節點,再右旋當前節點。
- RL型,在當前節點的右子節點的左子節點插入新的節點后,當前節點失衡,此時為了保持平衡需要先右旋當前節點的右子節點,再左旋當前節點。
在 LL型 情況中,我們注意到失衡節點的失衡因子為 2,失衡節點的左子節點的失衡因子為 1。類似的,在 RR型 中,失衡節點的失衡因子為 -2,失衡節點的失衡因子為 -1;在 LR型 中,失衡節點的失衡因子為 2,失衡節點的失衡因子為 -1;在 RL型 中,失衡節點的失衡因子為 -2,失衡節點的失衡因子為 1。于是,我們可以根據失衡節點的失衡因子、失衡節點的左子節點的失衡因子、失衡節點的右子節點的失衡因子來判斷當前是什么類型,進而做出相應的旋轉決策。




3 插入節點
插入節點和在二叉查找樹中插入節點一樣,先進行未命中的查找,將節點插入到合適的位置。然后根據以上四種情況進行旋轉以保持平衡二叉樹的平衡性。比如在圖 2-6 中插入一個新的節點 8,通過二分查找確定節點 8 的位置為節點 6 右子節點。但是插入節點 8 之后,造成根節點 5 的平衡因子失衡了。因為這是 RL型 情況,所以先右旋根節點的右子樹,然后再左旋根節點。
需要注意的是,當插入節點導致多個祖先節點失衡,只需要調整距離插入節點最近的失衡節點即可,其他失衡節點會自動平衡。

實現代碼
// 插入節點
Node* insert(Node* node, int key) {if (node == nullptr)return new Node(key);if (key < node->key)node->left = insert(node->left, key);else if (key > node->key)node->right = insert(node->right, key);elsereturn node;node->height = 1 + std::max(height(node->left), height(node->right));int balance = getBalance(node);// LL 型if (balance > 1 && key < node->left->key)return rightRotate(node);// RR 型if (balance < -1 && key > node->right->key)return leftRotate(node);// LR 型if (balance > 1 && key > node->left->key) {node->left = leftRotate(node->left);return rightRotate(node);}// RL 型if (balance < -1 && key < node->right->key) {node->right = rightRotate(node->right);return leftRotate(node);}return node;
}
在上述插入代碼中,我們先進行未命中的查找,在遞歸查找中插入新的節點。接著,更新當前根節點的高度。最后根據當前節點的平衡因子的不同值來確定是哪一個類型,再根據不同的類型進行相應的調整。具體地:
- 如果當前節點的失衡因子為 2,并且是在當前節點的左子節點的左子節點插入新的節點,則是 LL 型,右旋該節點即可;
- 如果當前節點的失衡因子為 -2,并且是在當前節點的右子節點的右子節點插入新的節點,則是 RR 型,左旋節點即可;
- 如果當前節點的失衡因子為 2,并且是在當前節點的左子節點的右子節點插入新的節點,則是 LR 型,先左旋該節點的左子節點,再右旋該節點;
- 如果當前節點的失衡因子為 -2,并且是在當前節點的右子節點的左子節點插入新的節點,則是 RL 型,先右旋該節點的右子節點,再左旋該節點;
4 刪除節點
刪除節點操作也需要先進行查找,遞歸的進行查找。查找到需要刪除的節點 root
之后,按照以下情況進行處理:
- 如果該節點是葉子節點,直接刪除該節點;
- 如果該節點僅有左子節點或者右子節點,則刪除對應的子節點;
- 如果該節點的左右節點都齊全,則用右子節點中最小的節點更新
root
,并刪除這個右子節點。
接著更新 root
的高度。最后將失衡的節點旋轉,保持平衡。依然是根據當前節點與左、右子節點的平衡因子來判斷屬于哪種旋轉情況,然后進行相應的旋轉。
實現代碼
// 刪除節點
Node* deleteNode(Node* root, int key) {if (root == nullptr)return root;if (key < root->key)root->left = deleteNode(root->left, key);else if (key > root->key)root->right = deleteNode(root->right, key);else {if ((root->left == nullptr) || (root->right == nullptr)) {Node* temp = root->left ? root->left : root->right;if (temp == nullptr) {temp = root;root = nullptr;} else*root = *temp;delete temp;} else {Node* temp = minValueNode(root->right);root->key = temp->key;root->right = deleteNode(root->right, temp->key);}}if (root == nullptr)return root;root->height = 1 + std::max(height(root->left), height(root->right));int balance = getBalance(root);if (balance > 1 && getBalance(root->left) >= 0)return rightRotate(root);if (balance > 1 && getBalance(root->left) < 0) {root->left = leftRotate(root->left);return rightRotate(root);}if (balance < -1 && getBalance(root->right) <= 0)return leftRotate(root);if (balance < -1 && getBalance(root->right) > 0) {root->right = rightRotate(root->right);return leftRotate(root);}return root;
}
5 代碼
#include <iostream>
#include <algorithm>class AVLTree {
private:struct Node {int key;Node* left;Node* right;int height;Node(int k) : key(k), left(nullptr), right(nullptr), height(1) {}};Node* root;int height(Node* n) {return n == nullptr ? 0 : n->height;}// 求平衡因子int getBalance(Node* n) {return n == nullptr ? 0 : height(n->left) - height(n->right);}// 右旋操作Node* rightRotate(Node* y) {Node* x = y->left;Node* T2 = x->right;x->right = y;y->left = T2;y->height = std::max(height(y->left), height(y->right)) + 1;x->height = std::max(height(x->left), height(x->right)) + 1;return x;}// 左旋操作Node* leftRotate(Node* x) {Node* y = x->right;Node* T2 = y->left;y->left = x;x->right = T2;x->height = std::max(height(x->left), height(x->right)) + 1;y->height = std::max(height(y->left), height(y->right)) + 1;return y;}// 插入節點Node* insert(Node* node, int key) {if (node == nullptr)return new Node(key);if (key < node->key)node->left = insert(node->left, key);else if (key > node->key)node->right = insert(node->right, key);elsereturn node;node->height = 1 + std::max(height(node->left), height(node->right));int balance = getBalance(node);// LL 型if (balance > 1 && key < node->left->key)return rightRotate(node);// RR 型if (balance < -1 && key > node->right->key)return leftRotate(node);// LR 型if (balance > 1 && key > node->left->key) {node->left = leftRotate(node->left);return rightRotate(node);}// RL 型if (balance < -1 && key < node->right->key) {node->right = rightRotate(node->right);return leftRotate(node);}return node;}// 獲得最小節點值Node* minValueNode(Node* node) {Node* current = node;while (current->left != nullptr)current = current->left;return current;}// 刪除節點Node* deleteNode(Node* root, int key) {if (root == nullptr)return root;if (key < root->key)root->left = deleteNode(root->left, key);else if (key > root->key)root->right = deleteNode(root->right, key);else {if ((root->left == nullptr) || (root->right == nullptr)) {Node* temp = root->left ? root->left : root->right;if (temp == nullptr) {temp = root;root = nullptr;} else*root = *temp;delete temp;} else {Node* temp = minValueNode(root->right);root->key = temp->key;root->right = deleteNode(root->right, temp->key);}}if (root == nullptr)return root;root->height = 1 + std::max(height(root->left), height(root->right));int balance = getBalance(root);if (balance > 1 && getBalance(root->left) >= 0)return rightRotate(root);if (balance > 1 && getBalance(root->left) < 0) {root->left = leftRotate(root->left);return rightRotate(root);}if (balance < -1 && getBalance(root->right) <= 0)return leftRotate(root);if (balance < -1 && getBalance(root->right) > 0) {root->right = rightRotate(root->right);return leftRotate(root);}return root;}// 查找節點Node* search(Node* root, int key) {if (root == nullptr || root->key == key)return root;if (key < root->key)return search(root->left, key);return search(root->right, key);}// 中序遍歷void inOrder(Node* root) {if (root != nullptr) {inOrder(root->left);std::cout << root->key << " ";inOrder(root->right);}}public:AVLTree() : root(nullptr) {}// 插入節點的對外接口void insert(int key) {root = insert(root, key);}// 刪除節點的對外接口void deleteNode(int key) {root = deleteNode(root, key);}// 搜索節點的對外接口bool search(int key) {return search(root, key) != nullptr;}// 中序遍歷的對外接口void inOrder() {inOrder(root);std::cout << std::endl;}
};int main() {AVLTree tree;tree.insert(10);tree.insert(20);tree.insert(30);tree.insert(40);tree.insert(50);tree.insert(25);std::cout << "中序遍歷后的AVL樹: ";tree.inOrder();tree.deleteNode(10);std::cout << "刪除節點10后的AVL樹: ";tree.inOrder();int key = 25;if (tree.search(key))std::cout << "找到節點: " << key << std::endl;elsestd::cout << "未找到節點: " << key << std::endl;return 0;
}
參考資料
【視頻】平衡二叉樹(AVL樹)
寫在最后
如果您發現文章有任何錯誤或者對文章有任何疑問,歡迎私信博主或者在評論區指出 💬💬💬。
如果大家覺得有些地方需要補充,歡迎評論區交流。
最后,感謝您的閱讀,如果有所收獲的話可以給我點一個 👍 哦。