『数据结构』AVL树

什么是AVL树?

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


一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树

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

在这里插入图片描述
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在log2(N),搜索时的时间复杂度为O(log2(N))


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

AVL树结点的定义

// AVL树结点
template<class K, class V>
struct AVLTreeNode{

	AVLTreeNode(const pair<K, V>& kv)
		: _bf(0)
		, _kv(kv)
		, _parent(nullptr)
		, _left(nullptr)
		, _right(nullptr)
	{}

	// 平衡因子
	int _bf;
	pair<K, V> _kv;
	// 该节点双亲
	AVLTreeNode<K, V>* _parent;
	// 该节点左孩子
	AVLTreeNode<K, V>* _left;
	// 该节点右孩子
	AVLTreeNode<K, V>* _right;
};

AVL树的插入

从上面AVL数结点的定义,可以看出,AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成二叉搜索树。那么AVL树的插入过程可以分为以下步骤

  1. 按照二叉搜索树的方式将新节点插入到AVL树中
  2. 新节点插入后,AVL树的平衡性可能会遭到破坏,此时,就需要调整平衡因子,并检测是否破坏了AVL树的平衡性
  3. cur插入后,cur的parent的平衡因子一定要调整,在插入之前,cur的parent的平衡因子有三种情况:-1,0,1。分以下两种情况
    如果cur插入到parent的左侧,只需要给parent的平衡因子-1即可。
    如果cur插入到parent的右侧,只需要给parent的平衡因子+1即可。
  4. 此时,parent的平衡因子可能有三种情况:0,±1,±2
    如果parent的平衡因子为0,说明插入之前parent的平衡因子为±1插入后被更新为0,此时满足AVL树的性质,插入成功
    如果parent的平衡因子为±1,说明插入之前parent的平衡因子一定为0插入后被更新为±1此时以parent为根的树的高度增加,需要继续向上更新
    如果parent的平衡因子为±2,则parent的平衡因子违反平衡树的性质,需要对其进行旋转处理
// 插入
bool insert(const pair<K, V>& kv){
	// 空树,直接插入
	if (_root == nullptr){
		_root = new Node(kv);
		_root->bf = 0;

		return true;
	}

	// 保存父指针
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur != nullptr){
		// 大于根节点,右树找
		if (kv.first > cur->_kv.first){
			parent = cur;
			cur = cur->_right;
		}
		// 小于根节点,左树找
		else if (kv.first < cur->_kv.first){
			parent = cur;
			cur = cur->_left;
		}
		// 已经存在,插入失败
		else{
			return false;
		}
	}

	// 找到插入位置
	cur = new Node(kv);
	// 新插入结点比parent大,插入到parent右树
	if (kv.first > parent->_kv.first){
		parent->_right = cur;
		cur->_parent = parent;
	}
	// 新插入结点比parent小,插入到parent左树
	else{
		parent->_left = cur;
		cur->_parent = parent;
	}

	// 调整平衡因子
	while (parent){
		// 新插入在parent的右树,平衡因子加1
		if (cur == parent->_right){
			++parent->_bf;
		}
		// 新插入在parent的左树,平衡因子减1
		else{
			--parent->_bf;
		}

		// 平衡因子为0,高度无影响
		if (parent->_bf == 0){
			break;
		}
		// 平衡因子为1,高度变了,向上更新
		else if (abs(parent->_bf) == 1){
			cur = parent;
			parent = parent->_parent;
		}
		// 平衡因子绝对值为2,不平衡,旋转调整
		else if (abs(parent->_bf) == 2){
			// 平衡因子为2,右树变高
			if (parent->_bf == 2){
				// 当前为1,新节点插入在较高右子树的右侧,左单旋
				if (cur->_bf == 1){
					leftRotate(parent);
				}
				// 当前为-1,新节点插入在较高右子树的左侧,右左单旋
				else if (cur->_bf == -1){
					rightLeftRotate(parent);
				}
			}
			// 平衡因子为-2,左树变高
			else if (parent->_bf == -2){
				// 当前为-1,新节点插入在较高左子树的左侧,右单旋
				if (cur->_bf == -1){
					rightRotate(parent);
				}
				// 当前为1,新节点插入在较高左子树的右侧,左右单旋
				else if (cur->_bf == 1){
					rightLeftRotate(parent);
				}
			}

			break;
		}
		// 平衡因子为其他值,出错
		else{
			assert(false);
		}
	}
}

