C语言-AVL树

一、定义        

        AVL树即为带平衡条件的二叉搜索树。在保留二叉搜索树特性的同时,又加上了平衡条件,即每个节点的左右子树的高度最多差1。

        AVL树的存储结构如下:

typedef struct avlnode {
	int data;
	struct avlnode* left;
	struct avlnode* right;
	int height;
}avlnode;

          本文主要讨论AVL树的插入操作。

二、旋转

1.旋转操作

        所谓的旋转操作是保证AVL树平衡条件的一种操作方法。当我们插入新节点的时候,有可能破坏AVL树原有的平衡条件。我们记必须重新平衡的节点为a。由于一个节点最多有两个子节点,因此树不平衡时,两棵子树的高度最多差2。不难发现不平衡会出现在以下四种情况之中:

(1).对a左子节点的左子树进行插入。

(2).对a左子节点的右子树进行插入。

(3).对a右子节点的左子树进行插入。

(4).对a右子节点的右子树进行插入。

其中(1)(4)镜像对称,(2)(3)镜像对称。所以理论分析上我们只有两种情况。当然,我们写程序时还要分4种情况。

2.单旋转操作

        当插入节点在“外侧”时(即左-左或右-右的情况),进行所谓的单旋转操作。下图为左-左的情况示意图。在示意图中,在同一虚线上代表在同一层。

我们可以把这个过程形象的想象为抓住节点k1,使劲“抖动”,使Y子树连接到k2上。这样就完成了子树的调整。由于子树的高度差最多为2,这样调整时可以满足AVL树的平衡条件的。

现在我们写出左-左旋转的代码。在写代码时,务必保持头脑清醒,最好对着画好的图来写。

//左-左,单旋转函数:左外侧子树长
avlnode* singlerotateleft(avlnode* root) {
	//更新节点的链接
	avlnode* root1 = root->left;
	root->left = root1->right;
	root1->right = root;
	//更新树的深度值
	root->height = umax(Height(root->left), Height(root->right)) + 1;//注意这里的加一
	root1->height = umax(Height(root1->left), Height(root1->right)) + 1;
	return root1;
}

对于右-右的情况,我们也可以相似处理:

代码:

//右-右,单旋转函数:右外侧子树长
avlnode* singlerotateright(avlnode* root) {
	avlnode* root1 = root->right;
	root->right = root1->left;
	root1->left = root;
	root->height = umax(Height(root->left), Height(root->right)) + 1;
	root1->height = umax(Height(root1->left), Height(root1->right)) + 1;
	return root1;
}

3.双旋转操作

        当插入节点在“内侧”的时候(如左-右或右-左情况),一次单旋转是无法完成修正的(如图4-34)

如何解决这一问题呢?这时,我们要将Y看成一个节点和两棵子树,在将两个子树分别放到左右两边(如图4-35)

我们可以把双旋转看成两个单旋转,即先将k1,k2进行一次单旋转,在对k3,k2进行一次单旋转。

按照这个想法,我们可以写出代码:

//左-右双旋转函数:左内侧子树长 root-k3,root1-k1,root2-k2
avlnode* doublerotateleft(avlnode* k3) {
	avlnode* k1 = k3->left, * k2 = k3->left->right;
	将k1和k2旋转,注意是右-右单旋转,将旋转完的结果作为root的左子节点
	k3->left = singlerotateright(k1);
	将k3和k2旋转,注意是左-左单旋转,将旋转完的结果返回
	return singlerotateleft(k3);
}

相似的,我们也可以写成右-左双旋转:

//右-左,双旋转函数:右内侧子树长
avlnode* doublerotateright(avlnode* k3) {
	k3->right = singlerotateleft(k3->right);
	return singlerotateright(k3);
}

三、插入节点操作

        在有了以上对旋转的讨论之后,完成节点的插入操作就不再是难事。

        总体上插入函数的思路与二叉搜索树的插入函数思路相似,不过多了对树高导致不平衡的条件的讨论。

//插入函数
void insert(avlnode** root, int x) {
	if (*root == NULL) {
		*root = (avlnode*)malloc(sizeof(avlnode));
		(*root)->data = x;
		(*root)->height = 0;
		(*root)->left = (*root)->right = NULL;
	}
	else {
		if ((*root)->data > x) {
			insert(&(*root)->left, x);
			if (Height((*root)->left) - Height((*root)->right) == 2) {
                //高度差等于二,树不再平衡
				if (x < (*root)->left->data) {//在“外侧”
					(*root) = singlerotateleft((*root));
				}
				else {//在“内侧”
					(*root) = doublerotateleft((*root));
				}
			}
		}
		else if ((*root)->data < x) {
			insert(&(*root)->right, x);
			if (Height((*root)->right) - Height((*root)->left) == 2) {
				if (x > (*root)->right->data) {//在“外侧”
					(*root) = singlerotateright((*root));
				}
				else {//在“内侧”
					(*root) = doublerotateright((*root));
				}
			}
		}
		//如果相等,那么不必操作
	}
	(*root)->height = umax(Height((*root)->left), Height((*root)->right)) + 1;更新高度
}

不过值得注意的是,代码中的Height函数单独出来写是有必要的,因为我们要处理空树的情况。单独出来写代码更加简洁。

int Height(avlnode* root) {
	if (root == NULL) return -1;//我们将空树的高度定义为-1
	return root->height;
}

 三、AVL树节点的删除操作

        《数据结构与算法分析》一书中认为AVL树的删除操作较为复杂,更加推荐使用懒惰删除,即保留节点在树中,但是加以标记。

        如果按正常的思路完成AVL树节点的删除也不是不行,但总共有12种情况。个人认为以下两篇文章对删除操作解释比较清楚:​​​​​​​​​​

 1.【数据结构】AVL树的删除(解析有点东西哦)

关于删除的图解:

2.AVL树删除,详细图解

参考资料:《数据结构与算法分析:C语言描述》

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值