AVL树的插入与删除

来源:互联网 发布:樱井知香喷泉图片下载 编辑:程序博客网 时间:2024/05/17 23:50

AVL树是一种高度平衡的二叉搜索树,其每一个结点的左树高和右树高相差不大于1。这个性质使得AVL树的搜索效率要比普通的二叉搜索树要高,因为对于一组递增的数组,其构成的二叉搜索树会是一个链表,搜索时间复杂度自然就是O(n),而其构成的AVL树则肯定是一个搜索效率为O(lg(n))的二叉树。也正因为此,为了保持其平衡的性质,AVL树的插入和删除要比普通二叉搜索树要复杂。

二叉搜索树可以参考二叉搜索树的插入与删除

1. 通过旋转保持AVL树的平衡

旋转分为左旋和右旋两种,这两种操作是对称的,如下图所示。

在插入或者删除结点后,AVL树保持平衡的方法是旋转。对于下图中一颗左子树要比右子树高大1的树来说,如果在其左子树中插入一个结点,则会引起该树不平衡。此时,可通过一次或两次旋转使树平衡。


当结点被插入到b结点的左子树下,只需要对树a进行一次右旋即可,如下图所示,旋转后的树a左子树和右子树高会达到平衡。

当结点被插入到b结点的右子树下,则需要先对树b进行一次左旋转变成上一种情况,再对树a进行一次右旋,如下图所示。

对树b进行左旋后,树b的左子树比右子树高1,即转变为上一种情况(将结点插入到树b的左子树中),然后再对树a进行一次右旋即可将树调整回平衡状态。为什么要对树b进行一次左旋呢?假设我们部队树b左旋,直接对树a右旋,得到的树会是如下图所示。

旋转后的树并没有变得平衡!原因很简单,因为右旋树a的时候,树b的右子树被转移到树a上了,这导致旋转前后导致树不平衡的这颗子树的高没有变化,树依然不平衡,因此我们需要先通过对树b进行一次左旋将树b变成一颗左子树比右子树高的树。

对于结点a的右子树比左子树高,然后结点被插入到结点a的右子树中的情况则是与上述情况完全对称。

下面是AVL树左旋和右旋的代码。

void AVLLeftRotate(Tree *tree, Node *x){leftRotate(tree, x);x->height = max(getHeight(x->left), getHeight(x->right)) + 1;x->parent->height = x->height + 1;if (x->parent->parent != NULL){x->parent->parent->height = max(getHeight(x->parent->parent->left), getHeight(x->parent->parent->right)) + 1;}}void AVLRightRotate(Tree *tree, Node *x){rightRotate(tree, x);x->height = max(getHeight(x->left), getHeight(x->right)) + 1;x->parent->height = x->height + 1;if (x->parent->parent != NULL){x->parent->parent->height = max(getHeight(x->parent->parent->left), getHeight(x->parent->parent->right)) + 1;}}void leftRotate(Tree *tree, Node *x){Node *y = x->right;x->right = y->left;if (y->left != tree->nil && y->left != NULL){y->left->parent = x;}y->parent = x->parent;if (x->parent == tree->nil || x->parent == NULL){tree->root = y;}else if (x == x->parent->left){x->parent->left = y;}else{x->parent->right = y;}y->left = x;x->parent = y;y->size = x->size;x->size -= ((y->right != tree->nil && y->right != NULL) ? y->right->size : 0) + 1;}void rightRotate(Tree *tree, Node *x){Node *y = x->left;x->left = y->right;if (y->right != tree->nil && y->right != NULL){y->right->parent = x;}y->parent = x->parent;if (x->parent == tree->nil || x->parent == NULL){tree->root = y;}else if (x == x->parent->left){x->parent->left = y;}else{x->parent->right = y;}y->right = x;x->parent = y;y->size = x->size;x->size -= ((y->left != tree->nil && y->left != NULL) ? y->left->size : 0) + 1;}

上述代码中,结点的size属性记录该结点的子树中一共有多少个结点,height属性为该结点的树高。