AVL树的旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化根据结点插入位置的不同,AVL树的旋转分为四种

  1. 新节点插入较高左子树的左侧–左左:右单旋
    在这里插入图片描述
    上图在插入前,AVL树是平衡的,新节点插入到10的左子树中,10的左子树高度增加1,导致以20为根的二叉树不平衡,要想让20为根的二叉树平衡,只能将20的左子树高度减少一层,20的右子树增加一层
    进行右单旋将左子树往上提,这样20转下来,因为20比10大,只能将其放在10的右树,而如果10有右树,右树的根一定大于10小于20,因此将其放在20的左树,旋转完成后,更新结点的平衡因子即可

注意事项

  • 10结点的右孩子可能存在,也可能不存在
  • 20可能是根节点,也可能是子树
    如果是根节点,旋转完成后,要更新根节点
    如果是子树,可能是某个节点的左子树,也可能是右子树
// 右单旋
void rightRotate(Node* parent){
	// 20结点的左树10
	Node* subL = parent->_left;
	// 20结点的左树的右树b
	Node* subLR = parent->_left->_right;

	// 将b挂到20结点的左树
	parent->_left = subLR;

	// 如果b不为空,将b的双亲结点指向20结点
	if (subLR){
		subLR->_parent = parent;
	}

	// 将20结点挂到10结点的右树
	subL->_right = parent;

	// pp保存20结点的双亲结点
	Node* pp = parent->_parent;
	// 将20结点的双亲结点改为10结点
	parent->_parent = subL;

	// 如果20结点是根节点
	if (pp == nullptr){
		// 10结点设为根节点
		_root = subL;
		_root->_parent = nullptr;
	}
	// 20结点不是根节点
	else{
		// 20结点是其双亲结点的左子树
		if (pp->_left == parent){
			pp->_left = subL;
		}
		// 20结点是其双亲结点的右子树
		else{
			pp->_right = subL;
		}

		// 10结点的双亲结点改为20结点的双亲结点
		subL->_parent = pp;
	}

	// 旋转完成后,平衡因子更新为0
	parent->_bf = subL->_bf = 0;
}

  1. 新节点插入较高右子树的右侧–右右:左单旋
    在这里插入图片描述
    同样,上图在插入前是平衡的,新节点插入到20节点的右树,导致20节点的右树高度增加1,从而导致20节点的平衡因子增加到1,10节点的平衡因子增加到2,导致不平衡,需要进行调整,使其达到平衡
    进行左单旋调整将10节点压下去,因为20节点的左树都是大于10,小于20的,所以可以将20结点的左树调整到10节点的右树,同时将10节点调整为20节点的左树。即可达到平衡,旋转完成后,更新平衡因子

注意事项

  • 20节点的左孩子可能存在,也可能不存在
  • 10节点可能是根节点,也可能是子树
    如果10节点是根节点,旋转完成后,要更新根节点
    如果是子树,可能是某个节点的左子树,也可能是右子树
// 左单旋
void leftRotate(Node* parent){
	// 10结点的右树20
	Node* subR = parent->_right;
	// 10结点的右树的左树b
	Node* subRL = parent->_right->_left;

	// 将b设置为10结点的右树
	parent->_right = subRL;

	// 如果b非空,将b的父亲设置为10结点
	if (subRL){
		subRL->_parent = parent;
	}

	// 保存10结点的父结点
	Node* pp = parent->_parent;

	// 将10结点的父结点设置为结点20
	parent->_parent = subR;

	// 将10结点设置为结点20的左树
	subR->_left = parent;

	// 10结点是根节点
	if (parent == _root){
		// 20结点设为根节点
		_root = subR;
		_root->_parent = nullptr;
	}
	// 10结点不是根结点
	else{
		// 10结点是其父结点的左树
		if (parent == pp->_left){
			pp->_left = subR;
			subR->_parent = pp;
		}
		// 10结点是其父结点的右树
		else{
			pp->_right = subR;
			subR->_parent = pp;
		}
	}

	// 调整平衡因子
	parent->_bf = subR->_bf = 0;
}

  1. 新节点插入较高左子树的右侧–左右:先左单旋再右单旋
    在这里插入图片描述
    上图在插入前是平衡的,但是20结点的左树插入之后,导致20结点的高度增加1,从而影响10结点,最终影响30结点,30结点平衡因子为-2,不平衡,需要调整。
    进行双旋,先进行左单旋,再进行右单旋。将20结点的左树挂到10结点的右树,再将10结点挂到20结点的左树,完成左单旋;然后将20结点的右树挂到30结点的左树。在将30结点挂到20结点的右树,完成右单旋,树达到平衡

