【STL】AVL树(插入、删除逻辑,旋转的引入)

目录

AVL树简介

AVL树框架构建

AVL树节点类

AVL树类框架构建

构造与拷贝构造

构造

拷贝构造

析构函数

Insert 插入

四大旋转介绍

总体情况介绍

左单旋

右单旋

右左双旋

左右双旋

Insert 插入(旋转逻辑补全版)

AVL树删除算法

总代码 + 测试代码

总代码

测试代码

结语


AVL树简介

我们在前面的学习中会知道,二叉搜索树有一个弊端,就是可能会一直插入在一条线上,如下:

当我们遇到这种情况的时候,其实就和链表差不多了,效率就会从logN变成O(N)

换句话说就是,100万个数据从搜索20次变成100万次

所以就有了AVL树,这个树有一个规定就是:左右节点的高度差不超过1

如上,这就是一棵AVL树,我们看到节点3,左子树高度为1,右子树为2,高度差为1

我们看到节点8,左子树高3,右子树为2,高度差为1

并且,AVL树我们可以理解成是一棵增加了AVL特性的二叉搜索树

因为AVL树也是规定,左子树比根节点小,右子树比根节点大,所以我们中序遍历时就是从小到大的一个顺序

AVL树框架构建

AVL树节点类

首先,我们的AVL树这个类的成员变量就只有一个,就是AVL树的节点

但是一个节点需要有左右指针,还要有一个指针指向parent,还要有平衡因子和一个类型为pair的值

所以单靠一个变量是没法同时实现这些功能的,所以我们需要实现一个类

代码如下:

template<class k, class v>
struct AVLTreeNode
{
	AVLTreeNode<k, v>* _left;
	AVLTreeNode<k, v>* _right;
	AVLTreeNode<k, v>* _parent;

	int _bf;
	pair<k, v> _kv;

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

AVL树类框架构建

首先,我们的类只有一个成员变量就是AVL树节点

然后,我们还需要一个模板,这样我们就可以控制pair的两个值

最后,我们还可以给这个节点的类名typedef一下,这样我们后续会好写一点

代码如下:

template<class k, class v>
class AVLTree
{
	typedef AVLTreeNode<k, v> Node;
public:

private:
	Node* _root;
};

构造与拷贝构造

构造

我们的构造斌不需要显示写,因为只有一个成员变量,这个成员还是自定义类型,默认生成的构造函数会自动调用他的构造函数

但是,我们后面还要写拷贝构造,默认构造生成的条件是不能有构造,拷贝构造也是构造

所以,我们可以直接使用default这种做法来强制编译器默认生成一个

代码如下:

AVLTree() = default;

拷贝构造

我们的拷贝构造就只能老老实实,一个一个拷贝了

这时我们可以写一个递归来实现拷贝

递归的主逻辑:首先,递归出口是当传过来的节点为空,就返回空指针

然后是new一个节点,这个节点的左子树链接到一个递归,右子树一个递归,最后返回这个新插入的节点即可,就不再赘述了

AVLTree(const AVLTree<k, v>& avl)
{
	_root = copy(avl._root);
}


Node* copy(Node* root)
{
	if (root == nullptr)return nullptr;

	Node* newnode = new Node(root->_kv);

	newnode->_right = copy(root->_right);
	newnode->_left = copy(root->_left);

	return newnode;
}

析构函数

析构的大逻辑还是递归,具体就是,如果传过来的节点为空,就直接return

然后就是先走左,再走右,最后删除传过来的节点

代码如下:

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

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

	Destroy(root->_left);
	Destroy(root->_right);
	delete root;
}

Insert 插入

我们的AVL树引入了一个平衡因子的概念,就是每一个节点都有这么一个成员,而具体算法就是:

右子树的高度 - 左子树的高度

而我们要插入的话,我们得先找一找,这棵树里面有没有一样的值,如果有的话,那我还插入什么,直接return即可

要是如果找不到一样的值的话,那我们也就找到了待插入的位置,因为一直找不到,直到为空就退出,那么我们退出的时候,那个节点就是待插入的位置

但是我们光找到这个没有用,我们new完节点之后,还需要将其与父节点链接起来,所以我们同时还需要再定义一个变量用来记录父节点的位置

