C++实现AVL树

C++ 实现AVL树

一、介绍二叉搜索树

​ 二叉搜索树,就是一种特殊的二叉树,二叉树中每个节点都分为左、右两个子节点。这两个子节点不仅拥有自己的子节点而且还指向自己父节点。下面是代码表示和图示

template<typename T>
struct TreeNode
{
    TreeNode(T d,TreeNode* p=nullptr,TreeNode* l=nullptr,TreeNode* r= nullptr):data(d),parent(p),lc(l),rc(r){};
    TreeNode* parent,lc,rc;	//树型结构
    T data;				//数据元素
}

节点

​ 而二叉搜索树在二叉树的基础规则之上定义了新的规则:

每个节点的左子树均小于本节点,其右子树均大于本节点。

​ 这样整棵树的中序遍历一定是有序的。那么一课这样的二叉搜索树,其查询效率可以近似的看做是o(n)。在完全二叉树的情况下,每次向下搜索都排除掉一半的选项。但是可以预想到现实中不存在完全巧合的刚好是一个完全二叉树,或者说完全二叉树的条件过于苛刻。
在这里插入图片描述

读者可以考虑一下,如果我们插入一个有序序列会发生什么?

在这里插入图片描述

可以看到这个都已经退化为链表了。那么搜索效率肯定是降低至o(n)了,因为需要遍历一遍(链式结构,不能二分)。

二、自平衡二叉树

​ 通过上面的例子看出,普通的二叉搜索树是有缺陷的,甚至在极端情况下退化为链表。那么我们怎么解决的呢?科学家研究发明了自平衡二叉树(AVL),这棵树可以通过特殊的平衡方法去平衡节点。自平衡的方法就是旋转

AVL树的旋转

LL:对n1进行右旋

在这里插入图片描述

RR:对n1进项左旋

在这里插入图片描述

LR:先左旋最下面的节点,然后转换为LL的情况

在这里插入图片描述
RL:先右旋最下面的节点,然后转换为RR的情况

在这里插入图片描述

代码实现取了个巧,因为设计3个节点,4课子树,所以有一部分代码可以复用,就是调整树结构,我的设计就是先判断如何旋转,然后传入结构调整函数

//旋转接口
template<typename T>
TreeNodePtr<T> AVLTree<T>::rotateAt(TreeNodePtr<T> v)
{
	TreeNodePtr<T> p = v->parent;	TreeNodePtr<T> g = p->parent;	//找出connect34中的3个节点
	//三个节点关系v是孙子, p是父亲, g是爷爷
	//根据node2是左子节点还是右子节点可以判断时左旋还是右旋
	if (IsLChild(*p)) //p是左子树,zig
		if (IsLChild(*v))	//v是左子树 ,zig
		{
			p->parent = g->parent;
			return connect34(v, p, g, v->lc, v->rc, p->rc, g->rc);
		} 
		else				//zag
		{
			v->parent = g->parent;
			return connect34(p, v, g, p->lc, v->lc, v->rc, g->rc);
		}
	else					//zag
		if (IsRChild(*v))	//v是左子树  zag
		{
			p->parent = g->parent;
			return connect34(g, p, v, g->lc,p->lc, v->lc, v->rc);
		}
		else					//zig
		{
			v->parent = g->parent;
			return connect34(g, v, p, g->lc, v->lc, v->rc, p->rc);
		}
}


//旋转
/*		三个节点,四颗子树		*/
template<typename T>
TreeNodePtr<T> AVLTree<T>::connect34(TreeNodePtr<T> node1, TreeNodePtr<T>node2, TreeNodePtr<T>node3,
	TreeNodePtr<T>tree1, TreeNodePtr<T>tree2, TreeNodePtr<T>tree3, TreeNodePtr<T>tree4)		//平衡操作,根据输入顺序区分左右旋
{
	node1->lc = tree1; if (tree1) tree1->parent = node1;
	node1->rc = tree2; if (tree2) tree2->parent = node1;
	updataHeight(node1);	//更新该节点高度
	node3->lc = tree3;	if (tree3) tree3->parent = node3;
	node3->rc = tree4;  if (tree4) tree4->parent = node3;
	updataHeight(node3);
	node2->lc = node1; node1->parent = node2;
	node2->rc = node3; node3->parent = node2;
	updataHeight(node2);
	return node2;
}

三、解析

​ 插入和删除操作就是改变后,对失衡节点进行重平衡。AVL树如何判断是否失衡呢?通过平衡因子,其定义是左子树高度减去右子树高度,如果树高相差超过2(即平衡因子绝对值)。

