【C++ 第十三章】AVL 二叉平衡树

news2024/9/20 22:36:58

1. AVL树的概念

        普通二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。

        因此,两位俄罗斯的数学家 G.M.Adelson-Velskii 和 E.M.Landis 在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

即为 一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过 1 (-1/0/1)

子树高度:即 从根节点开始算,一直到最后一层叶子节点 中,一共多少树层

我们本文默认 左右子树高度之差 = 右子树高度 - 左子树高度

(右减左 和 左减右 都一样的,意义一样)

如果一棵二叉搜索树是高度平衡的,它就是AVL树。

如果它有 N 个结点,其高度可保持在 logN 层 ,搜索时间复杂度O(logN)。

下图中数字为  左右子树高度之差


2. AVL树节点 类

节点默认  key/value 键值对模型

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

	pair<K, V> _kv;
	Node* _left;
	Node* _right;
	Node* _parent;
	int _bf; // 存平衡因子:balance factor

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

3. AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:

1. 按照二叉搜索树的方式插入新节点

2. 调整节点的平衡因子

3.1 按照二叉搜索树的方式插入新节点

// 插入
Node* insert(const pair<K, V>& kv) {
	if (_root == nullptr) {
		_root = new Node(kv);
		return _root;
	}

	Node* cur = _root;
	Node* parent = cur;
	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;
		}
	}

	// 在 cur 的位置插入该节点
	cur = new Node(kv);

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

	cur->_parent = parent; // 每个节点连接父节点

	// 更新平衡因子
	// .....

	return _root;
}

3.2 调整节点的平衡因子

前面提过 左右子树高度之差 即为 平衡因子


插入一个节点,会影响子树的高度,因此影响一系列 平衡因子

当前插入一个节点 cur ,其 父节点 parent 和 祖先节点 的 平衡因子 都可能被影响

⭐例如:

1、当在 节点 8 的左边插入一个 节点,节点 8 的 平衡因子变成 0,其他的节点不变

2、当在 节点 4 的右边插入一个 节点,节点 4 的 平衡因子变成 1,节点 3 的 平衡因子变成 0,其他的节点不变


插入节点,会影响部分祖先节点的平衡因子

⭐(1)更新平衡因子

插入在左子树,平衡因子--

插入在右子树,平衡因子++

⭐(2)处理 平衡因子 的 几种情况:

是否继续往上更新祖先,要看 parent 所在子树的高度是否变化

🐵1、 parent 的平衡因子  bf == 0

说明 parent 的平衡因子更新前是 1 or -1,

插入节点插入矮的那边 parent 所在子树的高度不变,

说明刚好平衡,不需要继续往上更新

🐵2. parent 的平衡因子 bf == 1 or -1

说明 parent 的平衡因子更新前是 0:即两边高度一样,子树平衡

插入节点插入在任意一边 parent 所在的子树高度都会变化了

说明刚好打破平衡,继续向上更新

🐵3. parent 的平衡因子== 2 or -2

说明parent的平衡因子更新前是 1 or -1,插入节点插入在高的那边

进一步加剧了parent所在的子树的不平衡,已经违反违规了,

子树失衡,需要旋转处理


🐵4. 其他情况:都是不合理的,直接报错


 

注意看注释理解

// 更新平衡因子
while (parent) {

    // 插入在左边,父亲平衡因子 减减
    // 插入在右边,父亲平衡因子 加加
    if (cur == parent->_left) parent->_bf--;
    else if (cur == parent->_right) parent->_bf++;


    // 若 bf == 0:说明刚好平衡
    // 若 bf == 1 or -1:说明刚好打破平衡,但还算做平衡,继续向上更新
    // 若 bf == 2 or -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);
}

3.3 旋转逻辑

前面 3.2 节中提到,当 插入一个节点 parent 的平衡因子== 2 or -2 时,应该执行旋转

旋转有 四种类型:

RR 型、LL 型 、LR型、RL型

⭐RR 型旋转:左单旋

添加 节点 7 后:节点 3 的平衡因子变成 2

则要对 节点 3 执行旋转操作:因为 节点 5 是 右孩子,节点 6 也是 右孩子 

双 R,即 RR型,向左旋转一次