目前代码如下:

bool Insert(const pair<k, v>& kv)
{
	if (!_root)
	{
		_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;
		}
	}

接下来就是需要new一个cur节点出来,然后我们再根据这个节点里面的值与父节点里面的值的大小关系,将这个新节点和父节点链接起来

如果cur的值比父节点的值小,就插入在父节点的左边,反之则是右边

最后控制一下新节点的parent指针即可,此段代码如下:

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

然后我们就进入了平衡因子的调整环节

首先我们需要写一个while循环,因为我们可能会一直向上调整,所以需要一个循环,循环的条件就是parent还存在,如果parent == nullptr的话,就证明已经到根节点了,没法旋了,就可以退出了

然后控制一下平衡因子,如果新节点插入在parent的左边,那我们的parent的平衡因子就需要--,因为是右子树的高度 - 左子树的高度,所以在左的话就增加了左的高度,就需要--

反之,在右边就是++

此段如下:

while (parent)
{
	if (cur == parent->_left)
		parent->_bf--;
	else
		parent->_bf++;
}

然后就是判断需不需要旋转了,这可以分为一下三种情况:

  1. 父节点的平衡因子为0,证明左右两边高度相同,不需要旋转啥的,直接返回即可(或者说插入的地方本来是只有一个节点的,插入之后变成两个节点,整体高度没变,直接返回即可)
  2. 父节点的平衡因子为1或者-1,就说明可能是在原本为0的地方插入了一个节点,这棵子树虽然不用旋转,但是整体高度+1了,上面的节点可能就不平衡了,所以我们需要向上调整,也就是cur = parent, parent = parent->_parent;
  3. 父节点的平衡因子等于2或者-2,已经严重不平衡了,需要旋转处理

(为0的情况)

(为1或-1的情况)

(为2或-2的情况)

代码如下:

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)
{
	旋转逻辑
}
else
{
	assert(false);
}

插入里面的旋转逻辑在下文,讲完旋转之后就是剩下为2或者-2的情况了

四大旋转介绍

总体情况介绍

接下来我们来盘点一下,插入中旋转的四种情况:

(右单旋)

(左单旋)

(右左双旋)

右左双旋就是:

下图为例:如果父节点的平衡因子与子节点的平衡因子异号的话,我们对下图左的30进行一次左单旋的话,会发现依然是不平衡,再对其进行一次右单旋会发现,又变回来了

所以我们先对下图中的节点30进行一次右单旋,会发现其就直接变成了对10节点左单旋的情况

所以这叫做右左双旋

(左右双旋)

就是右左的镜像处理

左单旋

左单旋的代码如下:(解析在代码下面)

//左单旋
void RotateL(Node* parent)
{
    ///
    
	Node* curR = parent->_right;
	Node* curRL = curR->_left;
	Node* Parentparent = parent->_parent;

	//旋转逻辑
	parent->_right = curRL;
	curR->_left = parent;

    ///

	//处理parent
	if (curRL)curRL->_parent = parent;
	parent->_parent = curR;

    ///

	if (!Parentparent)
	{
		_root = curR;
		curR->_parent = nullptr;
	}
	else
	{
		if (Parentparent->_left == parent)
			Parentparent->_left = curR;
		else
			Parentparent->_right = curR;

		curR->_parent = Parentparent;
	}

    ///

	curR->_bf = parent->_bf = 0;

    ///
}

这代码其实很简单,不要将其想得太复杂,首先我们是先将三个节点定义了出来

其中第三个是parent的_parent,我们将其命名为Parentparent

然后将curRL变成parent的右节点

然后parent自己成为curR的左节点

因为本来就是curR那边多出来了,我们旋转一下之后,parent下去了,那么自然就平衡了

(上述为第一块代码区域)

而后面的代码就都是在干一件事:找父节点

因为我们的节点是有三个指针的,一个左,一个右,一个parent

我们接下来解决的就是parent指针的指向

首先是curRL,因为我们不确定其是否为空,所以我们需要判断一下,如果存在,再让其的parent指针指向parent

(上述为第二块代码区域)

然后是parent,parent的_parent指针指向的是curR

最后是curR,我们无法确定Parentparent是否为空,因为如果parent节点旋转之前是根节点呢