判断平衡因子
//当前平衡因子
template<typename T>
inline int BalFac(const TreeNode<T>& x) { return (((x.lc)?x.lc->height:-1)-((x.rc)?x.rc->height:-1)); }
//是否平衡
template<typename T>
inline bool AvlBalanced(const TreeNode<T>& x) { return (-2 < BalFac(x) && 2 > BalFac(x)); }	
搜索:二分搜索
template<typename T>
TreeNodePtr<T>& AVLTree<T>::search(const T& e)
{
	//特殊情况:插入第一个节点
	if (!_root || e == _root->data) {
		_parent = NULL;
		return _root;
	} //在树根v处命中
	//printf("树根不是节点\n");
	for (_parent = _root; ; ) { //否则,自顶而下

		TreeNodePtr<T>& c = (e < _parent->data) ? _parent->lc : _parent->rc; //确定方向

		if (!c || e == c->data) {
			return c;
		}
		_parent = c;
	}
}

插入:先插入值,在判断是否需要重平衡。注意insert后可能在某个祖先节点失衡,极端情况下是在根节点处平衡。因为平衡导致的失衡传播(因为平衡操作导致树高变化,引起了祖先节点的失衡),所以需要全树遍历。

template<typename T>
TreeNodePtr<T> AVLTree<T>::insert(const T& e)
{
	//先判断是否存在本节点
	TreeNodePtr<T>& p = search(e);	if (p!=nullptr) return p;	
	//确定此节点不存在,创建新节点,新节点一定是叶节点
	TreeNodePtr<T> x= p = new TreeNode<T>(e, _parent);
	_size++;	

	//向上循环重平衡
	for (TreeNodePtr<T> i = _parent; i; i = i->parent)	//从插入节点的父节点开始,向上逐层平衡
	{
		if (!AvlBalanced(*i))	//失衡,则调用接口复衡
		{
			FromParentTo(*i) = rotateAt(tallerChild(tallerChild(i)));	//涉及的三个节点中进行判断,即判断几次旋转
			break;
		}
		else
			updataHeight(i);	//平衡就更新高度,以防高度错误
	}
	return x;
}

删除:删除时需要找到直接后继(该节点在中序遍历中的下一个节点,被称为该节点的直接后继),删除操作除了找直接后继比较麻烦,平衡和insert一样。所以本来考虑抽出来写个函数,但是如果以后有时间再整理吧。

/*删除就是先删除节点,然后根据删除节点前后,向上平衡,平衡策略和insert一样,主要就是remove一个节点*/
template<typename T>
bool AVLTree<T>::remove(const T& e)
{
	//先判断是否存在本节点
	TreeNodePtr<T>& x = search(e);	if (!x) return false;	//节点不存在,返回错误
	//确定此节点不存在,创建新节点,新节点一定是叶节点
	{//想抽出来写个函数,这样比较整洁
		TreeNodePtr<T> w = x; //实际被摘除的节点,初值同x
		TreeNodePtr<T> succ = NULL; //实际被删除节点的接替者
		if (!x->lc) //若*x的左子树为空,则可
			succ = x = x->rc; //直接将*x替换为其右子树
		else if (!x->rc) //若右子树为空
			succ = x = x->lc;
		else { //若左右子树均存在,则选择x的直接后继作为实际被摘除节点,为此需要
			w = w->succ(); //(在右子树中)找到*x的直接后继*w
			std::swap(x->data, w->data); //交换*x和*w的数据元素
			TreeNodePtr<T> u = w->parent;
			((u == x) ? u->rc : u->lc) = succ = w->rc;
		}
		_parent = w->parent; //记录实际被删除节点的父亲
		if (succ) succ->parent = _parent; //并将被删除节点的接替者与parent相联
	}
	_size--;	//这里是-1

	//向上循环重平衡
	for (TreeNodePtr<T> i = _parent; i; i = i->parent)	//从插入节点的父节点开始,向上逐层平衡
	{
		if (!AvlBalanced(*i))	//失衡,则调用接口复衡
			FromParentTo(*i) = rotateAt(tallerChild(tallerChild(i)));	//涉及的三个节点中进行判断,即判断几次旋转
		updataHeight(i);	//删除树节点,高度更容易改变,需要依次更新节点
	}
	return true;
	
}

romParentTo(*i) = rotateAt(tallerChild(tallerChild(i)));	//涉及的三个节点中进行判断,即判断几次旋转
		updataHeight(i);	//删除树节点,高度更容易改变,需要依次更新节点
	}
	return true;
	
}

四、源码地址

​ https://github.com/yqm-307/yqm_data_structure

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绿色健康老清新

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值