2. 插入新结点

插入结点后的AVL树可能会不平衡,我们需要用旋转来保持树的平衡。程序的工作流程是:

1). 和普通的二叉搜索树一样,将新结点z插入到合适的位置作为叶子结点;

2). 从结点z出发向上回溯,如果z的父结点的父结点zpp不为空,则判断zpp是否平衡,即zpp的左子树和右子树的高相差是否不大于1,如果平衡则从结点z的父结点zp继续往上回溯,否则则通过旋转调整zpp的平衡;

3). 旋转zpp的方法是,加入zpp的左子树zl比右子树zr高2,而且zl的左子树比右子树高,则只需要对zpp进行一次右旋即可,如果zl的左子树比右子树低,则要先对zl进行一次左旋,再对zpp右旋。zpp的左子树比右子树低2的情况对称处理即可。

得到某一结点的树高的方法如下。

int getHeight(Node *node){if (node == NULL)return -1;return node->height;}

插入新节点的方法和普通的二叉搜索树插入结点的方法一样,代码如下。

void insertAVLTree(Tree *tree, int value){Node *node = createAVLNode(value);if (tree->root == NULL){tree->root = node;return;}Node *n = tree->root;while (1){n->size++;if (value < n->value){if (n->left == NULL){n->left = node;node->parent = n;break;}n = n->left;}else{if (n->right == NULL){n->right = node;node->parent = n;break;}n = n->right;}}AVLInsertFixup(tree, node);}

上述代码的关键在于在插入结点完成后,需要从新插入的结点node开始向上回溯调整树的平衡。AVLInsertFixUp方法的代码如下。

// 从插入的新节点开始向上更新树高并通过旋转结点保持平衡void AVLInsertFixup(Tree *tree, Node *z){Node *tmp = z;while (z != NULL){z->height = max(getHeight(z->left), getHeight(z->right)) + 1;if (z->parent != NULL && z->parent->parent != NULL){Node *zp = z->parent;Node *zpp = zp->parent;zp->height = max(getHeight(zp->left), getHeight(zp->right)) + 1;zpp->height = max(getHeight(zpp->left), getHeight(zpp->right)) + 1;if (getHeight(zpp->left) - getHeight(zpp->right) == -2){// 左旋zpp之前确保zp的右树高大于等于左树高if (getHeight(zp->left) > getHeight(zp->right)){AVLRightRotate(tree, zp);}AVLLeftRotate(tree, zpp);}else if (getHeight(zpp->left) - getHeight(zpp->right) == 2){// 右旋zpp之前确保zp的左树高大于等于右树高if (getHeight(zp->left) < getHeight(zp->right)){AVLLeftRotate(tree, zp);}AVLRightRotate(tree, zpp);}}z = z->parent;}}

上述代码在向上回溯的同时,一边按需调整树的平衡,一边更新各个结点的高度。

3. 删除结点

删除结点可能会导致该结点所在的树高发生变化,从而引起树的不平衡。AVL树删除结点先执行和普通二叉搜索树一样的过程,然后再从最开始因为删除结点而树高发生变化的结点向上回溯调整树高。该操作的关键在于如何确定“最先发生树高变化的结点”。因为普通二叉搜索树的删除结点有3种情况,所以AVL树对“最先发生树高变化的结点”的确定也有3种情况。假设被删除的结点为x

1). x是叶子结点,“最先发生树高变化的结点”为x的父结点;

2). x只有一个孩子,“最先发生树高变化的结点”为x的父结点;

3). x有两个孩子,则“最先发生树高变化的结点”为x的后驱的父结点。

在确定了“最先发生树高变化的结点”之后,就需要从该结点出发向上回溯,与插入结点一样,对沿路经过的结点按需进行旋转。

删除结点以及删除结点后调整的代码如下。下面的代码使用了一个transplant方法通过“移植”树来删除结点。在像普通二叉搜索树一样删除结点后,就执行AVLDeleteFixup方法调整树高以使树达到平衡。