所以我们就加了一条判断逻辑(如果为空,就直接让curR做根节点,并将curR的parent节点置空)

然后,如果Parentparent不为空,因为Parentparent还指向着parent节点,所以我们就直接判断curR是要链接在Parentparent的左边还是右边

最后让curR的parent指针指向Parentparent

(上述为第三块代码区域)

最后的最后就是平衡因子的处理,单旋的两种情况,都是旋转完之后平衡因子都变成0,所以我们直接置为0即可

(上述为第四块代码区域)

右单旋

右单旋就是左单选的镜像处理,这里就不再赘述了

//右单旋
void RotateR(Node* parent)
{
	Node* curL = parent->_left;
	Node* curLR = curL->_right;
	Node* Parentparent = parent->_parent;

	//旋转逻辑
	parent->_left = curLR;
	curL->_right = parent;

	//处理parent
	if (curLR)curLR->_parent = parent;
	parent->_parent = curL;

	if (!Parentparent)
	{
		_root = curL;
		curL->_parent = nullptr;
	}
	else
	{
		if (Parentparent->_left == parent)
			Parentparent->_left = curL;
		else
			Parentparent->_right = curL;

		curL->_parent = Parentparent;
	}
	curL->_bf = parent->_bf = 0;
}

右左双旋

代码如下,解析在代码下面:

//右左双旋
void RotateRL(Node* parent)
{
	Node* curR = parent->_right;
	Node* curRL = curR->_left;
	int bf = curRL->_bf;

	RotateR(curR);
	RotateL(parent);

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

右左双旋最关键的,就是最下面那个节点的平衡因子的值

我们看图,就这么去抽象地想,旋转完之后,curRL节点从最下面跑到了最上面

然后parent和curR就分居在curRL的左右两侧

然后curRL的左子树和右子树,分别给了parent当右子树,给了curR当左子树

所以,curRL的平衡因子,就决定了最后的平衡因子的调整

综上,我们在定义出curR和curRL的同时,我们还需要将curRL的平衡因子积累下来

接着就是先对curR进行右旋,然后再对parent进行左旋操作

最后,分三种情况讨论,bf为0、bf为1,bf为-1(bf是curRL的平衡因子)

左右双旋

左右双旋就是右左双旋的镜像,这里就不再赘述了

代码如下:

//左右双旋
void RotateLR(Node* parent)
{
	Node* curL = parent->_left;
	Node* curLR = curL->_right;
	int bf = curLR->_bf;

	RotateL(curL);
	RotateR(parent);

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

Insert 插入(旋转逻辑补全版)

上面,我们刚讲到要旋转的地方就停止了,接着就是讲解旋转的四种情况

接下来,我们来讲一讲插入时要遇到的旋转逻辑:

如果父节点的平衡因子为 2,子节点的平衡因子为 1,就左单旋

如果父节点的平衡因子为 -2,子节点的平衡因子为 -1,就右单旋

如果父节点的平衡因子为 2,子节点的平衡因子为 -1,就右左双旋

如果父节点的平衡因子为 -2,子节点的平衡因子为 1,就左右双旋

代码如下:

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)
		{
			//左单旋
			RotateL(parent);
		}
		else if (parent->_bf == -2 && cur->_bf == -1)
		{
			//右单旋
			RotateR(parent);
		}
		else if (parent->_bf == 2 && cur->_bf == -1)
		{
			//右左双旋
			RotateRL(parent);
		}
		else
		{
			//左右双旋
			RotateLR(parent);
		}
		break;
	}
	else
	{
		assert(false);
	}
}
return true;

插入完整代码如下:

bool Insert(const pair<k, v>& kv)
{
	if (!_root)
	{
		_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 (cur->_kv.first < parent->_kv.first)
		parent->_left = cur;
	else
		parent->_right = cur;
	cur->_parent = parent;

	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)
			{
				//左单旋
				RotateL(parent);
			}
			else if (parent->_bf == -2 && cur->_bf == -1)
			{
				//右单旋
				RotateR(parent);
			}
			else if (parent->_bf == 2 && cur->_bf == -1)
			{
				//右左双旋
				RotateRL(parent);
			}
			else
			{
				//左右双旋
				RotateLR(parent);
			}
			break;
		}
		else
		{
			assert(false);
		}
	}
	return true;
}

