C++数据结构(上):模拟实现AVL

 "我们无法一直活在过去,亦如我们从未能挽救死亡"


(1)AVL树:

概念:平衡搜索二叉树。

性质:

①根节点的左右子树 是AVL树

②左右子树的高度的差 不超过1

③比根节点大的 插在根节点的右边。 小的 在左边。


(2)AVL树模拟实现: 

 关于AVL树概念上的东西,多讲无用。我们现在直接来实现。

① 结构框架:

注:这里实现的是key值版。

//AVL树的 节点
template<class T>
struct AVLTreeNode
{
	//节点指针
	AVLTreeNode<T>* _left;
	AVLTreeNode<T>* _right;
	AVLTreeNode<T>* _parent;
	//节点数值
	T _key;  

	//为了调节平衡 这里引入平衡因子;
	int _bf;

	//构造
	AVLTreeNode(const T& key)
		:_left(nullptr),
		: _right(nullptr),
		_parent(nullptr),
		_key(key),
		_bf(0)      //任何新插入的 节点 平衡因子都默认设置为0
	{}

};

template<class K>
class AVLTree
{
	typedef AVLTreeNode<T> Node;

	AVLTree()
		:_root(nullptr)
	{}

private:
	Node* _root;
};

对于AVL树而言,显然难点在插入的部分。所以这留到最后讲。那么这部分进行相关功能的实现。

 ②查找+中序遍历:

	Node* find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)
			{
				cur = cur->_right;
			}
			else if(key < cur->_key)
			{
				cur = cur->_left;
			}
			else
			{
				//找到了
				return cur;
			}
		}
		return nullptr;
	}


	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_key << endl;
		_InOrder(root->_right);
	}

	void InOrder()
	{
		_InOrder(_root);
	}

③检查平衡+析构:

	
	void _Destory(Node* root)
	{
		if (root == nullptr)
			return;
		_Destory(root->_left);
		_Destory(root->_right);
		delete root;
	}

     ~AVLTree()
	{
		_Destory(_root);
		_root =nullptr;
	} 


    int TreeHight(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftdepth = TreeHight(root->_left);
		int rightdepth = TreeHight(root->_right);

		//返回 左右子树 大的那个
		return leftdepth > rightdepth ? leftdepth + 1 : rightdepth + 1;
	}

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

		int Maxleft = TreeHight(_root->_left);
		int Maxright = TreeHight(_root->_right);

		return abs(Maxleft - Maxright) < 2
			&& _isBalance(root->_left)
			&& _isBalance(root->_right);
	}

	bool _isBalance()
	{
         return _isBalance(_root);
	}

 关于如何理解检查这棵树是否平衡的函数,也就不多赘述。因为在前面二叉树篇讲过。

下面就进入重头戏~


④AVL的插入;

        

	//如果是第一次插入 直接去做根 root
		if (_root == nullptr)
		{
			_root = new Node(k);
			return true;
		}

		//不是第一个值
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)  //这里和find的思想一样 
		{
			if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//如果走到这里 说明 已经插入了和key 一样的数
				return true;
			}
		}

		//cur 走到空的位置了
		cur = new Node(key);
		Node* newnode = cur;

		//更新与parent的关系
		if (cur->_key  > parent->_key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		//注意我们设计的是三叉连 结构
		//cur 还有反过来 指向parent
		cur->_parent = parent; 

 

任何一次对树数据的操作,都可能造成对树结构的破坏。

所以,为了 保护树的结构。我们需要根据不同情况,对树进行位置的更换

(1)平衡因子:

 我们让在根节点 右插入对 _bf ++ 

                                左插入 _bf--;

由此,每棵树节点的 平衡因子会分为三种情况。

 [0]  [-1,1]  [2,-2]

 

	//调节平衡因子;
		//while(parent)
		while (cur != root)
		{
		   //更新节点bf
			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;
			}
			else if(parent->_bf == -2 || parent->_bf == 2)
			{
				//调整
			}
			else
			{
				//理论上不会走到这里来
				//如果走到这里来 也是调整前 已经不是AVL数
				assert(false);
			}
		}

 

(2)左右单旋:

 我们在看单旋图中,就是bf=-1,1 的节点(也就是parent) 是旋转的轴。

	else if(parent->_bf == -2 || parent->_bf == 2)
			{
				//调整
				//左边新增节点
				if (parent->_bf == -2)  
				{
					if (cur->_bf == -1) //新节点在左边插入
					{
						//左高 右边低 需要右翻转
						RotateR(parent);
					}
					else  //新增节点插入在右边
					{

					}
				}
				else //右边新增节点
				{
					if (cur->_bf == 1)  //新节点从右边插入
					{
						//此时 右高 左低 需要 右翻转
						RotateL(parent);
					}
					else //新增节点在左边
					{

					}
				}
				//翻转完 就跳出
				break;
			}

这也就是单旋插入的逻辑。

右单旋实现:

也及时sub subLR parent 三方的关系;

 

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

		//让subLR去做parent的左
		parent->_left = subLR;
		//subLR可能不存在
		if(subLR)
		subLR->_parent = parent;

		sub->_right = parent;
		//记录 父节点 因为 sub 可能不是唯一独立的树
		Node* grandparent = parent->_parent;
		parent->_parent = sub;

		if (parent == _root)
		{
			//独立树
			_root = sub;
			sub->_parent = nullptr;
		}
		else
		{
			//子树
			//parent的 父亲仍然保留着 节点地址
			if (grandparent->_left == parent)
			{
				grandparent->_left = sub;
			}
			else
			{
				grandparent->_right = sub;
			}
			//更新回来
			sub->_parent = grandparent;
		}
		
		//调节平衡因子
		parent->_bf = sub->_bf = 0;
	}

 

 

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

		//让subRL 去做parent的右边
		parent->_right = subRL;
		//仍然注意避坑
		if(subRL)
			subRL->_parent = parent;

		Node* grandparent = parent->_parent;
		sub->_left = parent;
		parent->_parent = sub;

		if (parent == _root)
		{
			_root = sub;
			sub->_parent = nullptr;
		}
		else
		{
			if (grandparent->_left == parent)
			{
				grandparent->_left = sub;
			}
			else
			{
				grandparent->_right = sub;
			}
			sub->_parent = grandparent;
		}
	
		parent->_bf = sub->_bf = 0;
	}

(3)双旋转:

左右双旋转:
 

	void RotateLR(Node* parent)
	{
		Node* sub = parent->_left;
		Node* subLR = sub->_right;
		//保存插入处 的平衡因子~
		int bf = subLR->_bf;

		RotateL(sub);
		RotateR(parent);

		//调整平衡因子
		if (bf == -1)  //左边B插入
		{
			sub->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = 1;
		}
		else if(bf ==1)
		{
			sub->_bf = -1;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else if(bf ==0)
		{
			sub->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

右左双旋:

这个同上也就 不再多分析。

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

		RotateR(sub);
		RotateL(parent);

		if (bf == 1) //从C插入
		{
			subRL->_bf = 0;
			sub->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subRL->_bf = 0;
			sub->_bf = 1;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subRL->_bf = 0;
			sub->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

④测试;

插入也就到此为止,我们先来试着跑几组数据看是否正确。


 

 (3)K、V版AVL 树

namespace dy
{
	template<class K, class V>
	struct AVLTreeNode
	{
		//节点指针
		AVLTreeNode<K, V>* _left;
		AVLTreeNode<K, V>* _right;
		AVLTreeNode<K, V>* _parent;
		//节点数值
		pair<K, V> _kv;

		//为了调节平衡 这里引入平衡因子;
		int _bf;

		//构造
		AVLTreeNode(const pair<K,V>& kv)
			:_left(nullptr),
			_right(nullptr),
			_parent(nullptr),
			_kv(kv),
			_bf(0)      //任何新插入的 节点 平衡因子都默认设置为0
		{}
	};

	template<class K,class V>
	struct AVLTreeKV
	{
		typedef AVLTreeNode<K, V> Node;
		Node* _root;

		AVLTreeKV()
			:_root(nullptr)
		{}

		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;

			_InOrder(root->_left);
			cout << root->_kv.first << ":"<<root->_kv.second<<endl;
			_InOrder(root->_right);
		}


		//没封装迭代器 暂且用Node* 替
		pair<Node*,bool> insert(const pair<K,V> kv)
		{
			//如果是第一次插入 直接去做根 root
			if (_root == nullptr)
			{
				_root = new Node(kv);
				return make_pair(_root,true);
			}

			//不是第一个值
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)  //这里和find的思想一样 
			{
				if (kv.first > cur->_kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (kv.first < cur->_kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					//如果走到这里 说明 已经插入了和key 一样的数
					return make_pair(cur,true);
				}
			}

			//cur 走到空的位置了
			cur = new Node(kv);
			Node* newnode = cur;

			//更新与parent的关系
			if (cur->_kv.first > parent->_kv.first)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}
			//注意我们设计的是三叉连 结构
			//cur 还有反过来 指向parent
			cur->_parent = parent;

			//调节平衡因子;
			//while(parent)
			while (cur != _root)
			{
				//更新节点bf
				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)
					{
						if (cur->_bf == -1) //新节点在左边插入
						{
							//左高 右边低 需要右翻转
							RotateR(parent);
						}
						else  //新增节点插入在右边
						{
							//此时 是折线
						   //使用双旋
							RotateLR(parent);
						}
					}
					else //右边新增节点
					{
						if (cur->_bf == 1)  //新节点从右边插入
						{
							//此时 右高 左低 需要 右翻转
							RotateL(parent);
						}
						else //新增节点在左边
						{
							RotateRL(parent);
						}
					}
					//翻转完 就跳出
					break;
				}
				else
				{
					//理论上不会走到这里来
					//如果走到这里来 也是调整前 已经不是AVL数
					assert(false);
				}
			}
			return make_pair(newnode,true);
		}

}

 


总结: 

①AVL树 插入节点时很看重平衡因子。

②平衡因子条件 [0] [-1,1] [-2,2]

③单旋是直线 双旋 为折线

内容到这里也就结束啦,感谢你的阅读。

祝你好运~

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值