旋转逻辑 如图示:


动图演示 旋转:

(注:动图取材取自 B站up主:蓝不过海呀 (若侵权即删))

代码示例
// RR型:左单旋
void rotateRR(Node* parent) {
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	Node* parentParent = parent->_parent;

	// 1、subRL 变成 parent 的右孩子
	parent->_right = subRL;
	if (subRL) subRL->_parent = parent;  // subRL 是有可能为 空的


	// 2、parent变成subR的左孩子
	subR->_left = parent;
	parent->_parent = subR;


	// 3、subR变成当前子树的根
	if (parentParent) {
		if (parent == parentParent->_right)
			parentParent->_right = subR;
		else parentParent->_left = subR;

		subR->_parent = parentParent;
	}
	// 如果 parentParent == nullptr:说明 parent 是该树的 _root,_root 的 parent 是空
	else {
		_root = subR;
		subR->_parent = nullptr;
	}


	// 单独处理 平衡因子
	subR->_bf = 0;
	parent->_bf = 0;
}

⭐LL 型旋转:右单旋

添加 节点 2 后:节点 5 的平衡因子变成 -2

节点 5 :左子树高度 = 3 ,右子树高度 = 1 ,则节点 5 的平衡因子 = -2,需要旋转

旋转逻辑 如图示:


动图演示 旋转:

(注:动图取材取自 B站up主:蓝不过海呀 (若侵权即删))

代码示例

其实代码逻辑 和 上面的 RR型就是刚好 镜像相反