AVL树删除算法

提示,在讲删除算法之前,需要理解二叉搜索树中的删除逻辑,下文虽然有讲解,但是不会那么详细,建议不熟的可以看一看下面这篇讲解二叉搜索树的博客:
【STL】二叉搜索树 BSTree(底层讲解 + key_value(KV)的引入)

接下来我们来正式开始讲解AVL树的删除

在开始删除之前,我们需要先找到这个待删除的节点,所以我们需要写一个循环,找到这个节点

但是我们还是需要写一个parent节点,因为删除了节点之后,我们还要和被删除节点的孩子节点链接起来

我们先将前面的代码实现出来(找待删除节点):

bool Erase(const k& key)
{
	if (!_root)return false;

	Node* parent = nullptr;
	Node* cur = _root;

	while (cur)
	{
		if (cur->_kv.first < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
            //
			// 删除主逻辑
            //
		}
	}
	return false;
}

删除里面还需要分三种请款

第一种是待删除节点没有孩子

第二种情况是待删除节点只有一个孩子

第三种情况是待删除节点有两个孩子

面对第一种情况,我们直接删除,然后让父节点指向这个节点的指针置空即可

面对第二种情况,我们就需要让父节点指向这个节点的孩子节点,随后才能将这个待删除节点删除掉

面对第三种情况,我们就需要转换问题,先去找左子树的最右节点、或者是右子树的最左节点(这里以右子树的最左举例)     然后我们用这个节点的值覆盖待删除节点的值,接着我们就将问题从删除待删除节点——>删除待删除节点的右子树的最左节点

而我们右子树的最左节点肯定是满足第一种情况或第二种情况的,按这两种情况处理即可

第一种和第二种情况其实可以合并成一种情况,我们在删除的时候,只需要判断一下左孩子节点是否为空,走一个逻辑;判断一下右孩子节点是否为空,走一个逻辑

如果没有孩子节点的话,就都会走左孩子节点为空的情况,但是里面是将右孩子交给父节点,其实就是将父节点置空,所以走哪个条件都无所谓

当我们走完了判断左孩子和右孩子之后,就顺便把第二种情况解决完了,因为只有一个孩子,不是左孩子就是右孩子

但是有两个孩子的情况我们需要单独写一个逻辑

我们需要先找到右子树的最左节点

然后将那个节点的值与待删除节点交换(转换问题,之后就变成了删除右子树的最左节点)

然后我们重置搜索待删除节点时的parent和cur

最后删除右子树的最左节点即可

上述代码如下:

// 删除主逻辑
// 判断三种情况,没孩子,一个孩子,两个孩子
if (cur->_left == nullptr)
{
	// 假如删除的是根节点
	if (!parent)
	{
		_root = cur->_right;
		delete cur;
		return true;
	}
	else
	{
		if (parent->_left == cur)
		{
			// 如果待删除节点在parent的左
			// 平衡因子 ++
			parent->_bf++;
			parent->_left = cur->_right;
		}
		else
		{
			// 如果待删除节点在parent的右
			// 平衡因子 --
			parent->_bf--;
			parent->_right = cur->_right;
		}
	}
}
else if (cur->_right == nullptr)
{
	// 假如删除的是根节点
	if (!parent)
	{
		_root = cur->_left;
		delete cur;
		return true;
	}
	else
	{
		if (parent->_left == cur)
		{
			// 如果待删除节点在parent的左
			// 平衡因子 ++
			parent->_bf++;
			parent->_left = cur->_left;
		}
		else
		{
		    // 如果待删除节点在parent的右
			// 平衡因子 --
			parent->_bf--;
			parent->_right = cur->_left;
		}
	}
}
else
{
	// 有两个孩子的情况
	// 找右子树的最左节点
	Node* rightMinP = cur;
	Node* rightMin = cur->_right;

	// 循环找最左节点
	while (rightMin->_left)
	{
		rightMinP = rightMin;
		rightMin = rightMin->_left;
	}

	// 交换右子树的最左节点与待删除节点的值
	cur->_kv = rightMin->_kv;

	// 删除右子树的最左节点
	if (rightMin == rightMinP->_left)
	{
		rightMinP->_bf++;
		rightMinP->_left = rightMin->_right;
	}
	else
	{
		rightMinP->_bf--;
		rightMinP->_right = rightMin->_right;
	}

	// 重置parent和cur
	parent = rightMinP;
	cur = rightMin;

}
delete cur;