注意事项

  • 这里要单独考虑平衡因子,如果不单独考虑平衡因子,左右单旋后,三个结点的平衡因子都是0,从图上看,三个结点的平衡因子显然不全为0
  • 上述情况,如果新插入的结点在b,则双旋之后,30结点的平衡因子应该为1,其他两个节点平衡因子为0,因为插入在b,c的高度为h-1,双旋后,c会成为30结点的左树,从而导致30结点的平衡因子为1
  • 如果新插入的结点在c,则双旋之后,10结点的平衡因子为-1,其他两个节点的平衡因子为0,因为插入在c,b的高度为h-1,双旋之后,b会成为10结点的右树,从而导致10结点的平衡因子为-1
// 左右双旋
void leftRightRotate(Node* parent){
	// 保存20结点的平衡因子,旋转完成后
	// 根据该平衡因子对其他平衡因子进行调整
	int bf = parent->_left->_right->_bf;

	// 对10结点所在子树进行左单旋
	leftRotate(parent->_left);

	// 对30结点所在树进行右单旋
	rightRotate(parent);

	// 插入后20结点平衡因子为1
	// 说明新插入结点在20结点的右树,说明20结点左树高度较低
	// 左右旋之后,20结点的左树成为10结点的右树
	// 从而10结点的平衡因子为-1
	if (bf == 1){
		parent->_left->_bf = -1;
	}
	// 插入后20结点平衡因子为-1
	// 说明新插入结点在20结点的左树
	// 20结点的右树高度较低
	// 左右旋之后,20结点的右树成为30结点的左树
	// 从而30结点的平衡因子为1
	else if (bf == -1){
		parent->_bf = 1;
	}
}

  1. 新节点插入较高右子树的左侧–右左:先右单旋再左单旋
    在这里插入图片描述同样,上图插入在c,插入之后20结点右子树高度增加,从而向上影响,直到影响到10结点,使得10结点平衡因子为2,导致不平衡,双旋进行调整。
    首先进行右单旋,将c挂到30结点的左,再将30加点挂到20结点的有,完成右单旋;然后进行左单旋,将b挂到10结点的右,然后将10结点挂到20结点的左,完成左单旋

注意事项

  • 这里要注意平衡因子需要单独处理
  • 如果新节点插入到b,则20的右树c的高度较低,为h-1,双旋之后,c会成为30结点的左树,从而导致30结点的平衡因子为1,其他两个节点平衡因子为0
  • 如果新节点插入到c,则20结点的左树b的高度较低,为h-1,双旋之后,b会成为10结点的右树,从而导致10结点的平衡因子为-1
// 右左旋
void rightLeftRotate(Node* parent){
	// 首先保存插入之后20结点的平衡因子
	int bf = parent->_right->_left->_bf;

	// 进行右单旋
	rightRotate(parent->_right);

	// 进行左单旋
	leftRotate(parent);

	// 新节点插入在c
	// b的高度较低为h-1
	// 双旋之后,b会成为10结点的右树
	// 因此10结点的平衡因子为-1
	if (bf == 1){
		parent->_bf = -1;
	}
	// 新节点插入在b
	// c的高度较低为h-1
	// 双旋之后,c会成为30结点的左树
	// 因此30结点的平衡因子为1
	else if (bf == -1){
		parent->_right->_bf = 1;
	}
}

AVL树旋转总结
假如以parent为根的子树不平衡,即parent的平衡因子为2或-2,分以下情况考虑:

  1. parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为subR
    当subR的平衡因子为1时,执行左单旋
    当subR的平衡因子为-1时,执行右左双旋
  2. parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为subL
    当subL的平衡因子为-1时,执行右单旋
    当subL的平衡因子为1时,执行左右双旋

旋转完成后,原parent为根的子树高度降低,已经平衡,不需要再向上更新

AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡的限制,所以要验证一棵树是否为AVL树,只需要验证两点即可:

  1. 这棵树是否为二叉搜索树
  2. 这棵树是否平衡

如果两个条件都满足就说明是AVL树


首先来验证其是否为二叉搜索树
中序遍历结果为有序即为二叉搜索树

// 中序
void _inorder(Node* root){
	_inorder(root->_left);
	cout << root->_kv.first << " ";
	_inorder(root->_right);
}

// 中序遍历
void inorder(){
	_inOrder(_root);
	cout << endl;
}

