AVL树复习

Aki 发布于 2022-12-25 336 次阅读


概念、

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

AVL树节点设计、

class AVL_Tree_Node
{
public:

        //这里省略了一些函数没写,比如拷贝构造,析构等等,但是不影响
	AVL_Tree_Node(int data = 0):data(data){}
	AVL_Tree_Node* left = nullptr;     //左孩子节点指针
	AVL_Tree_Node* right = nullptr;    //右孩子节点指针
	AVL_Tree_Node* parent = nullptr;   //父节点指针
	int data = 0;                      //data域
	int64_t balance_value = 0;         //平衡因子
};

旋转核心操作、

AVL树的核心在于任意一个节点,该节点的左右子树的高度差的绝对值小于2!!!定义平衡因子为左子树的高度减去右子树的高度,即该值必须小于2,大于等于2时就要进行旋转来保持平。下面就是两个核心的旋转!!!

(1)左旋转(RR型)

上图添加的节点是60节点,需要调整的是60节点至根节点中第一个不平衡的节点也就是节点20,因为20节点的平衡因子是 -2。

左旋的操作是使用两个指针,一个指针叫做parent指向不平衡的节点即节点20,另一个指针是cur指针,指向parent的右孩子即节点40。这两个指针的指向都是固定的。然后就开始调整指针的指向。

左旋转的条件为 :不平衡节点的平衡因子为-2,且右节点的平衡因子为-1,即 if (parent->balance_value == -2 && cur->balance_value == -1) 。

还有在更改指针指向时我们并不知道cur指向的节点有没有左子节点,parent有没有父节点,和parent是parent父节点的左子节点还是右子节点,都需要做判断!!!

//左旋,把parent结点作为右子节点的左子节点
void _rotate_left_(AVL_Tree_Node* parent)
{
		
	AVL_Tree_Node* cur = parent->right;     //parent的右子节点
	AVL_Tree_Node* pp = parent->parent;     //parent的父节点
	AVL_Tree_Node* cur_left = cur->left;    //cur的左节点

	cur->left = parent;                     //更改指针指向
	parent->parent = cur;                   //更改指针指向
	parent->right = cur_left;               //更改指针指向

	if (cur_left)  //判断cur有没有左子节点
	{
		cur_left->parent = parent;
	}

	if (pp)        //有父节点,不是根节点的情况
	{
		if (pp->left == parent) //判断parent是父节点的左子节点还是右子节点
		{
			pp->left = cur;
		}
		else
		{
			pp->right = cur;
		}
	}
	else //根节点的情况,需要更新根节点的信息
	{
		_root = cur;
	}
        cur->parent = pp;

        //更新parent和cur的平衡因子信息
	parent->balance_value = _height_(parent->left) - _height_(parent->right);
	cur->balance_value = _height_(cur->left) - _height_(cur->right);
}

(2)右旋转(LL型)

右旋的逻辑和操作都和左旋差不多。

平衡破坏的节点是30,因为平衡因子为2。parent指针指向从插入节点10到根节点路径上第一个平衡破坏的节点也就是30,cur指针指向parent节点的左子节点,也就是20。然后开始调整指针。

右旋的条件为:if (parent->balance_value == 2 && cur->balance_value == 1)

还有在更改指针指向时我们并不知道cur指向的节点有没有左子节点,parent有没有父节点,和parent是parent父节点的左子节点还是右子节点,都需要做判断!!!

//右旋,把parent作为左子结点cur的右子结点
void _rotate_right_(AVL_Tree_Node* parent)
{

	AVL_Tree_Node* cur = parent->left;
	AVL_Tree_Node* pp = parent->parent;
	AVL_Tree_Node* cur_right = cur->right;

	cur->right = parent;
	parent->parent = cur;
	parent->left = cur_right;
	if (cur_right)
	{
		cur_right->parent = parent;
	}

	if (pp)
	{
		if (pp->left == parent)
		{
			pp->left = cur;
		}
		else
		{
			pp->right = cur;
		}
	}
	else
	{
		_root = cur;
	}
	cur->parent = pp;

	parent->balance_value = _height_(parent->left) - _height_(parent->right);
	cur->balance_value = _height_(cur->left) - _height_(cur->right);
}