// LL型:右单旋
void rotateLL(Node* parent) {
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	Node* parentParent = parent->_parent;

	// 1、subLR变成parent的左孩子
	parent->_left = subLR;
	if (subLR) subLR->_parent = parent;  // subRL 是有可能为 空的 

	// 2、parent变成subL的右孩子
	subL->_right = parent;
	parent->_parent = subL;

	// 3、subL变成当前子树的根
	if (parentParent) {
		if (parent == parentParent->_right)
			parentParent->_right = subL;
		else parentParent->_left = subL;

		subL->_parent = parentParent;
	}
	// 如果 parentParent == nullptr:说明 parent 是该树的 _root,_root 的 parent 是空
	else {
		_root = subL;
		subL->_parent = nullptr;
	}



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

⭐LR 型旋转:subL 先 左旋,parent 再 右旋(先 L 后 R)

旋转逻辑 如图示:


动图演示 旋转:

(注:动图取材取自 B站up主:蓝不过海呀 (若侵权即删))

代码示例

双旋,其实直接复用 前面两个单旋的函数即可,无需自己再实现

关于平衡因子的更新:因为单旋函数中,都是直接将 平衡因子置为 0

       而双旋的平衡因子会改变,因此需要 先旋转后,再添加处理平衡因子的逻辑

各个节点平衡因子的更新数值如何确定:这个直接看双旋后各个节点的平衡因子为多少即可,是固定值

// LR 型:subL 先 左旋, parent 右旋
void rotateLR(Node* parent) {
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;

	rotateRR(parent->_left);
	rotateLL(parent);



	// 双旋后,各个节点平衡因子的更新
	if (bf == -1) {
		parent->_bf = 1;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else if (bf == 1) {
		parent->_bf = 0;
		subL->_bf = -1;
		subLR->_bf = 0;
	}
	else if (bf == 0) {
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else assert(false);
}

⭐RL 型旋转:subR 先 右旋,parent 再 左旋(先 R 后 L)

这个原理也就是上面的 LR 型旋转 刚好镜像相反

动图演示 旋转:

(注:动图取材取自 B站up主:蓝不过海呀 (若侵权即删))

代码示例
// RL 型:subR 先 右旋, parent 左旋
void rotateRL(Node* parent) {
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;

	rotateLL(parent->_right);
	rotateRR(parent);


	// 双旋后,各个节点平衡因子的更新
	if (bf == -1) {
		parent->_bf = 0;
		subR->_bf = 1;
		subRL->_bf = 0;
	}
	else if (bf == 1) {
		parent->_bf = -1;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else if (bf == 0) {
		parent->_bf = 0;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else assert(false);
}

⭐关于 双旋 中 平衡因子的更新逻辑

上面 LR 型旋转 和  RL 型旋转 在 复用单旋的函数后,还需要对 平衡因子进行处理更新

我们以 LR 型旋转为例,解释为什么 可以根据 subRL 的 平衡因子的数值 来区分几种情况

int bf = subLR->_bf;

// 双旋后,各个节点平衡因子的更新
if (bf == -1) {
	parent->_bf = 1;
	subL->_bf = 0;
	subLR->_bf = 0;
}
else if (bf == 1) {
	parent->_bf = 0;
	subL->_bf = -1;
	subLR->_bf = 0;
}
else if (bf == 0) {
	parent->_bf = 0;
	subL->_bf = 0;
	subLR->_bf = 0;
}
else assert(false);

下面三种情况:

初始状态下,parent 和 subL 都是固定相同的唯一不同的是 subLR 的,因此可以以 subLR 作为区分指标

情况一:bf == -1

情况二:bf == 1

情况三:bf == 0




3.4 旋转 逻辑的代码运用  与  相关总结

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

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

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

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

2. parent 的平衡因子为 -2,说明 parent 的左子树高,设 parent 的左子树的根为 subL

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

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

旋转完成后,原 parent 为根的子树个高度降低,已经平衡,不需要再向上更新(因此在旋转逻辑代码中,旋转完直接 break 退出循环)

⭐提炼上面的总结,转换为代码中条件为同号单旋,异号双旋

(注 : bf_P 即为 parent 的平衡因子, bf_C 即为 cur 的平衡因子)

bf_P == 2    &&     bf_C  == 1   :RR型 左单旋

bf_P == -2    &&     bf_C  == -1   :LL型 右单旋

bf_P == -2    &&     bf_C  == 1   :LR型 左右双旋

bf_P == 2    &&     bf_C  == -1   :RL型 右左双旋

更新平衡因子,当 平衡因子 == 2 or -2 时,就要旋转

// 更新平衡因子
while (parent) {
	if (cur == parent->_left) parent->_bf--;
	else if (cur == parent->_right) parent->_bf++;



	// 若 bf == 0:说明刚好平衡
	// 若 bf == 1 or -1:说明刚好打破平衡,但还算做平衡,继续向上更新
	// 若 bf == 2 or -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) {
		// 旋转:同号单旋,异号双旋
		// RR 型:左旋
		if (parent->_bf == 2 && cur->_bf == 1) rotateRR(parent);
		// LL 型:右旋
		if (parent->_bf == -2 && cur->_bf == -1) rotateLL(parent);
		// LR 型:subL 先 左旋, parent 右旋
		if (parent->_bf == -2 && cur->_bf == 1) {
			rotateLR(parent);
		}
		// RL 型:subR 先 右旋, parent 左旋
		if (parent->_bf == 2 && cur->_bf == -1) {
			rotateRL(parent);
		}
		break;  // 旋转完了就要 break 出去,否则会继续向上更新,导致 平衡因子出错
	}
	else assert(false);
}

4. AVL树的性能(讨论)

        AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 O(logN)。

        但是如果要对 AVL 树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时, 有可能一直要让旋转持续到根的位置

        因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。(这种情况就推荐 我们下一个章节学习的 红黑树 )

5. AVL 树总代码:额外添加其他各种功能函数

额外添加:

Size :求二叉树的节点个数

Height:获取该树的高度

IsBalanceTree:判断本树是否平衡

  •         每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
  •         节点的平衡因子是否计算正确

以及 基础函数:拷贝构造、赋值重载、析构函数

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

/*
1、先手搓一棵二叉搜索树
*/

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

	pair<K, V> _kv;
	Node* _left;
	Node* _right;
	Node* _parent;
	int _bf; // 存平衡因子:balance factor

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

template<class K, class V>
class AVLTree
{
public:
	typedef AVLTreeNode<K, V> Node;

	AVLTree() = default;

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

	// 拷贝构造
	AVLTree(const AVLTree<K, V>& t) {
		_root = CopyTree(t._root);
	}

	// 赋值重载
	AVLTree<K, V>& operator=(const AVLTree<K, V>& t) {
		AVLTree tmp(t);
		std::swap(_root, tmp._root);
		return *this;
	}



	// 查找
	bool find(const K& key) const {
		Node* cur = _root;
		while (cur) {
			if ((cur->_kv).first < key) {
				cur = cur->_right;
			}
			else if ((cur->_kv).first > key) {
				cur = cur->_left;
			}
			else return true;
		}
		return false;
	}

	// 插入
	Node* insert(const pair<K, V>& kv) {
		if (_root == nullptr) {
			_root = new Node(kv);
			return _root;
		}

		Node* cur = _root;
		Node* parent = cur;
		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;
			}
		}

		// 在 cur 的位置插入该节点
		cur = new Node(kv);
		
		if ((parent->_kv).first > kv.first) parent->_left = cur;
		else  parent->_right = cur;

		cur->_parent = parent; // 每个节点连接父节点

		// 更新平衡因子
		while (parent) {
			if (cur == parent->_left) parent->_bf--;
			else if (cur == parent->_right) parent->_bf++;

			// 若 bf == 0:说明刚好平衡
			// 若 bf == 1 or -1:说明刚好打破平衡,但还算做平衡,继续向上更新
			// 若 bf == 2 or -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) {
				// 旋转:同号单旋,异号双旋
				// RR 型:左旋
				if (parent->_bf == 2 && cur->_bf == 1) rotateRR(parent);
				// LL 型:右旋
				if (parent->_bf == -2 && cur->_bf == -1) rotateLL(parent);
				// LR 型:subL 先 左旋, parent 右旋
				if (parent->_bf == -2 && cur->_bf == 1) {
					rotateLR(parent);
				}
				// RL 型:subR 先 右旋, parent 左旋
				if (parent->_bf == 2 && cur->_bf == -1) {
					rotateRL(parent);
				}
				break;  // 旋转完了就要 break 出去,否则会继续向上更新,导致 平衡因子出错
			}
			else assert(false);
		}

		return _root;
	}
	

	// RR型:左单旋
	void rotateRR(Node* parent) {
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* parentParent = parent->_parent;

		// 1、subRL 变成 parent 的右孩子
		parent->_right = subRL;
		if (subRL) subRL->_parent = parent;  // subRL 是有可能为 空的


		// 2、parent变成subR的左孩子
		subR->_left = parent;
		parent->_parent = subR;


		// 3、subR变成当前子树的根
		if (parentParent) {
			if (parent == parentParent->_right)
				parentParent->_right = subR;
			else parentParent->_left = subR;

			subR->_parent = parentParent;
		}
		// 如果 parentParent == nullptr:说明 parent 是该树的 _root,_root 的 parent 是空
		else {
			_root = subR;
			subR->_parent = nullptr;
		}

		
		// 单独处理 平衡因子
		subR->_bf = 0;
		parent->_bf = 0;
	}

	// LL型:右单旋
	void rotateLL(Node* parent) {
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* parentParent = parent->_parent;

		// 1、subLR变成parent的左孩子
		parent->_left = subLR;
		if (subLR) subLR->_parent = parent;  // subRL 是有可能为 空的 

		// 2、parent变成subL的右孩子
		subL->_right = parent;
		parent->_parent = subL;

		// 3、subL变成当前子树的根
		if (parentParent) {
			if (parent == parentParent->_right)
				parentParent->_right = subL;
			else parentParent->_left = subL;

			subL->_parent = parentParent;
		}
		// 如果 parentParent == nullptr:说明 parent 是该树的 _root,_root 的 parent 是空
		else {
			_root = subL;
			subL->_parent = nullptr;
		}

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

	// LR 型:subL 先 左旋, parent 右旋
	void rotateLR(Node* parent) {
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		rotateRR(parent->_left);
		rotateLL(parent);



		// 双旋后,各个节点平衡因子的更新
		if (bf == -1) {
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == 1) {
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == 0) {
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else assert(false);
	}

	// RL 型:subR 先 右旋, parent 左旋
	void rotateRL(Node* parent) {
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		rotateLL(parent->_right);
		rotateRR(parent);


		// 双旋后,各个节点平衡因子的更新
		if (bf == -1) {
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 1) {
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == 0) {
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else assert(false);
	}

	// 删除分三种情况
	// 1、节点没有孩子
	// 2、节点只有一个孩子:分是左孩子,还是右孩子
	// 3、节点有两个孩子

	// 删除默认删除中序遍历第一个和数值相等的节点
	Node* erase(const K& key) {
		if (_root == nullptr) {
			cout << "整棵树已被删除" << '\n';
			return _root;
		}

		// 看这个元素是否存在
		if (!find(key)) {
			cout << "节点不存在" << '\n';
			return _root;  // 我们这里删除操作失败,也返回 原树
		}


		
		// 查找操作
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur) {
			if ((cur->_kv).first < key) {
				parent = cur;
				cur = cur->_right;
			}
			else if ((cur->_kv).first > key) {
				parent = cur;
				cur = cur->_left;
			}
			else break;
		}

		// 删除操作
		if (cur->_left == nullptr) {
			if (parent == nullptr) {
				_root = cur->_right;
			}
			else {
				if ((parent->_kv).first < key) {
					parent->_right = cur->_right;
				}
				else parent->_left = cur->_right;
			}
		
			delete cur;
			cur = nullptr;
		}
		else if (cur->_right == nullptr) {
			if (parent == nullptr) {
				_root = cur->_left;
			}
			else {
				if ((parent->_kv).first < key) {
					parent->_right = cur->_left;
				}
				else parent->_left = cur->_left;
			}
			delete cur;
			cur = nullptr;
		}
		else {
			// 取左子树最大值:即左子树的最右边的节点
			// 取右子树最小值:即右子树的最左边的节点
			// 我们这里采取第二种方法

			// 不断向左遍历
			Node* MinRight = cur->_right;
			Node* MinRight_parent = cur;
			while (MinRight->_left) {  // 这里很奇怪
				MinRight_parent = MinRight;
				MinRight = MinRight->_left;
			}
			
			if ((MinRight_parent->_kv).first < (MinRight->_kv).first) {
				MinRight_parent->_right = MinRight->_right;
			}
			else MinRight_parent->_left = MinRight->_right;

			(cur->_kv).first = (MinRight->_kv).first;
			
			delete MinRight;
		}


		return _root;
	}

	// 中序遍历
	void InOrder() {
		_InOrder(_root);
		cout << '\n';
	}

	
	// 获取根节点
	Node* GetRoot() {
		return _root;
	}

	// 获取该树的高度
	void Height() {
		return _Height(_root);
	}
	// 本树是否平衡
	bool IsBalanceTree() {
		return _IsBalanceTree(_root);
	}
	// 获取节点个数
	int Size() {
		return _Size(_root);
	}

private:
	int _Size(Node* pRoot) {
		if (pRoot == nullptr) return 0;
		//if (pRoot->_left == nullptr && pRoot->_right == nullptr) return 1;

		return 1 + _Size(pRoot->_left) + _Size(pRoot->_right);
	}
	int _Height(Node* pRoot) {
		if (pRoot == nullptr)
			return 0;

		return 1 + max(_Height(pRoot->_left), _Height(pRoot->_right));
	}
	bool _IsBalanceTree(Node* pRoot)
	{
		// 空树也是AVL树
		if (nullptr == pRoot) return true;

		// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差
		int leftHeight = _Height(pRoot->_left);
		int rightHeight = _Height(pRoot->_right);
		int diff = rightHeight - leftHeight;

		// 这个判断平衡因子的方法 直接 帮我检查出 之前没写 break 导致平衡因子自己更新
		// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者
		// pRoot平衡因子的绝对值超过1,则一定不是AVL树
		if (diff != pRoot->_bf || (diff > 1 || diff < -1))
			return false;
		// pRoot的左和右如果都是AVL树,则该树一定是AVL树
		return _IsBalanceTree(pRoot->_left) && _IsBalanceTree(pRoot->_right);
	}


	// 销毁一棵树:后序遍历
	void destory(Node* root) {
		if (root == nullptr) {
			return;
		}

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

	// 拷贝一棵树
	Node* CopyTree(const Node* root) {
		if (root == nullptr) {
			return nullptr;
		}

		Node* newRoot = new Node(root->_kv);
		newRoot->_left = CopyTree(root->_left);
		newRoot->_right = CopyTree(root->_right);
		return newRoot;
	}

	void _InOrder(const Node* root) {
		if (root == nullptr) {
			return;
		}
		_InOrder(root->_left);
		cout << (root->_kv).first  << " : " << (root->_kv).second << '\n';
		_InOrder(root->_right);
	}

	Node* _root = nullptr;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2054936.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【故障处理】- ping不通的原因

PING不通是一个非常常见的网络问题&#xff0c;它可能由多种原因引起。如链路故障、ARP学习失败等 以一个Ping不通的尝试示例&#xff0c;介绍Ping不通故障的定位思路。如下图&#xff1a; PC3 Ping不通PC4 PC>ping 20.1.1.20Ping 20.1.1.20: 32 data bytes, Press Ctrl_C…

产品经理-​​实习中的自我迭代(41)

实习中的自我迭代,优秀实习生必备素质 跟大家认识了之后&#xff0c;就要开始做事情了&#xff0c;那我们怎么做一个优秀的实习生呢&#xff1f;以下几点作为参考。 1. 目标明确 知道自己的工作为什么要做&#xff0c;要做到什么程度&#xff0c;目前存在什么问题&#xff0c;该…

初探:c++异步编程之std::promise和std::future【异步数据获取】

c异步编程之std::promise和std::future 1.std::future获取std::asnyc结果2.模拟一个异步函数接口i.模拟一个客户端类包含异步请求接口ii.调用异步接口获取结果 c11以后标准库提供了thread&#xff0c;说起异步可能会第一时间想起thread&#xff0c;线程确实好东西&#xff0c;不…

2023年人均GDP百强市分布图

2023年人均GDP百强市分布图

redis安装,redis的数据类型和使用场景,Redis事务,Redis持久化,Redis淘汰策略

Redis简介 https://redis.io/docs/data-types/ Redis&#xff08;Remote Dictionary Server )远程字典服务&#xff0c;是一个开源的使用ANSI C语言编写、支持网络、可基于内存也可持久化的日志型、Key-Value(NoSQL)数据库。 Redis的特点 性能极高&#xff0c;基于内存&…

ORACLE ADG 主库的归档日志不能主动传递到备库

主库有三个节点 &#xff0c;其中两个节点传递没有问题&#xff0c;唯独节点二的归档日志不能主动传递到备库&#xff0c;都是在备库恢复需要的时候一个个传递到备库。下面是备库的日志。 Media Recovery Waiting for thread 2 sequence 1204582 …

SAP和致远OA系统集成案例

一、项目介绍 重庆某控股&#xff08;集团&#xff09;有限公司是一家集合汽柴油动力及终端、摩托车、储能电源、汽车零部件、金融服务等产业的多元化集团公司&#xff0c;业务遍布全球80多个国家及地区&#xff0c;2021年营业收入达80亿元。 为推动集团信息化、数字化转型…

基于WonderJourney生成电影级连续的3D场景视频

在本文中,我将详细记录在Windows环境下配置和使用WonderJourney项目的完整流程,包括环境搭建、常见问题的解决方案以及如何修改源码以兼容Windows系统。WonderJourney项目能够生成高度逼真的村庄视频,并允许用户通过配置文件对视频生成过程进行精细化控制。 由于官方文档在…

选型指南:CNAPP能力成熟度评估Checklist

随着云计算服务大量使用&#xff0c;网络攻击面的不断扩大&#xff0c;那些过去为传统数据中心而设计的安全工具和运营流程将很难应对云端的安全威胁。相比过去&#xff0c;安全团队现在面临超过10到100倍的容器化保护需求&#xff0c;大量的动态云资产需要追踪&#xff0c;同时…

MySQL 在 Windows 和 Ubuntu 上的安装与远程连接配置简介

MySQL 是一个广泛使用的开源关系型数据库管理系统&#xff0c;它提供了多用户、多线程的数据库服务。本文将介绍如何在 Windows 和 Ubuntu 操作系统上安装 MySQL&#xff0c;并配置远程连接。 Windows 上的 MySQL 安装 1. 下载 MySQL Installer 访问 MySQL 官方网站下载 Win…

c++--类(上)

C之类&#xff08;上&#xff09; 一、类的定义1.1 类定义格式1.2 访问限定符1.3 类域 二、实例化2.1 实例化的概念2.2 对象大小 三、this指针 一、类的定义 1.1 类定义格式 1、class为定义类的关键字&#xff0c;{}中为类的主体&#xff0c;注意类定义结束时后⾯分号不能省略…

Ollama - Llama3 docker版本安装部署使用

项目地址&#xff1a;https://github.com/meta-llama/llama3 Meta 发布两款开源Llama 3 8B与Llama 3 70B模型&#xff0c;供外部开发者免费使用。Llama 3的这两个版本&#xff0c;也将很快登陆主要的云供应商。 按照Meta的说法&#xff0c;Llama 3 8B和Llama 3 70B是目前同体量…

threejs中实现物体阴影

在Three.js中实现阴影需要几个步骤&#xff0c;包括设置渲染器、光源以及物体的材质等。以下是一个基本的实现阴影的步骤&#xff1a; 1、设置渲染器以支持阴影&#xff1a; const renderer new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerH…

三大运营管理平台:打造智能化新能源数据管理的核心利器

随着全球能源结构的转型和新能源技术的快速发展&#xff0c;智能化新能源数据管理成为行业发展的关键。三大运营管理平台的出现&#xff0c;正是为了解决这一需求&#xff0c;它们通过整合先进的信息技术和智能算法&#xff0c;为新能源企业提供了全面、高效、精准的数据管理解…

告别帕金森手抖,这些维生素是你的秘密武器!

亲们&#xff0c;你们有没有遇到过这样的情况&#xff1f;家里的长辈或是自己&#xff0c;偶尔会出现手不自觉颤抖的现象&#xff0c;特别是被诊断为帕金森病的朋友&#xff0c;更是深受其扰。&#x1f614; 别担心&#xff0c;今天就来聊聊如何通过科学补充一些关键维生素&…

函数:02

1.三角函数 名称表达式正弦sinx b r \frac{b}{r} rb​余弦cosx a r \frac{a}{r} ra​正切tanx b a \frac{b}{a} ab​余切cotx a b \frac{a}{b} ba​正割secx r a \frac{r}{a} ar​余割cotx r b \frac{r}{b} br​ 1.1用正弦&#xff0c;余弦函数表示正/余切&#xff0c;正/余割…

Web大学生网页作业成品——保护环境环保介绍网页设计与实现(HTML+CSS)(1个页面)

&#x1f389;&#x1f389;&#x1f389; 常见网页设计作业题材有**汽车、环保、明星、文化、国家、抗疫、景点、人物、体育、植物、公益、图书、节日、游戏、商城、旅游、家乡、学校、电影、动漫、非遗、动物、个人、企业、美食、婚纱、其他**等网页设计题目, 可满足大学生网…

20221元组

在Python语言中, (7)是一种可变的、有序的序列结构,其中元素可以重复。 A.元组(tuple) B. 字符串(str) C. 列表(list) D.集合(set) ChatGPT 说&#xff1a; ChatGPT 在Python中&#xff0c;选项 C 列表(list) 符合题目描述。 解释&#xff1a; 列表 (list) 是一种可变的、有…

OOP篇(Java - 思维逻辑练习)(doing)

目录 一、继承 1. 简介 2. 表现形式 2.1. 电脑的表现形式 2.2. 程序的表现形式 资料库 课程 课程放入到资料库 视频资源 资源库 存在问题 如何解决问题 3. 继承 定义父类 课程类继承 视频类继承item 资源库类2 整个的继承关系 4. 通过继承得到了什么&#x…

基于spring boot的酒店管理系统

获取源码联系方式请查看文章结尾&#x1f345; 目 录 基于spying boot的酒店管理系统 ABSTRACT 第一章 绪论 1.1课题背景 1.2研究意义 1.3研究内容 第二章 技术介绍 2.1相关技术 2.2java技术 2.3MySQL数据库 2.4 Tomcat介绍 2.5 SSM框架 第3章 需求分析 3.1需求分…