【数据结构】二叉平衡树(AVL树)

引入

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。
因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

1、AVL树的概念

AVL树是在满足二叉搜索树特性的基础上,且满足以下特性:

  1. 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
  2. 它的左右子树都是AVL树

平衡因子的计算方法:右子树高度 - 左子树高度

下面这棵二叉树就是一棵AVL树:
在这里插入图片描述

2、AVL树的操作

2.1 AVL树结点的定义
	template <class T>
	class TreeNode
	{
		int m_bf;//该结点的平衡因子,可能取值有:0/1/-1
		T m_data;
		TreeNode<T> * m_left;
		TreeNode<T> * m_right;
		TreeNode<T> * m_parent;
	public:
		TreeNode(const T & val = T()) :
			m_bf(0),
			m_data(val),
			m_left(nullptr),
			m_right(nullptr),
			m_parent(nullptr)
		{

		}
	};
2.2 AVL树的插入

AVL树是在二叉搜索树的基础上进行操作的,对AVL树插入新的结点需要进行以下两个步骤:
1.按照二叉搜索树插入元素的规则插入结点
2.调整平衡因子

  • 插入curh可分为以下两种情况:
    1.若插入结点是parent的左孩子,则parent->bf - 1;
    2.若插入结点是parent的右孩子,则parent->bf + 1;

  • 插入cur后parent的平衡因子可能有三种情况:0,正负1, 正负2
    1.若parent->bf ==0,说明插入之前parent->bf ==±1,插入后被调整为0,此时满足AVL树的性质,插入成功;
    2.若parent->bf == ±1,说明插入前parent->bf == 0,插入后被调整为±1,此时以parent为根的树的高度增加,需要向上更新;
    3.若parent->bf == ±2,则parent的平衡因子违反平衡树的性质,需要对其进行旋转处理

	bool Insert(const &T val)
	{

		if (m_root == nullptr)
		{
			m_root = new TreeNode<T>(val);
			return true;
		}

		TreeNode<T> * cur = m_root;
		TreeNode<T> *pre = nullptr;

		while (cur)
		{
			if (val < cur->m_data)
			{
				pre = cur;
				cur = cur->m_left;
			}
			else if (val > cur->m_data)
			{
				pre = cur;
				cur = cur->m_right;
			}
			else
			{
				return false;
			}
		}
		cur = new TreeNode<T>(val);
		if (val < pre->m_data)
		{
			pre->m_left = cur;
		}
		else
		{
			pre->m_right = cur;
		}

		cur->m_parent = pre;

		while (pre)
		{
			if (pre->m_left == cur)
			{
				pre->m_bf--;
			}
			else
			{
				pre->m_bf++;
			}

			if (pre->m_bf == 0)
			{
				break;
			}
			else if (pre->m_bf == 1 || pre->m_bf == -1)
			{
				cur = pre;
				pre = pre->m_parent;
			}
			else
			{
				//插入新结点会导致原来的二叉树不平衡,可以总结为以下四种情况进行调整,使二叉树重新平衡
				if (pre->m_bf == 2)
				{
					if (cur->m_bf == 1)//“右右”
					{
						Lround(pre);
					}
					else//“右左”
					{
						RLround(pre);
					}
				}
				else
				{
					if (cur->m_bf == 1)//“左右”
					{
						LRround(pre);
					}
					else//“左左”
					{
						Rround(pre);
					}
				}
				break;
			}
		}
		return true;
	}
2.3 AVL树的旋转

AVL树插入结点的过程中需要进行调整,使得满足AVL树的特性,AVL树的旋转可分为以下四种情况:

  1. 左-左型(右旋)
    左-左型是表示左子树的左孩子插入新的结点,需要进行右旋。
    在这里插入图片描述
void Rround(TreeNode<T> * pre)
	{
		TreeNode<T> * parent = pre->m_parent;
		TreeNode<T> * cur = pre->m_left;

		cur->m_parent = parent;

		if(parent)
		{
			if (parent->m_left == pre)
			{
				parent->m_left = cur;
			}
			else
			{
				parent->m_right = cur;
			}
		}
		else
		{
			m_root = cur;
		}

		pre->m_left = cur->m_right;
		if (cur->m_right)
		{
			cur->m_right->m_parent = pre;
		}

		cur->m_right = pre;
		pre->m_parent = cur;

		cur->m_bf = pre->m_bf = 0;
	}
  1. 右-右型(左旋)
    右-右型是表示右子树的右孩子插入新结点,需要进行左旋。
    在这里插入图片描述