注意,上述代码中加入了平衡因子调节的逻辑

并且在有两个孩子的情况的最后面,我们是将cur置为rightMin之后,统一删除cur节点的

这时cur节点就变成了野指针,所以我们不能使用

但这也有一个解决方法,就是后面调整平衡因子逻辑的时候,我们就将第一次调整给跳过,因为我们上面已经调整过了,只是第一次调整不需要

后面我们会给cur赋值之后才使用,各位不必担心

更新平衡因子和旋转逻辑

首先我们第一次是不需要更新平衡因子的,所以我们可以设一个bool变量为false,平衡因子那里就判断一下,如果为true才进入调整平衡因子,在第一次跳过之后,后面我们永久将那个bool变量置为true即可

上述代码如下:

bool is_or_not = false;
while (parent)
{
	// 更新平衡因子
	if (is_or_not)
	{
		if (parent->_left == cur)
			parent->_bf++;
		else
			parent->_bf--;
	}
	is_or_not = true;

    /
    后续旋转逻辑
    具体代码为下一段
    /

}

接着我们就需要分析一下是否需要了

这时候可以分三种情况讨论:

  1. 父节点的平衡因子为0
  2. 父节点的平衡因子为1或-1
  3. 父节点的平衡因子为2或-2

首先我们是删除逻辑,如果删除之后,父节点的平衡因子为0,就证明之前是1或-1,就是本来只有一个节点,但是现在删除的就是那个节点,这时候,整个子树的高度已经变了,所以我们需要向上调整

然后是为1或-1的情况,如果是1或-1,只能是原本为0的情况,因为0代表有两个节点,但是现在删掉了左孩子或者右孩子,就变成了1或-1,这里只有0或2或-2能删除了一个节点之后变成1或-1,但是如果是2或-2的话代表原来本来就不平衡了,所以2或-2变成1或-1是不可能的,只能是0变成1或-1

最后是2或-2的情况,这个已经严重不平衡了,需要旋转

上述代码如下:

if (parent->_bf == 1 || parent->_bf == -1)
	return true;
else if (parent->_bf == 0)
{
	cur = parent;
	parent = parent->_parent;
}
else if(parent->_bf == 2 || parent->_bf == -2)
{
	//	
    此处代码为旋转
    具体代码在下一段
	//	
}
else
{
	assert(false);
}

进入了旋转环节之后,同样需要分情况讨论,我们都知道平衡因子只能是0,1,-1,2,-2

而这时父节点已经是2或-2的其中一个了

而我们的孩子节点肯定不可能是2或者-2,因为是的话代码就出问题了,孩子早就该旋转了,所以只有在我们代码写错了的情况下才会出现这种情况

所以子节点只能是0,1,-1

  1. 父节点为2或-2,孩子节点为0(0是一种特殊情况,调整完之后就平衡了,高度也没变,所以0的情况调整完之后就直接return退出即可)
  2. 父节点为2,子节点为1(同号,单旋)(左单旋)
  3. 父节点为-2,子节点为-1(同号,单旋)(右单旋)
  4. 父节点为2,子节点为-1(异号,双旋)(右左双旋)
  5. 父节点为-2,子节点为1(异号,双旋)(左右双旋)

上述看似有5种情况,但其实只有三种,因为中间的同号单旋算一种,异号双旋算一种

我们先来看子节点为0的情况:

接着就是同号和异号各两种情况了,剩下的这几种情况和插入那里都是一样的,同号单旋异号双旋

只不过,我们这个旋转逻辑处理完之后,我们需要自己手动向上调整,因为剩下的这几种情况删除旋转之后,整棵树的高度都会发生改变,整体的变了,上面的节点就会收到影响,所以我们就需要向上调整

一直调整到为1或-1,或者就是调整到根,才停止

代码如下:

