一棵AVL树或者是空树,或者是具有谢下列性质的二叉搜索树:他的左子树和右子树都是AVL树,且左子树和右子树的高度只差的绝对值不超过1.
如上图,左边的二叉搜索树每个结点的左右子树高度差的绝对值小于1,所以是AVL树,右边的树不是每个结点的左右子树高度差的绝对值小于1所以不是AVL树。
每个结点附加一个数字,给出该结点右子树的高度减去左子树的高度所得的高度差。这个数字既是为结点的平衡因子balance。根据AVL树的定义,任何一个结点的平衡因子只能取-1,0,1。
如果一个结点的平衡因子的绝对值大于1,则这颗二叉搜索树就失去了平衡不再是AVL树。如果一棵二叉搜索树是高度平衡的,他就称为AVL树。如果他又n个结点,其高度可保持在O(log2n),平均搜索长度可保持在O(log2n)。
AVL树结构体相比于二叉搜索树多了一个平衡因子
typedef int KeyType;
typedef struct AVLNode {struct AVLNode* leftchild;struct AVLNode* parent;struct AVLNode* rightchild;int balance;KeyType key;
}AVLNode;
typedef struct {AVLNode* root;int cursize;
}AVLTree;
在向一棵本来是高度平衡的的AVL树中插入一个数据导致出现了不平衡,我们就需要做平衡话处理,使得树中各结点重新平衡化。
左单旋转的操作如下图,我们从新加入的结点向上遍历,找到平衡因子不小于等于1的结点后有如下三步操作:
代码:
AVLNode* RotateLeft(AVLTree* ptree, AVLNode* ptr) {assert(ptree != nullptr && ptr != nullptr);AVLNode* newroot = ptr->rightchild;//1newroot->parent = ptr->parent;//1的双亲ptr->rightchild = newroot->leftchild;//2if (newroot->leftchild != nullptr) {//2的双亲newroot->leftchild->parent = ptr;}newroot->leftchild = ptr;//3AVLNode* pa = ptr->parent;if (pa == nullptr) {//此处ptr为n根节点ptree->root = newroot;}else {//ptr不是根节点if (pa->leftchild == ptr) {pa->leftchild = newroot;}else {pa->rightchild = newroot;}}ptr->parent = newroot;//3的双亲return newroot;
}
右单旋转和左单旋转是相同的思想,如下图:
代码:
void RotateRight(AVLTree* ptree, AVLNode* ptr) {assert(ptree != nullptr && ptr != nullptr);AVLNode* newroot = ptr->leftchild;//1newroot->parent = ptr->parent;//ptr->leftchild = newroot->rightchild;//2if (newroot->rightchild != nullptr) {newroot->rightchild->parent = ptr;}newroot->rightchild = ptr;//3AVLNode* pa = ptr->parent;if (pa == nullptr) {ptree->root = newroot;}else {if (pa->leftchild == ptr) {pa->leftchild = newroot;}else {pa->rightchild = newroot;}}ptr->parent = newroot;
}
下图如果新增加
先左后右的操作如下图:
具体操作:
void LeftBalance(AVLTree* ptree, AVLNode* ptr) {//左平衡assert(ptree != nullptr && ptr != nullptr);AVLNode* leftsub = ptr->leftchild, * ringhtsub = nullptr;switch (leftsub->balance) {case 0:printf("tree left balance \n"); break;case -1://新插入结点在D上 直线 右单旋ptr->balance = 0;leftsub->balance = 0;//平衡因子自行修改RotateRight(ptree, ptr);break;case 1://折线 左右旋转ringhtsub = leftsub->rightchild;switch (ringhtsub->balance) {case 0:ptr->balance = 0;leftsub->balance = 0;break;case -1:ptr->balance = 1;leftsub->balance = 0;break;case 1:ptr->balance = 0;leftsub->balance = -1;break;}ringhtsub->balance = 0;RotateLeft(ptree,leftsub);//左右单旋即先左单旋后右单旋RotateRight(ptree, ptr);break;}
}
同先左后右双旋转刚好相反,思路一致,此处不多做叙述
代码:
AVLNode* RightBalance(AVLTree* ptree, AVLNode* ptr) {assert(ptree != nullptr && ptr != nullptr);AVLNode* newroot=nullptr;AVLNode* rightsub = ptr->rightchild, *leftsub = nullptr;switch (rightsub->balance) {case 0:break;case 1:ptr->balance = 0;rightsub->balance = 0;newroot=RotateLeft(ptree, ptr);break;case -1:leftsub = rightsub->leftchild;switch (leftsub->balance) {case 0:ptr->balance = 0;rightsub->balance = 0;break;case 1:ptr->balance = -1;rightsub->balance = 0;break;case -1:ptr->balance = 0;rightsub->balance = 1;break;}leftsub->balance = 0;newroot=RotateRight(ptree, rightsub);newroot=RotateLeft(ptree, ptr);break;}return newroot;
}
向AVL树中插入数据时,刚开始同我们二叉搜索树的插入一致。我们通过新插入的结点开始向根回溯的同时判断,我们肯定要首先判断插入的位置,判断其插入的位置是左孩子还是右孩子,
左孩子:原来新插入结点的双亲结点的平衡因子是0则变为-1.原来平衡因子是1则变为0,且高度不会改变,如果是-1,我们就会发现发生了问题,平衡因子本质上已经变成了-2,所以我们进行左平衡,做了平衡操作(旋转)之后他就会变成了原来的高度,所以高度不会改变。
右孩子同左孩子类似判断即可。
代码:
void Adjust_Insert_Item(AVLTree* ptree,AVLNode* ptr) {assert(ptree != nullptr && nullptr != ptr);bool taller = true;//判断高度会不会发生改变AVLNode* pa = ptr->parent;while (pa != nullptr && taller) {//插入位置if (pa->leftchild == ptr) {//插入到左子树switch (pa->balance) {case 0:pa->balance = -1; break;case 1:pa->balance = 0; taller = false;break;case -1:LeftBalance(ptree, pa);taller = false;break;}}else {//插入右子树switch (pa->balance) {case 0:pa->balance = 1; break;case -1:pa->balance = 0; taller = false;break;case 1:RightBalance(ptree, pa);taller = false;break;}}ptr = pa;//回溯pa = ptr->parent;}
}
bool Insert_Item(AVLTree* ptree, KeyType kx) {assert(ptree != nullptr);if (ptree->root == nullptr) {//判断不存在根节点ptree->root = Buynode();ptree->root->key = kx;ptree->cursize = 1;return true;}AVLNode* ptr = ptree->root, * pa = nullptr;while (ptr != nullptr && ptr->key != kx) {//遍历,遍历结束找出该结点本应该存放的双亲结点pa = ptr;ptr = kx > ptr->key ? ptr->rightchild : ptr->leftchild;}if (ptr != nullptr && ptr->key == kx) return false;//树中原本就存在这个数据,直接返回错误//树中不存在该结点进行插入ptr = Buynode();//购买新节点ptr->key = kx;//新节点关键码赋值ptr->parent = pa;//双亲指针赋值//判断双亲结点的关键码与要插入的值的大小比较if (ptr->key < pa->key) {pa->leftchild = ptr;}else {pa->rightchild = ptr;}Adjust_Insert_Item(ptree, ptr);ptree->cursize += 1;//节点个数加一return true;
}
删除分析:
case 1:当前结点p的balance为0.如果他的左子树或右子树被缩短,则balance(下图中的3类似于p)
case 2:结点p的balance不为0,且较高的子树被缩短,则p的balance改为0,同时shorter置为true。(下图中的7和3结点即为p)
case 3:结点p的balance不为0,且较矮的子树又被虽短,且在结点p发生不平衡。需要进行平衡话旋转来恢复平衡。令p的较高的子树的根为q,根据q的balance,有如下三种情况(下图中q的平衡因子大小来判断,下图均是右哦平衡为例)
case 3a:q的balance为1,我们发现他是直线,先将旋转后的平衡因子进行赋值,然后进行左单旋。
case 3b:q的平衡因子是0,同上,先进行旋转后的赋值,然后进行左单旋。
case 3c:q的平衡因子为-1,所以是折线,要进行双旋转,我们因此我们根据r的balance来判断,进行操作。
bool Adj_LeftBalance(AVLTree* ptree, AVLNode* &pa) {assert(ptree != nullptr && pa != nullptr);AVLNode* leftsub = pa->leftchild, * rightsub = nullptr;bool ret = false;switch (leftsub->balance) {case 0:pa->balance = -1;leftsub->balance=1;RotateRight(ptree, pa);ret = false;break;case -1:pa->balance = 0;leftsub->balance = 0;RotateRight(ptree,pa);ret = true;break;case 1:rightsub = leftsub->rightchild;switch (rightsub->balance) {case 0:pa->balance = 0;leftsub->balance = 0;break;case 1:break;pa->balance = 0;leftsub->balance = -1;case -1:pa->balance = 1;leftsub->balance = 0;break;}rightsub->balance = 0;RotateLeft(ptree, leftsub);pa=RotateRight(ptree, pa);ret = true;break;}return ret;
}
bool Adj_RightBalance(AVLTree* ptree,AVLNode* &pa){assert(ptree != nullptr && pa != nullptr);AVLNode* rightsub = pa->rightchild, * leftsub = nullptr;bool ret = false;switch (rightsub->balance){//q的平衡因子判断后面是如何旋转case 0:pa->balance = 1;rightsub->balance = -1;pa=RotateLeft(ptree,pa);ret = false;break;case 1:pa->balance = 0;rightsub->balance = 0;pa=RotateLeft(ptree, pa);ret = true;break;case -1:leftsub = rightsub->leftchild;switch (leftsub->balance) {case 0:pa->balance = 0;rightsub->balance = 0;break;case 1:pa->balance = -1;rightsub->balance = 0;break;case -1:pa->balance = 0;rightsub->balance = 1;break;}rightsub->balance = 0;RotateRight(ptree, rightsub);pa=RotateLeft(ptree, pa);ret = true;break;}return ret;
}
void Adjust_Del_Tree(AVLTree* ptree, AVLNode* ptr, bool leftTag)
{assert(ptree != nullptr && ptr != nullptr);bool shorter = true;AVLNode* pa = ptr->parent;while (pa != nullptr && shorter){if (leftTag){ // pa->leftchild ==> ptr;switch (pa->balance){case 0: pa->balance = 1;shorter = false;//本来左右高度一样,删除左孩子,高度没发生变化break;case -1: pa->balance = 0;shorter = true;//原本左高右低,删除左孩子,高度变化break;case 1://左低右高,删除左孩子,需要进行右平衡。shorter = Adj_RightBalance(ptree, pa);break;}}else{ // pa->rightchild ==> ptr;switch (pa->balance){case 0: pa->balance = -1;shorter = false;break;case 1: pa->balance = 0;shorter = true;break;case -1:shorter = Adj_LeftBalance(ptree, pa);break;}}ptr = pa;pa = ptr->parent;if (pa != nullptr){leftTag = pa->leftchild == ptr ? true : false;}}
}
bool Remove_Item(AVLTree* ptree, const KeyType kx)
{assert(ptree != nullptr);if (ptree->root == nullptr) return false;AVLNode* ptr = FindValue(ptree, kx);bool leftTag = true;if (ptr == nullptr) return false;if (ptr->leftchild != nullptr && ptr->rightchild != nullptr){AVLNode* nextnode = Next(ptr); // Prev(ptr);ptr->key = nextnode->key;ptr = nextnode;}// del leaf; del oneBrchAVLNode* child = ptr->leftchild != nullptr ? ptr->leftchild : ptr->rightchild;AVLNode* pa = ptr->parent;if (pa != nullptr)//修改指向关系之前判断出待删除结点是双亲指针的左孩子还是右孩子{leftTag = pa->leftchild == ptr ? true : false; }if (child != nullptr) child->parent = pa;if (pa != nullptr){if (pa->leftchild == ptr){pa->leftchild = child;}else{pa->rightchild = child;}}else{ptree->root = child;}if (ptr != nullptr && ptr->parent != nullptr){Adjust_Del_Tree(ptree, ptr, leftTag); // pa = ptr->parent;}Freenode(ptr);ptree->cursize -= 1;return true;
}