验证是否平衡

// 计算树的高度
int _height(Node* root){
	if (root == nullptr){
		return 0;
	}

	if (root->_left == nullptr && root->_right == nullptr){
		return 1;
	}

	// 左树高度
	int left_height = _height(root->_left) + 1;
	// 右树高度
	int right_height = _height(root->_right) + 1;

	// 左树右树中高度的最大值为这棵树的高度
	return left_height > right_height ? left_height : right_height;
}

// 是否平衡
bool _isBalance(Node* root){
	// 空树是平衡的
	if (root == nullptr){
		return true;
	}

	// 计算平衡因子
	int left_height = _height(root->_left);
	int right_height = _height(root->_right);
	int bf = right_height - left_height;

	// 计算出的平衡因子和root的平衡因子不同
	// 或者平衡因子绝对值超过1,不平衡
	if (bf != root->_bf || abs(bf) > 1){
		return false;
	}

	// root的左树和右树是否平衡
	return _isBalance(root->_left) && _isBalance(root->_right);
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
AVL树是一种自平衡二叉搜索树,它的平衡因子(左子树高度减去右子树高度)在任何时候都是-1、0或1。当插入或删除节点后,如果AVL树失去平衡,则需要通过旋转来重新平衡它。 在Python中实现AVL树,可以使用节点类和AVL树类。节点类包括节点值、左子树、右子树、高度和平衡因子等属性。AVL树类包括根节点、插入节点、删除节点、旋转方法和平衡方法等方法。 下面是一个简单的Python实现AVL树的代码示例: ```python class Node: def __init__(self, val): self.val = val self.left = None self.right = None self.height = 1 self.balance = 0 class AVLTree: def __init__(self): self.root = None def insert(self, val): self.root = self._insert(self.root, val) def _insert(self, node, val): if not node: return Node(val) if val < node.val: node.left = self._insert(node.left, val) else: node.right = self._insert(node.right, val) node.height = 1 + max(self.get_height(node.left), self.get_height(node.right)) node.balance = self.get_balance(node) if node.balance > 1 and val < node.left.val: return self.right_rotate(node) if node.balance < -1 and val > node.right.val: return self.left_rotate(node) if node.balance > 1 and val > node.left.val: node.left = self.left_rotate(node.left) return self.right_rotate(node) if node.balance < -1 and val < node.right.val: node.right = self.right_rotate(node.right) return self.left_rotate(node) return node def delete(self, val): self.root = self._delete(self.root, val) def _delete(self, node, val): if not node: return node if val < node.val: node.left = self._delete(node.left, val) elif val > node.val: node.right = self._delete(node.right, val) else: if not node.left or not node.right: temp = node.left if node.left else node.right if not temp: node = None else: node = temp else: temp = self.get_min(node.right) node.val = temp.val node.right = self._delete(node.right, temp.val) if not node: return node node.height = 1 + max(self.get_height(node.left), self.get_height(node.right)) node.balance = self.get_balance(node) if node.balance > 1 and self.get_balance(node.left) >= 0: return self.right_rotate(node) if node.balance < -1 and self.get_balance(node.right) <= 0: return self.left_rotate(node) if node.balance > 1 and self.get_balance(node.left) < 0: node.left = self.left_rotate(node.left) return self.right_rotate(node) if node.balance < -1 and self.get_balance(node.right) > 0: node.right = self.right_rotate(node.right) return self.left_rotate(node) return node def right_rotate(self, node): left_child = node.left node.left = left_child.right left_child.right = node node.height = 1 + max(self.get_height(node.left), self.get_height(node.right)) left_child.height = 1 + max(self.get_height(left_child.left), self.get_height(left_child.right)) node.balance = self.get_balance(node) left_child.balance = self.get_balance(left_child) return left_child def left_rotate(self, node): right_child = node.right node.right = right_child.left right_child.left = node node.height = 1 + max(self.get_height(node.left), self.get_height(node.right)) right_child.height = 1 + max(self.get_height(right_child.left), self.get_height(right_child.right)) node.balance = self.get_balance(node) right_child.balance = self.get_balance(right_child) return right_child def get_height(self, node): if not node: return 0 return node.height def get_balance(self, node): if not node: return 0 return self.get_height(node.left) - self.get_height(node.right) def get_min(self, node): while node.left: node = node.left return node ``` 这个实现包括插入、删除、旋转和平衡等基本操作。你可以按需调用这些方法,来实现你的具体需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值