(3)先左旋后右旋(LR型)

此时,破坏平衡的节点为30,平衡因子为2,parent指针指向30节点,cur指针指向30的左子节点20,20节点的平衡因子为-1。这种情况只旋转一次是不能平衡的,需要旋转两次。具体步骤是,先对cur指针左旋转,然后对parent指针右旋转。

旋转条件是: if (parent->balance_value == 2 && cur->balance_value == -1)

(4)先右旋后左旋(RL型)

旋转条件是:if (parent->balance_value == -2 && cur->balance_value == 1)

parent指针指向节点30,cur指针指向节点40,然后分别对40节点进行右旋转,对30节点进行左旋转。

其实说白了双旋转就是左右旋转的组合而已,只要写好了最重要的左右旋转,双旋转完全没问题。我在写代码和测试代码的时候,出现了一个问题我弄了好久才解决,问题的原因在于旋转时少写了一个指针连接。。。折腾了我好久,所以写代码时一定要仔细和细心!!!

删除节点核心操作、

除了旋转操作,还有删除操作也是核心内容。

AVL 树的删除操作相对复杂一些,需要删除节点后回溯调整平衡到根节点。

AVL树删除一个节点,最坏情况下需要logn次旋转才能恢复平衡性质(需要回溯到根节点)。

AVL树的删除情况比较复杂,我现在还写不出没有bug的删除方法。。。。。。不过有一种取巧的方法,可以规避删除。具体是在树节点结构体中加入一个标识符来表示该节点是否存在,在使用树节点时要判断该标识符的真假,进而才能够使用节点。

AVL树的代码实现、

下面的代码删除操作存在bug,但是我现在看不出哪里有问题所以解决不了。。。其他操作没问题

class AVL_Tree_Node
{
public:
	AVL_Tree_Node(int data = 0):data(data){}
	AVL_Tree_Node* left = nullptr;
	AVL_Tree_Node* right = nullptr;
	AVL_Tree_Node* parent = nullptr;
	int data = 0;
	int64_t balance_value = 0;
};


class AVL_Tree
{
public:
	AVL_Tree() :sizes(0), _root(nullptr) {}

	~AVL_Tree()noexcept
	{
		clear();
	}


        //插入操作,没问题
	void insert(int data)
	{
                //判断有没有根节点
		if (_root == nullptr)
		{
			_root = new AVL_Tree_Node{ data };
			++sizes;
			return;
		}


                //判断是不是重复值
		if (find(data) != nullptr)
		{
			return;
		}

                //寻找插入点
		AVL_Tree_Node* pos = _root;
		AVL_Tree_Node* parent = nullptr;
		AVL_Tree_Node* new_node = new AVL_Tree_Node{ data };
		while (pos)
		{
			parent = pos;
			if (data > pos->data)
			{
				pos = pos->right;
			}
			else
			{
				pos = pos->left;
			}
		}


                //插入
		if (parent->data < data)
		{
			parent->right = new_node;
		}
		else  
		{
			parent->left = new_node;
		}
		new_node->parent = parent;
		++sizes;


                //调平衡
		_rotate_(parent, new_node);
	}

        //中序遍历
	void in_order()const
	{
		_in_order_(_root);
	}

