数据结构实现 10.1:AVL树(C++版)
1. 概念及基本框架
AVL树 (即自平衡树)是一种二分搜索树,AVL 树要求每个结点的左右子树的树高度差不超过 1 。
因为在二分搜索树中,不管二叉树左右子树的高度,所以二分搜索树的最坏情况可以退化成链表。所以这里我们需要对二分搜索树进行适当的修正,提高效率。
与二分搜索树类似,我们先给出AVL树的结点类:
template <typename K, typename V>
class AVLNode{
public:
AVLNode(K key, V value){
this->key = key;
this->value = value;
height = 1;
left = NULL;
right = NULL;
}
public:
K key;
V value;
int height;
AVLNode<K, V> *left;
AVLNode<K, V> *right;
};
这里我们增加了高度变量,用来记录每个结点的当前高度。
下面给出AVL树大体框架:
template <typename K, typename V>
class AVLTree{
public:
AVLTree(){
root = NULL;
m_size = 0;
}
...
private:
AVLNode<K, V> *root;
int m_size;
};
与二分搜索树十分类似,所以这里不再赘述。
接下来我们就对AVL树的增加、删除、查找、遍历以及一些其他基本操作用代码去实现。
2. 基本操作程序实现
2.1 增加操作
既然AVL树是二分搜索树的一种,所以其基本实现代码与二分搜索树类似,我们直接给出:
template <typename K, typename V>
class AVLTree{
public:
...
//增加操作
void add(K key, V value);
...
private:
AVLNode<K, V>* add(AVLNode<K, V>* node, K key, V value);
...
};
类外实现代码:
template <typename K, typename V>
void AVLTree<K, V>::add(K key, V value){
root = add(root, key, value);
}
template <typename K, typename V>
AVLNode<K, V>* AVLTree<K, V>::add(AVLNode<K, V>* node, K key, V value){
if (node == NULL){
m_size++;
return new AVLNode<K, V>(key, value);
}
else if (key < node->key){
node->left = add(node->left, key, value);
}
else if (key > node->key){
node->right = add(node->right, key, value);
}
else if (key == node->key){
node->value = value;
return node;
}
//计算平衡因子
node->height = max(getHeight(node->left), getHeight(node->right)) + 1;
int balanceFactor = getBalanceFactor(node);
//平衡维护
if (balanceFactor > 1 && getBalanceFactor(node->left) >= 0){
return rightRotate(node);
}
else if (balanceFactor < -1 && getBalanceFactor(node->right) <= 0){
return leftRotate(node);
}
else if (balanceFactor > 1 && getBalanceFactor(node->left) < 0){
node->left = leftRotate(node->left);
return rightRotate(node);
}
else if (balanceFactor < -1 && getBalanceFactor(node->right) > 0){
node->right = rightRotate(node->right);
return leftRotate(node);
}
return node;
}
这里需要注意的是在进行完增加操作之后我们需要进行一定的修正。修正之前需要得到一些基础信息,用下面几个函数来实现。
max 返回其中的最大值。
getHeight 返回一个结点的高度。
getBalanceFactor 返回一个结点的平衡因子。(用左子树高度减去右子树高度)
leftRotate、rightRotate 分别表示左旋、右旋,具体实现方法后面详细讲述。
前面三个函数代码如下:
template <typename K, typename V>
class AVLTree{
...
private:
...
int max(int a, int b){
return a > b ? a : b; }
...
};
template <typename K, typename V>
int AVLTree<K, V>::getHeight(AVLNode<K, V>* node){
if (node == NULL){
return 0;
}
return node->height;
}
template <typename K, typename V>
int AVLTree<K, V>::getBalanceFactor(AVLNode<K, V>* node){
int lHeight = 0, rHeight = 0;
if (node == NULL){
return 0;
}
if (node->left != NULL){
lHeight = node->left->height;
}
if (node->right != NULL){
rHeight = node->right->height;
}
return lHeight - rHeight;
}
接下来我们详细讲述修正的左旋和右旋函数。
首先我们分情况,当一个新增加的结点需要修正,一定是其祖父结点一脉单传到了新结点。那么,作为一个新增加的结点,有两种可能,在父结点的左/右,而父结点也可以在祖父节点的左/右,所以一共有四种。我们可以简称为左左、左右、右左、右右四种情况。
2.1.1 左左
注:这里每个结点都挂有子树(子树可以为空),这些子树本身已经是平衡的了(下同,这里考虑的是修正自下而上递推到这里),我们可以忽略。
如上图,结点 C 左右子树平衡因子为 2 > 1 ,所以需要修正,修正过程如下:
1.新建一个临时结点 P 指向 B 的右子结点。
2.把 C 作为 B 的右子结点。
3.把 P 作为 C 的左子结点。
4.将结点 B 返回。
经过旋转,结点的数据依旧满足二分搜索树的基本性质,所以可以旋转,这里结点 B 和结点 C 发生了 右旋 。
图中虚线圈住的结点表示实际发生了旋转的结点,橙色(紫色)的线表示发生变化的支,黄色的线表示返回结点的指向,后面不再赘述。
右旋代码实现如下:
template <typename K, typename V>
AVLNode<K, V>* AVLTree<K, V>::rightRotate(AVLNode<K, V>* node){
AVLNode<K, V>* res =