else if(parent->_bf == 2 || parent->_bf == -2)
{
	//旋转逻辑
	Node* highernode;
	int sign;
	if (parent->_bf > 0)
	{
		sign = 1;
		highernode = parent->_right;
	}
	else
	{
		sign = -1;
		highernode = parent->_left;
	}

	if (highernode->_bf == 0)
	{
		if (highernode == parent->_right)
		{
			RotateL(parent);
			parent->_bf = 1;
			highernode->_bf = -1;
		}
		else
		{
			RotateR(parent);
			parent->_bf = -1;
			highernode->_bf = 1;
		}
	}
	else if (sign == highernode->_bf)
	{
		// 单旋
		if (sign > 0)
			RotateL(parent);
		else
			RotateR(parent);
	}
	else
	{
		// 多旋
		if (sign > 0)
			RotateRL(parent);
		else
			RotateLR(parent);
	}
	cur = parent;
	parent = parent->_parent;
}

删除总代码如下:

bool Erase(const k& key)
{
	if (!_root)return false;

	Node* parent = nullptr;
	Node* cur = _root;

	while (cur)
	{
		if (cur->_kv.first < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			// 删除主逻辑
			// 判断三种情况,没孩子,一个孩子,两个孩子
			if (cur->_left == nullptr)
			{
				// 假如删除的是根节点
				if (!parent)
				{
					_root = cur->_right;
					delete cur;
					return true;
				}
				else
				{
					if (parent->_left == cur)
					{
						// 如果待删除节点在parent的左
						// 平衡因子 ++
						parent->_bf++;
						parent->_left = cur->_right;
					}
					else
					{
						// 如果待删除节点在parent的右
						// 平衡因子 --
						parent->_bf--;
						parent->_right = cur->_right;
					}
				}
			}
			else if (cur->_right == nullptr)
			{
				// 假如删除的是根节点
				if (!parent)
				{
					_root = cur->_left;
					delete cur;
					return true;
				}
				else
				{
					if (parent->_left == cur)
					{
						// 如果待删除节点在parent的左
						// 平衡因子 ++
						parent->_bf++;
						parent->_left = cur->_left;
					}
					else
					{
						// 如果待删除节点在parent的右
						// 平衡因子 --
						parent->_bf--;
						parent->_right = cur->_left;
					}
				}
			}
			else
			{
				// 有两个孩子的情况
				// 找右子树的最左节点
				Node* rightMinP = cur;
				Node* rightMin = cur->_right;

				// 循环找最左节点
				while (rightMin->_left)
				{
					rightMinP = rightMin;
					rightMin = rightMin->_left;
				}

				// 交换右子树的最左节点与待删除节点的值
				cur->_kv = rightMin->_kv;

				// 删除右子树的最左节点
				if (rightMin == rightMinP->_left)
				{
					rightMinP->_bf++;
					rightMinP->_left = rightMin->_right;
				}
				else
				{
					rightMinP->_bf--;
					rightMinP->_right = rightMin->_right;
				}

				// 重置parent和cur
				parent = rightMinP;
				cur = rightMin;

			}
			delete cur;
			/


			bool is_or_not = false;
			while (parent)
			{
				// 更新平衡因子
				if (is_or_not)
				{
					if (parent->_left == cur)
						parent->_bf++;
					else
						parent->_bf--;
				}
				is_or_not = true;

				if (parent->_bf == 1 || parent->_bf == -1)
					return true;
				else if (parent->_bf == 0)
				{
					cur = parent;
					parent = parent->_parent;
				}
				else if(parent->_bf == 2 || parent->_bf == -2)
				{
					//旋转逻辑
					Node* highernode;
					int sign;
					if (parent->_bf > 0)
					{
						sign = 1;
						highernode = parent->_right;
					}
					else
					{
						sign = -1;
						highernode = parent->_left;
					}

					if (highernode->_bf == 0)
					{
						if (highernode == parent->_right)
						{
							RotateL(parent);
							parent->_bf = 1;
							highernode->_bf = -1;
						}
						else
						{
							RotateR(parent);
							parent->_bf = -1;
							highernode->_bf = 1;
						}
					}
					else if (sign == highernode->_bf)
					{
						// 单旋
						if (sign > 0)
							RotateL(parent);
						else
							RotateR(parent);
					}
					else
					{
						// 多旋
						if (sign > 0)
							RotateRL(parent);
						else
							RotateLR(parent);
					}
					cur = parent;
					parent = parent->_parent;
				}
				else
				{
					assert(false);
				}
			}


		}
	}
	return false;
}