void Lround(TreeNode<T> * pre)
	{
		TreeNode<T> * parent = pre->m_parent;
		TreeNode<T> * cur = pre->m_right;

		//若进行左单旋,可能需要对三个结点进行调整
		cur->m_parent = parent;
		if (parent)//若需要旋转的该结点有父结点
		{
			if (parent->m_left == pre)
			{
				parent->m_left = cur;
			}
			else
			{
				parent->m_right = cur;
			}
		}
		else//若该结点没有父结点,则该结点就是根结点
		{
			m_root = cur;
		}

		//该结点的左孩子会成为原来父结点的右孩子
		pre->m_right = cur->m_left;
		if (cur->m_left)//若该结点原来的左孩子存在,则它的父亲结点会变成pre
		{
			cur->m_left->m_parent = pre;
		}

		cur->m_left = pre;//该结点之前的父结点将会变为该结点的左孩子
		pre->m_parent = cur;//该结点变为原来父亲结点的父结点

		cur->m_bf = pre->m_bf = 0;
	}
  1. 左-右型(先左旋再右旋)
    左-右型表示插入的新结点是左子树的右孩子,需要进行两步操作,先进行左旋,再进行右旋。
    在这里插入图片描述
void LRround(TreeNode<T> * pre)
	{
		TreeNode<T> * left = pre->m_left;
		TreeNode<T> * newroot = left->m_right;

		int flag = newroot->m_bf;

		Lround(pre->m_left);
		Rround(pre);

		if (flag == -1)
		{
			pre->m_bf = 1;
		}
		else
		{
			left->m_bf = -1;
		}
	}
  1. 右-左型(先右旋再左旋)
    右-左型表示插入的新结点是右子树的左孩子,需要进行两步操作,先右旋再左旋。
    在这里插入图片描述
void RLround(TreeNode<T> * pre)
	{
		TreeNode<T> * right = pre->m_right;
		TreeNode<T> * newroot = right->m_left;

		//旋转之前,保存newroot的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子
		int flag = newroot->m_bf;

		Rround(pre->m_right);//先进行右单旋
		Lround(pre);//再进行左单旋

					//判断右左单旋后flag的结果
		if (flag == -1)//当新结点的平衡因子为-1的情况是:在该结点的左孩子插入一个结点,经过右左单旋有的结果
		{
			right->m_bf = 1;
		}
		else//当新结点的平衡因子为 1 的情况是:在该结点的右孩子插入一个结点,经过右左单旋有的结果
		{
			pre->m_bf = -1;
		}
	}

总结:
假如以parent为根的子树不平衡,即parent的平衡因子为2或者-2,分以下情况考虑( pSubL: parent的左孩子,pSubLR: parent左孩子的右孩子)

  1. parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为pSubR
    当pSubR的平衡因子为1时,执行左单旋
    当pSubR的平衡因子为-1时,执行右左双旋
  2. parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为pSubL
    当pSubL的平衡因子为-1是,执行右单旋
    当pSubL的平衡因子为1时,执行左右双旋
    旋转完成后,原parent为根的子树个高度降低,已经平衡,不需要再向上更新。
2.4 AVL树的删除

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不过与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。

bool erase(const T &val)
	{
		if (m_root == nullptr)
		{
			m_root = new TreeNode<T>(val);
			return true;
		}

		TreeNode<T> * cur = m_root;
		TreeNode<T> *pre = nullptr;

		while (cur)
		{
			if (val < cur->m_data)
			{
				pre = cur;
				cur = cur->m_left;
			}
			else if (val > cur->m_data)
			{
				pre = cur;
				cur = cur->m_right;
			}
			else
			{
				break;
			}
		}

		if (cur == nullptr)
		{
			return false;
		}

		if (cur->m_left && cur->m_right)
		{
			TreeNode<T> * cur2 = cur->m_left;

			if (cur2->m_right)
			{
				for (; cur2->m_right; pre2 = cur2, cur2 = cur2->m_right);

				pre2->m_right = cur2->m_left;
				cur2->m_left = cur->m_left;
			}

			cur2->m_right = cur->m_right;

			if (cur->m_data < pre->m_data)
			{
				pre->m_left = cur2;
			}
			else
			{
				pre->m_right = cur2;
			}
			delete cur;
		}
		else if (cur->m_left)
		{
			if (cur->m_data < pre->m_data)
			{
				pre->m_left = cur->m_left;
			}
			else
			{
				pre->m_right = cur->m_left;
			}
			delete cur;
		}
		else
		{
			if (cur->m_data < pre->m_data)
			{
				pre->m_left = cur->m_right;
			}
			else
			{
				pre->m_right = cur->m_right;
			}
			delete cur;
		}
	}

