之前有篇文章讲二叉排序树( 动态查找二叉排序树
), 二叉排序树是用于动态查找的数据结构,但在文章结尾中也说明了普通的二叉排序树的查找效率是不确定的,这主要取决于二叉排序树 是否平衡,为了充分发挥二叉排序树的查找效率我们通常使用平衡的二叉排序树—— 平衡二叉树,常用的平衡二叉树主要有两种—— AVL树和 红黑树。今天我们介绍AVL树。
简介
AVL树的平衡条件为:其每个节点的左子树和右子树的高度最多为差1,但它必须首先是二叉排序树。如下图,图1、图4是平衡二叉树,图2不符合二叉排序树的性质所以肯定不是平衡二叉树,图3中的58节点的左右子树高度差为3>1,因此不是平衡二叉树。
平衡二叉树插入数据时需要检查其平衡条件是否被破坏,如果被破坏就要对结构进行调整使其重新平衡。如下图插入节点37时,节点58的左、右子树高度差为2,二叉树的平衡性被破坏,此时我们就需要对其结构进行调整。调整时,我们从新插入节点向上回溯,找到第一个其左、右子树高度差大于1的节点,以此节点为根节点的子树我们称之为最小不平衡树,然后对其最小不平衡树进行调整即可。如下图所示,虚线缩所圈的便是其最小不平衡树。
平衡二叉树的构建
我们把需要调整的最小不平衡树的根节点称为 α \alpha α,新插入的节点造成不平衡的情况出现在以下4种情况:
- 对 α \alpha α的左儿子的左子树进行插入;
- 对 α \alpha α的左儿子的右子树进行插入;
- 对 α \alpha α的右儿子的左子树进行插入;
- 对 α \alpha α的右儿子的右子树进行插入。
以上情况的1和4是关于 α \alpha α节点镜像对称的,2、3也是。所以理论上我们只需考虑两种情况,第一种是插入发生在“外边”即左-左或右-右的情况(情形1和4)。这时需要一次单旋转即可完成调整;第二种是插入发生在“内部”即左-右和右-左的情况(情形2和3),这时需要较为复杂的双旋转才可完成调整。
单旋转
单旋转分为左旋和右旋。下图展示了右旋如何调整平衡的情况。
如上图所示,左图为旋转前因插入新节点而不平衡的最小不平衡树,右图为旋转后重新平衡的子树。调整过程为:我们让k1节点的右子树成为k2节点的左子树,然后使k2子树整体成为k1节点的右子树,最后使k1节点成为整个子树的根节点。左旋的原理与左旋相同只是旋转方向不同,见下图。
调整过程为:我们让k2节点的左子树成为k1节点的右子树,然后使k1子树整体成为k2节点的左子树,最后使k2节点成为整个子树的根节点。
接下来我们演示从初始空的AVL树依次插入数字3~1。首先插入3和2,正常插入不需要调整,然后插入1,发现平衡被破坏,此时我们对最小不平衡树进行右旋使其平衡。如下图所示。
然后我们依次插入数字4~7,插入4时没有问题,然后插入5平衡被破坏,对节点3之后的子树进行左旋使其重新平衡。见下图。
然后插入6,我们将自根节点2以下进行左旋,此时节点4成为新的根节点。
然后插入数字7,将自节点5以下子树进行左旋。
以上便是单旋转便可解决的情形,也就是情形1和4。
双旋转
对于情形2和3仅使用单旋转无法解决问题。如下图所示。
此时新节点插入到了k2节点的左孩子k1的右子树中,使k1节点右子树的高度得不到降低。这时不能再将k2作根,但也不能将k1作为根。那应该如何呢?注意此时的关键原因是Y中有一棵子树比Z高两层,但无法确定是哪一层,于是我们将Y分为两个子树,为了降低高度,我们需要让k2当根节点,所以我们需要让k1子树进行一次左旋,使k2节点向上升一层,然后右旋使k2成为根节点。
上图便是双旋转中的左-右双旋转,对应情形2。而情形3所需的右-左双旋转与左-右双旋转原理相同见下图。
我们在上述平衡二叉树中以倒序方式继续插入数字10~16以演示双旋转。首先插入16没有问题,然后插入15,引起节点7处高度不平衡,属于情形3,我们进行右-左双旋转。
接着插入14,需要一次右-左双旋转。
插入13,只需一次单旋转。
插入12,需要一次单旋转。
插入11和10,需要一次单旋转,然后插入8。
插入9演示左-右双旋转。
总结上述,我们得到编程的逻辑:
- 进行正常的插入然后更新各节点高度;
- 检查是否破坏平衡,若不破坏则不需要进行调整,否则进行步骤3;
- 检查属于哪种情形,然后进行相对应的旋转并重新调整高度。
代码实现
平衡二叉树节点结构
平衡二叉树节点结构如下所示:
struct AVLnode
{
int key;//存储数据
struct AVLnode *left;
struct AVLnode *right;
int height;//存储节点高度
};
typedef struct AVLnode avlNode;
平衡二叉树类声明
class AvlTree
{
public:
AvlTree(): root(nullptr),height(0){};//默认构造函数
AvlTree(const AvlTree& T);//以另一个树构造
AvlTree(vector<int> vec);//以vector容器构造
~AvlTree() {};
private:
avlNode* root;//根节点指针
unsigned int height;//高度
protected:
avlNode *newNode(int key);//分配新节点并返回节点地址
int nodeHeight(avlNode *node);//返回节点高度
int heightDiff(avlNode *node);//返回节点左右子树高度差
avlNode *minNode(avlNode *node);/* Returns the node with min key in the left subtree*/
avlNode *rightRotate(avlNode *z);//右旋
avlNode *leftRotate(avlNode *z);//左旋
avlNode *LeftRightRotate(avlNode *z);//左-右双旋转
avlNode *RightLeftRotate(avlNode *z);//右-左双旋转
avlNode *findNode(avlNode *node, int queryNum);//查找节点
avlNode *insert(avlNode *node, int key);//插入新节点(供void Push(int key)调用)
avlNode *Delete(avlNode * node, int queryNum);//删除节点
void _printInOrder(avlNode *node);//中序打印
void Getelems(avlNode *node,vector<int>& vec);//获取以node为根节点的平衡二叉树的数据
public:
void Push(int key);//插入新节点
void Pop(int queryNum);//删除节点
void printInOrder();//中序打印
AvlTree& operator = (const AvlTree& T);
};
平衡二叉树类实现
#include "AvlTree.h"
inline int max(int a, int b)
{
return (a > b) ? a : b;
}
AvlTree::AvlTree(const AvlTree & T)
{
this->root = NULL;
vector<int> vec;
Getelems(T.root, vec);
vector<int>::iterator iter = vec.begin();
for (; iter != vec.end(); iter++)
{
this->Push(*iter);
}
}
AvlTree::AvlTree(vector<int> vec)
{
vector<int>::iterator iter = vec.begin();
for (; iter != vec.end(); iter++)
{
this->Push(*iter);
}
}
avlNode * AvlTree::newNode(int key)
{
avlNode *node = (avlNode*)malloc(sizeof(avlNode));
if (node == NULL)
printf("!! Out of Space !!\n");
else
{
node->key = key;
node->left = NULL;
node->right = NULL;
node->height = 0;
}
return node;
}
int AvlTree::nodeHeight(avlNode * node)
{
if (node == NULL)
return -1;
else
return(node->height);
}
int AvlTree::heightDiff(avlNode * node)
{
if (node == NULL)
return 0;
else
return(nodeHeight(node->left) - nodeHeight(node->right));
}
avlNode * AvlTree::minNode(avlNode * node)
{
avlNode *temp = node;
while (temp->left != NULL)
temp = temp->left;
return temp;
}
avlNode * AvlTree::rightRotate(avlNode * z)
{
avlNode *y = z->left;
avlNode *T3 = y->right;
y->right = z;
z->left = T3;
z->height = (max(nodeHeight(z->left), nodeHeight(z->right)) + 1);
y->height = (max(nodeHeight(y->left), nodeHeight(y->right)) + 1);
return y;
}
avlNode * AvlTree::leftRotate(avlNode * z)
{
avlNode *y = z->right;
avlNode *T3 = y->left;
y->left = z;
z->right = T3;
z->height = (max(nodeHeight(z->left), nodeHeight(z->right)) + 1);
y->height = (max(nodeHeight(y->left), nodeHeight(y->right)) + 1);
return y;
}
avlNode * AvlTree::LeftRightRotate(avlNode * z)
{
z->left = leftRotate(z->left);
return (rightRotate(z));
}
avlNode * AvlTree::RightLeftRotate(avlNode * z)
{
z->right = rightRotate(z->right);
return (leftRotate(z));
}
avlNode * AvlTree::insert(avlNode *node,int key)
{
if (node == NULL)
return (newNode(key));
/*Binary Search Tree insertion*/
if (key < node->key)
node->left = insert(node->left, key); /*Recursive insertion in L subtree*/
else if (key > node->key)
node->right = insert(node->right, key); /*Recursive insertion in R subtree*/
/* Node Height as per the AVL formula*/
node->height = (max(nodeHeight(node->left), nodeHeight(node->right)) + 1);
/*Checking for the balance condition*/
int balance = heightDiff(node);
/*Left Left */
if (balance>1 && key < (node->left->key))
return rightRotate(node);
/*Right Right */
if (balance<-1 && key >(node->right->key))
return leftRotate(node);
/*Left Right */
if (balance>1 && key > (node->left->key))
{
node = LeftRightRotate(node);
}
/*Right Left */
if (balance<-1 && key < (node->right->key))
{
node = RightLeftRotate(node);
}
return node;
}
avlNode * AvlTree::Delete(avlNode * node, int queryNum)
{
if (node == NULL)
return node;
if (queryNum < node->key)
node->left = Delete(node->left, queryNum); /*Recursive deletion in L subtree*/
else if (queryNum > node->key)
node->right = Delete(node->right, queryNum); /*Recursive deletion in R subtree*/
else
{
/*Single or No Child*/
if ((node->left == NULL) || (node->right == NULL))
{
avlNode *temp = node->left ? node->left : node->right;
/* No Child*/
if (temp == NULL)
{
temp = node;
node = NULL;
}
else /*Single Child : copy data to the parent*/
*node = *temp;
free(temp);
}
else
{
/*Two Child*/
/*Get the smallest key in the R subtree*/
avlNode *temp = minNode(node->right);
node->key = temp->key; /*Copy that to the root*/
node->right = Delete(node->right, temp->key); /*Delete the smallest in the R subtree.*/
}
}
/*single node in tree*/
if (node == NULL)
return node;
/*Update height*/
node->height = (max(nodeHeight(node->left), nodeHeight(node->right)) + 1);
int balance = heightDiff(node);
/*Left Left */
if ((balance>1) && (heightDiff(node->left) >= 0))
return rightRotate(node);
/*Left Right */
if ((balance>1) && (heightDiff(node->left) < 0))
{
node = LeftRightRotate(node);
}
/*Right Right */
if ((balance<-1) && (heightDiff(node->right) >= 0))
return leftRotate(node);
/*Right Left */
if ((balance<-1) && (heightDiff(node->right) < 0))
{
node = RightLeftRotate(node);
}
return node;
}
void AvlTree::Push(int key)
{
root=insert(root,key);
}
void AvlTree::Pop(int queryNum)
{
root = Delete(root,queryNum);
}
void AvlTree::printInOrder()
{
_printInOrder(root);
}
AvlTree & AvlTree::operator=(const AvlTree & T)
{
// TODO: 在此处插入 return 语句
this->root = NULL;
vector<int> vec;
Getelems(T.root, vec);
vector<int>::iterator iter = vec.begin();
for (; iter != vec.end(); iter++)
{
this->Push(*iter);
}
return *this;
}
void AvlTree::_printInOrder(avlNode * node)
{
if (node == NULL)
return;
_printInOrder(node->left);
printf("%d ", (node->key));
_printInOrder(node->right);
}
void AvlTree::Getelems(avlNode * node, vector<int>& vec)
{
if (!node)
return;
Getelems(node->left,vec);
vec.push_back(node->key);
Getelems(node->right,vec);
}
avlNode * AvlTree::findNode(avlNode * node, int queryNum)
{
if (node != NULL)
{
if (queryNum < node->key)
node = findNode(node->left, queryNum);
else if (queryNum > node->key)
node = findNode(node->right, queryNum);
}
return node;
}
测试
测试代码:
int main()
{
int i;
int a[] = { 62,88,58,47,35,73,51,99,37,93 };
AvlTree avl;
for (i = 0; i < 10; i++)
{
avl.Push(a[i]);
}
vector<int> vec= { 62,88,58,47,35,73,51,99,37,93 };
AvlTree avl2(vec);
cout << "avl数据:";
avl.printInOrder();
cout << endl;
cout << "avl2数据:";
avl2.printInOrder();
cout << endl;
cout << "删除58节点后avl数据:";
avl.Pop(58);
avl.printInOrder();
getchar();
return 0;
}
输出:
avl数据:35 37 47 51 58 62 73 88 93 99
avl2数据:35 37 47 51 58 62 73 88 93 99
删除58节点后avl数据:35 37 47 51 62 73 88 93 99
总结
AVL树为平衡二叉树,适用于频繁查找的动态查找,但对于频繁插入和删除的动态查找不适合,此时需要红黑树,我们将在后期文章讲解红黑树。
最后声明本文代码引用自开源项目改编。