总代码 + 测试代码

总代码

#pragma once
#include<iostream>
using namespace std;
#include<assert.h>

template<class k, class v>
struct AVLTreeNode
{
	AVLTreeNode<k, v>* _left;
	AVLTreeNode<k, v>* _right;
	AVLTreeNode<k, v>* _parent;

	int _bf;
	pair<k, v> _kv;

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


template<class k, class v>
class AVLTree
{
	typedef AVLTreeNode<k, v> Node;
public:
	AVLTree() = default;

	AVLTree(const AVLTree<k, v>& avl)
	{
		_root = copy(avl._root);
	}

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

	bool Insert(const pair<k, v>& kv)
	{
		if (!_root)
		{
			_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 (cur->_kv.first < parent->_kv.first)
			parent->_left = cur;
		else
			parent->_right = cur;
		cur->_parent = parent;

		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)
				{
					//左单旋
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					//右单旋
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					//右左双旋
					RotateRL(parent);
				}
				else
				{
					//左右双旋
					RotateLR(parent);
				}
				break;
			}
			else
			{
				assert(false);
			}
		}
		return true;
	}


	 AVL删除算法

	bool Erase(const k& key)
	{
		if (!_root)return false;

		Node* parent = nullptr;
		Node* cur = _root;

		while (cur)
		{
			if (cur->_kv.first < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				// 删除主逻辑
				// 判断三种情况,没孩子,一个孩子,两个孩子
				if (cur->_left == nullptr)
				{
					// 假如删除的是根节点
					if (!parent)
					{
						_root = cur->_right;
						delete cur;
						return true;
					}
					else
					{
						if (parent->_left == cur)
						{
							// 如果待删除节点在parent的左
							// 平衡因子 ++
							parent->_bf++;
							parent->_left = cur->_right;
						}
						else
						{
							// 如果待删除节点在parent的右
							// 平衡因子 --
							parent->_bf--;
							parent->_right = cur->_right;
						}
					}
				}
				else if (cur->_right == nullptr)
				{
					// 假如删除的是根节点
					if (!parent)
					{
						_root = cur->_left;
						delete cur;
						return true;
					}
					else
					{
						if (parent->_left == cur)
						{
							// 如果待删除节点在parent的左
							// 平衡因子 ++
							parent->_bf++;
							parent->_left = cur->_left;
						}
						else
						{
							// 如果待删除节点在parent的右
							// 平衡因子 --
							parent->_bf--;
							parent->_right = cur->_left;
						}
					}
				}
				else
				{
					// 有两个孩子的情况
					// 找右子树的最左节点
					Node* rightMinP = cur;
					Node* rightMin = cur->_right;

					// 循环找最左节点
					while (rightMin->_left)
					{
						rightMinP = rightMin;
						rightMin = rightMin->_left;
					}

					// 交换右子树的最左节点与待删除节点的值
					cur->_kv = rightMin->_kv;

					// 删除右子树的最左节点
					if (rightMin == rightMinP->_left)
					{
						rightMinP->_bf++;
						rightMinP->_left = rightMin->_right;
					}
					else
					{
						rightMinP->_bf--;
						rightMinP->_right = rightMin->_right;
					}

					// 重置parent和cur
					parent = rightMinP;
					cur = rightMin;

				}
				delete cur;
				/


				bool is_or_not = false;
				while (parent)
				{
					// 更新平衡因子
					if (is_or_not)
					{
						if (parent->_left == cur)
							parent->_bf++;
						else
							parent->_bf--;
					}
					is_or_not = true;

					if (parent->_bf == 1 || parent->_bf == -1)
						return true;
					else if (parent->_bf == 0)
					{
						cur = parent;
						parent = parent->_parent;
					}
					else if(parent->_bf == 2 || parent->_bf == -2)
					{
						//旋转逻辑
						Node* highernode;
						int sign;
						if (parent->_bf > 0)
						{
							sign = 1;
							highernode = parent->_right;
						}
						else
						{
							sign = -1;
							highernode = parent->_left;
						}

						if (highernode->_bf == 0)
						{
							if (highernode == parent->_right)
							{
								RotateL(parent);
								parent->_bf = 1;
								highernode->_bf = -1;
							}
							else
							{
								RotateR(parent);
								parent->_bf = -1;
								highernode->_bf = 1;
							}
						}
						else if (sign == highernode->_bf)
						{
							// 单旋
							if (sign > 0)
								RotateL(parent);
							else
								RotateR(parent);
						}
						else
						{
							// 多旋
							if (sign > 0)
								RotateRL(parent);
							else
								RotateLR(parent);
						}
						cur = parent;
						parent = parent->_parent;
					}
					else
					{
						assert(false);
					}
				}


			}
		}
		return false;
	}





	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