        //删除节点操作,有bug!!!具体在哪里我暂时看不出,目前解决不了
	void remove(int data)
	{
		if (_root == nullptr || data==_root->data)
		{
			return;
		}

		stack<AVL_Tree_Node*>s{};
		AVL_Tree_Node* pos = _root;
		AVL_Tree_Node* parent = nullptr;
		while (pos)
		{
			if (pos->data == data)
			{
				break;
			}
			parent = pos;
			s.push(parent);
			if (data > pos->data)
			{
				pos = pos->right;
			}
			else if (data < pos->data)
			{
				pos = pos->left;
			}
		}

		if (pos == nullptr)
		{
			return;
		}

		if (!pos->left && !pos->right)
		{
			if (parent->left == pos)
			{
				parent->left = nullptr;
			}
			else
			{
				parent->right = nullptr;
			}
			delete pos;
			--sizes;
			parent->balance_value = _height_(parent->left) - _height_(parent->right);
		}
		else if (pos->left && pos->right)
		{
			AVL_Tree_Node* lmax = pos->left;
			AVL_Tree_Node* lmax_parent = pos;
			s.push(pos);
	                s.push(pos->right);
			while (lmax->right)
			{
				lmax_parent = lmax;
				s.push(lmax_parent);
				lmax = lmax->right;
			}
			pos->data = lmax->data;
			if (pos->left->right == nullptr)
			{

				lmax_parent->left = lmax->left;
			}
			else
			{
				lmax_parent->right = lmax->left;
			}
                        if (lmax->left)
                        {
                                lmax->left->parent = lmax_parent;
                        }
			delete lmax;
			--sizes;
			lmax_parent->balance_value = _height_(lmax_parent->left) - _height_(lmax_parent->right);
		}
		else if (pos->left && !pos->right)
		{
			if (parent->left == pos)
			{
				parent->left = pos->left;
			}
			else
			{
				parent->right = pos->left;
			}
			pos->left->parent = parent;
			delete pos;
			--sizes;
		}
		else if (pos->right && !pos->left)
		{
			if (parent->left == pos)
			{
				parent->left = pos->right;
			}
			else
			{
				parent->right = pos->right;
			}
			pos->right->parent = parent;
			delete pos;
			--sizes;
		}

		AVL_Tree_Node* cur = nullptr;
		while (!s.empty())
		{
			cur = s.top();
			s.pop();
			_rotate_(cur->parent, cur);
		}
	}

        //BFS
	void BFS()
	{
		if (_root == nullptr)
		{
			return;
		}
		queue<AVL_Tree_Node*> q{};
		q.push(_root);
		AVL_Tree_Node* tmp = nullptr;
		while (!q.empty())
		{
			tmp = q.front();
			q.pop();
			cout << "value = " << tmp->data << " balance value = " << tmp->balance_value << endl;
			if (tmp->left)
			{
				q.push(tmp->left);
			}
			if (tmp->right)
			{
				q.push(tmp->right);
			}
		}
	}

        //DFS
	void DFS()const
	{
		if (_root == nullptr)
		{
			return;
		}
		stack<AVL_Tree_Node*>s{};
		s.push(_root);
		AVL_Tree_Node* tmp = nullptr;
		while (!s.empty())
		{
			tmp = s.top();
			s.pop();
			cout << tmp->data << "  ";
			if (tmp->left)
			{
				s.push(tmp->left);
			}
			if (tmp->right)
			{
				s.push(tmp->right);
			}
		}
	}

        //查找操作
	AVL_Tree_Node* find(int data)const
	{
		AVL_Tree_Node* pos = _root;
		while (pos)
		{
			if (data > pos->data)
			{
				pos = pos->right;
			}
			else if (data < pos->data)
			{
				pos = pos->left;
			}
			else
			{
				return pos;
			}
		}
		return nullptr;
	}
	

        //树高度
	int64_t height()const
	{
		return _height_(_root);
	}

        //清空操作
	void clear()
	{
		_destroy_(_root);
		sizes = 0;
		_root = nullptr;
	}

protected:

	int64_t _height_(AVL_Tree_Node* _p)const
	{
		if (_p == nullptr)
		{
			return 0;
		}
		int64_t lh = 0, rh = 0;
		lh = _height_(_p->left);
		rh = _height_(_p->right);
		return lh > rh ? ++lh : ++rh;
	}