3、AVL树的性能分析

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度为O(log2N)。
但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
应用场景:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,

但一个结构经常修改,AVL树就不太适合,这样的操作红黑树更加合适。
红黑树详解

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AVL树是一种自平衡二叉查找树,它的每个节点都保存了一个平衡因子(balance factor),用于判断是否需要进行旋转操作来保持树的平衡。AVL树平衡因子可以是-1、0或1,当插入或删除节点后,如果某个节点的平衡因子的绝对值大于1,则需要进行旋转操作来保持树的平衡。AVL树的查找、插入和删除操作的时间复杂度都是O(log n)。 以下是一个简单的Python实现AVL树的例子: ```python class AVLNode: def __init__(self, key): self.key = key self.left = None self.right = None self.height = 1 class AVLTree: def __init__(self): self.root = None def insert(self, key): self.root = self._insert(self.root, key) def _insert(self, node, key): if not node: return AVLNode(key) elif key < node.key: node.left = self._insert(node.left, key) else: node.right = self._insert(node.right, key) node.height = 1 + max(self._height(node.left), self._height(node.right)) balance = self._balance(node) if balance > 1 and key < node.left.key: return self._right_rotate(node) if balance < -1 and key > node.right.key: return self._left_rotate(node) if balance > 1 and key > node.left.key: node.left = self._left_rotate(node.left) return self._right_rotate(node) if balance < -1 and key < node.right.key: node.right = self._right_rotate(node.right) return self._left_rotate(node) return node def delete(self, key): self.root = self._delete(self.root, key) def _delete(self, node, key): if not node: return node elif key < node.key: node.left = self._delete(node.left, key) elif key > node.key: node.right = self._delete(node.right, key) else: if not node.left and not node.right: node = None elif not node.left: node = node.right elif not node.right: node = node.left else: temp = self._get_min(node.right) node.key = temp.key node.right = self._delete(node.right, temp.key) if not node: return node node.height = 1 + max(self._height(node.left), self._height(node.right)) balance = self._balance(node) if balance > 1 and self._balance(node.left) >= 0: return self._right_rotate(node) if balance < -1 and self._balance(node.right) <= 0: return self._left_rotate(node) if balance > 1 and self._balance(node.left) < 0: node.left = self._left_rotate(node.left) return self._right_rotate(node) if balance < -1 and self._balance(node.right) > 0: node.right = self._right_rotate(node.right) return self._left_rotate(node) return node def _height(self, node): if not node: return 0 return node.height def _balance(self, node): if not node: return 0 return self._height(node.left) - self._height(node.right) def _left_rotate(self, node): new_root = node.right node.right = new_root.left new_root.left = node node.height = 1 + max(self._height(node.left), self._height(node.right)) new_root.height = 1 + max(self._height(new_root.left), self._height(new_root.right)) return new_root def _right_rotate(self, node): new_root = node.left node.left = new_root.right new_root.right = node node.height = 1 + max(self._height(node.left), self._height(node.right)) new_root.height = 1 + max(self._height(new_root.left), self._height(new_root.right)) return new_root def _get_min(self, node): if not node.left: return node return self._get_min(node.left) def inorder_traversal(self): self._inorder_traversal(self.root) def _inorder_traversal(self, node): if node: self._inorder_traversal(node.left) print(node.key) self._inorder_traversal(node.right) tree = AVLTree() tree.insert(10) tree.insert(20) tree.insert(30) tree.insert(40) tree.insert(50) tree.insert(25) tree.delete(30) tree.inorder_traversal() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值