private:
	//左单旋
	void RotateL(Node* parent)
	{
		Node* curR = parent->_right;
		Node* curRL = curR->_left;
		Node* Parentparent = parent->_parent;

		//旋转逻辑
		parent->_right = curRL;
		curR->_left = parent;

		//处理parent
		if (curRL)curRL->_parent = parent;
		parent->_parent = curR;

		if (!Parentparent)
		{
			_root = curR;
			curR->_parent = nullptr;
		}
		else
		{
			if (Parentparent->_left == parent)
				Parentparent->_left = curR;
			else
				Parentparent->_right = curR;

			curR->_parent = Parentparent;
		}
		curR->_bf = parent->_bf = 0;
	}

	//右单旋
	void RotateR(Node* parent)
	{
		Node* curL = parent->_left;
		Node* curLR = curL->_right;
		Node* Parentparent = parent->_parent;

		//旋转逻辑
		parent->_left = curLR;
		curL->_right = parent;

		//处理parent
		if (curLR)curLR->_parent = parent;
		parent->_parent = curL;

		if (!Parentparent)
		{
			_root = curL;
			curL->_parent = nullptr;
		}
		else
		{
			if (Parentparent->_left == parent)
				Parentparent->_left = curL;
			else
				Parentparent->_right = curL;

			curL->_parent = Parentparent;
		}
		curL->_bf = parent->_bf = 0;
	}

	//右左双旋
	void RotateRL(Node* parent)
	{
		Node* curR = parent->_right;
		Node* curRL = curR->_left;
		int bf = curRL->_bf;

		RotateR(curR);
		RotateL(parent);

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

	//左右双旋
	void RotateLR(Node* parent)
	{
		Node* curL = parent->_left;
		Node* curLR = curL->_right;
		int bf = curLR->_bf;

		RotateL(curL);
		RotateR(parent);

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


	int _Height(Node* root)
	{
		if (!root)return 0;

		int left = _Height(root->_left);
		int right = _Height(root->_right);

		return max(left, right) + 1;
	}

	void _InOrder(Node* root)
	{
		if (!root)return;

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

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

		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
	}

	Node* copy(Node* root)
	{
		if (root == nullptr)return nullptr;

		Node* newnode = new Node(root->_kv);

		newnode->_right = copy(root->_right);
		newnode->_left = copy(root->_left);

		return newnode;
	}

private:
	Node* _root;
};


测试代码

#include"AVLTree.h"
#include<string>

void testAVLTree1()
{
	int a[] = { 1,2,3,4,5,6 };
	AVLTree<int, int> avl;
	for (auto e : a)
	{
		avl.Insert({ e,e });
	}
	avl.InOrder();
}

void testAVLTree2()
{
	AVLTree<string, string> avl;
	avl.Insert({ "left","左边"});
	avl.Insert({ "right","右边"});
	avl.Insert({ "word","单词"});
	avl.Insert({ "love","爱你"});
	avl.Insert({ "hate","恨你"});
	avl.InOrder();

	avl.Erase("word");
	avl.InOrder();
	avl.Erase("left");
	avl.InOrder();
	avl.Erase("right");
	avl.InOrder();
	avl.Erase("love");
	avl.InOrder();
	avl.Erase("hate");
	avl.InOrder();

}

int main()
{
	//testAVLTree1();
	testAVLTree2();
	return 0;
}

结语

看到这里,这篇博客有关AVL树的相关内容就讲完啦~( ̄▽ ̄)~*

如果觉得对你有帮助的话,希望可以多多支持博主喔(○` 3′○)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值