	void _destroy_(AVL_Tree_Node* _p)
	{
		if (_p == nullptr)
		{
			return;
		}
		_destroy_(_p->left);
		_destroy_(_p->right);
		delete _p;
	}

	//右旋,把parent作为左子结点的右结点
	void _rotate_right_(AVL_Tree_Node* parent)
	{
		//右旋转时并不知道cur有没有右结点,也不知道parent是parent的parent左节点还是右结点
		//都需要进行判断

		AVL_Tree_Node* cur = parent->left;
		AVL_Tree_Node* pp = parent->parent;
		AVL_Tree_Node* cur_right = cur->right;

		cur->right = parent;
		parent->parent = cur;
		parent->left = cur_right;
		if (cur_right)
		{
			cur_right->parent = parent;
		}

		if (pp)
		{
			if (pp->left == parent)
			{
				pp->left = cur;
			}
			else
			{
				pp->right = cur;
			}
		}
		else
		{
			_root = cur;
		}
		cur->parent = pp;

		parent->balance_value = _height_(parent->left) - _height_(parent->right);
		cur->balance_value = _height_(cur->left) - _height_(cur->right);
	}

	//左旋,把parent结点作为右子结点的左子节点
	void _rotate_left_(AVL_Tree_Node* parent)
	{
		AVL_Tree_Node* cur = parent->right;   //右子节点
		AVL_Tree_Node* pp = parent->parent;
		AVL_Tree_Node* cur_left = cur->left;    //右子节点的左节点
		cur->left = parent;
		parent->parent = cur;
		parent->right = cur_left;
		if (cur_left)
		{
			cur_left->parent = parent;
		}
		if (pp)
		{
			if (pp->left == parent)
			{
				pp->left = cur;
			}
			else
			{
				pp->right = cur;
			}
		}
		else
		{
			_root = cur;
		}
		cur->parent = pp;
		parent->balance_value = _height_(parent->left) - _height_(parent->right);
		cur->balance_value = _height_(cur->left) - _height_(cur->right);
	}

	//中序遍历
	void _in_order_(AVL_Tree_Node* _p)const
	{
		if (_p == nullptr)
		{
			return;
		}
		_in_order_(_p->left);
		cout << _p->data << "   ";
		_in_order_(_p->right);
	}

	//旋转操作
	void _rotate_(AVL_Tree_Node* parent, AVL_Tree_Node* cur)
	{
		while (parent && cur)
		{
			//更新节点平衡因子
			parent->balance_value = _height_(parent->left) - _height_(parent->right);

			//更新后的平衡因子为0的话,说明整棵树还是平衡的,直接退出
			if (parent->balance_value == 0)
			{
				break;
			}

			//变成1或者-1的话说明有改变,继续循环下去
			else if (parent->balance_value == 1 || parent->balance_value == -1)
			{
				cur = parent;
				parent = parent->parent;
				continue;
			}

			//右旋转
			if (parent->balance_value == 2 && cur->balance_value == 1)
			{
				_rotate_right_(parent);
				break;
			}

			//左旋,右边高度大于左边高度
			else if (parent->balance_value == -2 && cur->balance_value == -1)
			{
				_rotate_left_(parent);
				break;
			}

			//先左旋后右旋
			else if (parent->balance_value == 2 && cur->balance_value == -1)
			{
				_rotate_left_(cur);
				_rotate_right_(parent);
				break;
			}

			//先右旋后左旋
			else if (parent->balance_value == -2 && cur->balance_value == 1)
			{
				_rotate_right_(cur);
				_rotate_left_(parent);
				break;
			}

		}

                
                //我发现的两个特殊情况,可能还有其他的特殊情况我目前没有发现
		if (_root->balance_value == 2 && _root->right == nullptr)
		{
			_rotate_right_(_root);
		}
		else if (_root->balance_value == 2 && _root->left == nullptr)
		{
			_rotate_left_(_root);
		}
	}


private:
	size_t sizes;
	AVL_Tree_Node* _root;
};