/*** 将*tree1替换成tree2,若*tree1为根,则直接改变*tree1指向*/void transplant(Tree *tree, Node *tree1, Node *tree2){if (tree1->parent == NULL){tree->root = tree2;tree2->parent = NULL;return;}if (tree2 != NULL)tree2->parent = tree1->parent;if (tree1 == tree1->parent->left)tree1->parent->left = tree2;elsetree1->parent->right = tree2;}void deleteFromAVLTree(Tree *tree, Node *node){if (node == NULL)return;Node *d = node->parent;Node *n = node;while (n->parent != NULL){n = n->parent;n->size--;}if (node->left == NULL){transplant(tree, node, node->right);}else if (node->right == NULL){transplant(tree, node, node->left);}else{Tree t;t.root = node->right;Node *min = minimum(&t);Node *end = min->right;min->size = node->size - 1;if (node == tree->root)tree->root = min;if (node->right != min){transplant(tree, min, min->right);min->right = node->right;node->right->parent = min;d = min->parent;}else{d = min;}min->left = node->left;node->left->parent = min;transplant(tree, node, min);n = min->right;while (n != end){n->size--;n = n->left;}}AVLDeleteFixup(tree, d);free(node);}// 从实际被删除的结点的父结点(因为从该节点开始树高有可能发生变化)出发,调整树高并通过旋转保持平衡void AVLDeleteFixup(Tree *tree, Node *z){while (z != NULL){z->height = max(getHeight(z->left), getHeight(z->right)) + 1;if (getHeight(z->left) - getHeight(z->right) == -2){Node *zr = z->right;if (getHeight(zr->left) > getHeight(zr->right)){AVLRightRotate(tree, zr);}AVLLeftRotate(tree, z);}else if (getHeight(z->left) - getHeight(z->right) == 2){Node *zl = z->left;if (getHeight(zl->left) < getHeight(zl->right)){AVLLeftRotate(tree, zl);}AVLRightRotate(tree, z);}z = z->parent;}}

4. AVL树操作的时间复杂度

插入操作需要O(lg(n))的遍历时间以及至多两次旋转的常数时间;

删除操作需要O(lg(n))的遍历时间以及若干次旋转的常数时间;

总的来说,AVL树的插入和删除都只需要O(lg(n))的时间,只是其常系数量不同。

最后,本博客已经介绍的和即将介绍的数据结构与算法的C实现代码都在这里 数据结构与算法

1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 360儿童卫士忘记密码怎么办 微信注册忘记密码怎么办 旅行青蛙换手机iphone怎么办 网页邮箱添加不了附件怎么办 pos机密码忘了怎么办 在4楼上课地震了怎么办 微鲸电视待机打不开怎么办 苹果手机微信听不到语音怎么办 电信怎么办腾讯频看电视不要流量 孩子做事效率低怎么办教案 孩子做事效率低怎么办读后感 做事效率低总是焦虑怎么办 红米手机黑屏打不开怎么办 手机萤石云视频下载打不开怎么办 oppo打开网页视频慢怎么办? 晒课显示待提交怎么办 华为手机无法访问移动网络怎么办 晒课上传课堂实录太大怎么办 手机酷狗音乐下载要钱怎么办 手机酷狗下载要钱怎么办 酷我音乐没有声音怎么办 手机酷我音乐没有声音怎么办 酷我音乐歌曲下载收费怎么办 网易云下载超过每日上限怎么办 全民k歌领不了花怎么办 安卓全民k歌延迟怎么办 全民k歌唱歌延迟怎么办 全民k歌耳机延迟怎么办 word文档打开是乱码怎么办 全民k歌不能录音怎么办 全民k歌登录不上怎么办 平果手机迅雷闪退怎么办 电脑打开央视影音死机怎么办 先锋影音二级网页打不开怎么办 手机qq音乐登录失效怎么办 酷狗账号忘记了怎么办 手机qq音乐听不了歌怎么办 第一试用网密码忘了怎么办 玩h1z1画面卡顿怎么办 uu跑腿抢不到单怎么办 比特币加密忘了怎么办