AVL树的插入

131 阅读4分钟

AVL树的概念

对于map和set,它们的底层实际上是由二叉搜索树来构建的,但是对于普通的二叉搜索树而言,当插入的节点是有序的时候,二叉搜索树就会退化成单只树。这样查找的效率大大降低,AVL树就应运而生。

对于AVL树,它能保证插入节点之后左右子树的高度相差不超过1.这样降低树的高度从而提升查找的效率。对于每一个节点他们都有平衡因子为左右子树的差,来保证高度。

节点

template<class T>

struct AVLTreeNode

{

AVLTreeNode(const T& data)

: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)

, _data(data), _bf(0)

{}

AVLTreeNode<T>* _pLeft; // 该节点的左孩子

AVLTreeNode<T>* _pRight; // 该节点的右孩子

AVLTreeNode<T>* _pParent; // 该节点的双亲

T _data;

int _bf; // 该节点的平衡因子

};

上面是AVL树的节点的构成。

节点的插入

AVL树的插入分为两步

  1. 按照二叉搜索树的方式插入节点
  2. 调整平衡因子

插入一个节点名字叫做cur,在插入之前parent的平衡因子为1,-1,0那么插入后,cur的父节点的平衡因子可能为三种情况

1.平衡因子为0

这种方式最省事,直接按照寻常方式进行插入即可

2.平衡因子为正负1

说明插入之前parent的平衡因子为0,插入后被更新。这时以parent为根的树的高度增加,要向上进行调整。

3.平衡因子为正负2

说明插入之前为正负1,插入之后平衡因子为正负2,不满足平衡的条件,所以插入的时候要对其进行旋转处理。

bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		// 控制平衡
		// 1、更新平衡因子 -- 新增节点到根节点的祖先路径
		// 2、出现异常平衡因子,那么需要旋转平衡处理
		while (parent)
		{
			if (cur == parent->_left)
				parent->_bf--;
			else
				parent->_bf++;

			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				// 继续往上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				// 旋转处理
				// 右单旋
				if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == 1) // 左单旋
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else
				{
					assert(false);
				}

				break;
			}
			else
			{
				// 说明插入更新平衡因子之前,树中平衡因子就有问题了
				assert(false);
			}
		}

		return true;
	}
        

单独说一说如何旋转

右单旋

image.png 当新节点插入较高子树的左侧的时候就要进行右单旋,在这里,插入之后60的平衡因子是-2,所以我们进行旋转,将30的右树成为60的左树,60成为30的右树。 这里的操作是把30和b先记录下来,实现连接之后,记住恢复每一个节点的三叉链。同时要注意60是不是根节点,这里把60的父节点记录下来,连到30上,还要恢复三叉链。

void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		Node* parentParent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (parent == _root)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
				parentParent->_left = subL;
			else
				parentParent->_right = subL;

			subL->_parent = parentParent;
		}

		subL->_bf = parent->_bf = 0;
	}

左单旋

新节点插入较高右子树的右侧

image.png 这里的操作和上面差不多,要注意的事项实际上和上面差不多。注意三叉链的恢复,还要注意先保存30的父节点。

void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
		{
			subRL->_parent = parent;
		}

		Node* parentParent = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;

		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
				parentParent->_left = subR;
			else
				parentParent->_right = subR;
			subR->_parent = parentParent;
		}

		subR->_bf = parent->_bf = 0;
	}

左右双旋

新节点插入较高子树的右侧

image.png

void RotateLR(Node* parent)
	{
		// ...
		RotateL(parent->_left);
		RotateR(parent);
		// ...
	}

右左双旋

新节点插入较高右子树的左侧

image.png

    void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(parent->_right);
		RotateL(parent);

		if (bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

**总结:

假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR

当pSubR的平衡因子为1时,执行左单旋

当pSubR的平衡因子为-1时,执行右左双旋

  1. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL

当pSubL的平衡因子为-1是,执行右单旋

当pSubL的平衡因子为1时,执行左右双旋

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