【C++】AVL树和红黑树模拟实现

news2024/11/19 4:36:59

AVL树和红黑树

  • 1. 背景
  • 2. AVL树的概念
  • 3. AVL树节点的定义
  • 4. AVL树的插入
  • 5. AVL树的旋转
    • 5.1. 左单旋
    • 5.2. 右单旋
    • 5.3. 左右单旋
    • 5.4. 右左单旋
    • 5.5. 旋转总结
  • 6. AVL树的验证
  • 7. AVL树的性能
  • 8. 红黑树的概念
  • 9. 红黑树的节点的定义
  • 10. 红黑树的插入
    • 10.1. 情况一
    • 10.2.情况二
  • 11. 红黑树的验证
  • 12. 红黑树与AVL树的比较

1. 背景

  1. set、map、multiset、multimap,这几个关联式容器的共同点是:底层都是用二叉搜索树来实现的,但二叉搜索树自身有缺陷,若插入的元素有序或者接近有序,二叉搜索树就会退化为单支树,查询时相当于在顺序表中进行查询,效率低下,时间复杂度退化为O(n),因此map、set等关联式容器的底层结构对二叉搜索树进行了平衡处理,即采用平衡树来实现。

  2. 通过保证每个节点的左、右子树高度差不超过1,对树中的节点进行调整,即可降低树的高度,从而减少树的搜索长度,时间复杂度为O(longn)。

2. AVL树的概念

  1. AVL树具有的性质:a. 每个节点的左右子树都是AVL树, b. 每个节点的左右子树高度差(平衡因子)的绝对值不超过1(0、-1、1)。

  2. 平衡因子:右子树高度-左子树高度,不是必须存在的,只是一种控制方式,帮助我们更便捷的++控制树,检查是否为AVL树。

image.png

  • 如果一颗二叉搜索树高度是平衡的,那么它就是AVL树,如果它有n个节点,它的高度就是longn,则搜索的时间复杂度为O(longn)。

  • 无法做到平衡因子的值恒为0,因为二叉树插入节点是一个一个插入进去的,对于2个或者4个节点的树是无法做到左右子树高度差等于0,最优的高度差就是1。

3. AVL树节点的定义

template<class K, class V>   //AVL树的节点(KV模型)
struct AVLTreeNode{  
	typedef AVLTreeNode<K, V> Node;  

	Node* _left;      //该节点的左孩子
	Node* _right;    //该节点的右孩子
	Node* _parent;  //该节点的父亲,便于向上更新
	int _bf;       //该节点的平衡因子
	pair<K, V> _kv;  

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

4. AVL树的插入

方法:先按二叉搜索树的方式插入新节点,在更新平衡因子。

AVL的插入.png

void RotateL(Node* parent)  //右右—左单旋
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	Node* pphead = parent->_parent;
    
	parent->_right = subRL;   //b变成父节点的右孩子
	if (subRL)   //当前节点的左孩子可能存在,也可能不存在  h=0,b=0
		subRL->_parent = parent;   //更新当前节点右孩子的父亲
	subR->_left = parent;   //父节点变为当前节点的左孩纸
	parent->_parent = subR;   //更新父节点的父亲
    
	//更新当前节点的父亲,父节点可能为根节点,也可能为根节点
	if (parent == _root)  //根节点 
	{
		_root = subR;
		subR->_parent = nullptr; //更新当前节点的父亲
	}
	else
	{
		if (pphead->_left == parent)   //子树
			pphead->_left = subR;
		else
			pphead->_right = subR;

		subR->_parent = pphead; //更新当前节点的父亲
	}

	subR->_bf = 0;  //更新平衡因子
	parent->_bf = 0;
}

void RotateR(Node* parent)  //左左—右单旋
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	Node* pphead = parent->_parent;

	parent->_left = subLR;
	if (subLR)
		subLR->_parent = parent;
	subL->_right = parent;
	parent->_parent = subL;

	if (parent == _root)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (pphead->_left == parent)
			pphead->_left = subL;
		else
			pphead->_right = subL;

		subL->_parent = pphead;
	}

	subL->_bf = 0;  //更新平衡因子
	parent->_bf = 0;
}

void RotateRL(Node* parent)  //右左—先右旋再左旋
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	Node* bf = subRL->_bf;   //因为旋转后会改变bf,需要提前记录

	RotateR(subR); 
	RotateL(parent);

	subRL->_bf = 0;   //更新平衡因子
	if (bf == 0) //情况一:当h = 0,subRL为新增节点
	{
		subR->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == -1) //情况二:当h > 0,b插入,b的高度变为h,引发旋转
	{
		subR->_bf = 1;
		parent->_bf = 0;
	}
	else if(bf == 1) //情况三:当h > 0,c插入,c的高度变为h,引发旋转
	{
		subR->_bf = 0;
		parent->_bf = -1;
	}
	else  //平衡因子异常
	{
		assert(false);
	}
}

void RotateLR(Node* parent)  //左右—先左旋再右旋
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	int bf = subLR->_bf;

	RotateL(subL);
	RotateR(parent);
    
	subLR->_bf = 0; //更新平衡因子
	if (bf == 0)
	{
		subLR->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 1)
	{
		subL->_bf = -1;
		parent->_bf = 0;
	}
	else if(bf == -1)
	{
		subL->bf = 0;
		parent->_bf = 1;
	}
	else
	{
    	assert(false);
	}
}

bool insert(const pair<K, V>& kv)  //插入
{
	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 (parent == nullptr) //空树
	{
		_root = cur;
		return true;
	}

	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;  //记录当前节点的父亲

	while (parent)  //更新结束: 情况1,更新到根节点
	{
		if (parent->_left == cur) //再更新平衡因子
			parent->_bf--;
		else
			parent->_bf++;

		if (parent->_bf == 0) //更新结束: 情况2,p的高度不变,不会影响上层的bf
			break;
		else if (parent->_bf == 1 || parent->_bf == -1) //更新继续:p的高度改变,会影响上层的bf,但未违反平衡树性质
		{
			cur = cur->_parent;
			parent = parent->_parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2) //违反了平衡树的规则,做处理-》旋转 ->更新结束
		{   //注意:以下4种旋转,只要完成了某一种,都会更新结束,需要break
			if (parent->_bf == 2 && cur->_bf == 1) //插入到较高右子树的右边
			{
				RotateL(parent); //右右—左单旋
				break;  
			}
			else if (parent->_bf == -2 && cur->_bf == -1) //插入到较高左子树的左边
			{
				RotateR(parent);  //左左—右单旋
				break;
			}
			else if (parent->_bf == 2 && cur->_bf == -1)  //插入到较高右子树的左边
			{ 
				RotateRL(parent);  //右左—先右旋再左旋
				break;
			}
			else if (parent->_bf == -2 && cur->_bf == 1)  //插入到较高左子树的右边
			{ 
				RotateLR(parent);  //左右—先左旋再右旋
				break;
			}
			else   //在插入前就不是AVL树    必须保证再插入前是一颗AVL树 
			{
				assert(false);
				break;
			}
		}
	}
}

5. AVL树的旋转

5.1. 左单旋

void RotateL(Node* parent)  //右右—左单旋
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	Node* pphead = parent->_parent; 
    
	parent->_right = subRL;   //b变成父节点的右孩子
	if (subRL)   //当前节点的左孩子可能存在,也可能不存在  h=0,b=0
		subRL->_parent = parent;   //更新当前节点右孩子的父亲
	subR->_left = parent;   //父节点变为当前节点的左孩纸
	parent->_parent = subR;   //更新父节点的父亲

	//更新当前节点的父亲,父节点可能为根节点,也可能为根节点
	if (parent == _root)  //根节点 
	{
		_root = subR;
		subR->_parent = nullptr; //更新当前节点的父亲
	}
	else
	{
		if (pphead->_left == parent)   //子树
			pphead->_left = subR;
		else
			pphead->_right = subR;

		subR->_parent = pphead; //更新当前节点的父亲
	}
    subL->_bf = 0;  //更新平衡因子
	parent->_bf = 0;
}

左单旋.png

5.2. 右单旋

void RotateR(Node* parent)  //左左—右单旋
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	Node* pphead = parent->_parent;

	parent->_left = subLR;
	if (subLR)
		subLR->_parent = parent;
	subL->_right = parent;
	parent->_parent = subL;
    
	if (parent == _root)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (pphead->_left == parent)
			pphead->_left = subL;
		else
			pphead->_right = subL;

		subL->_parent = pphead;
	}
	subL->_bf = 0;  //更新平衡因子
	parent->_bf = 0;
}

image.png

5.3. 左右单旋

void RotateLR(Node* parent)  //左右—先左旋再右旋
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
    
	int bf = subLR->_bf;

	RotateL(subL);
	RotateR(parent);

	subLR->_bf = 0; //更新平衡因子
	if (bf == 0)
	{
		subLR->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 1)
	{
		subL->_bf = -1;
		parent->_bf = 0;
	}
	else if(bf == -1)
	{
		subL->bf = 0;
		parent->_bf = 1;
	}
	else
	{
		assert(false);
	}
}

左右旋.png

5.4. 右左单旋

void RotateRL(Node* parent)  //右左—先右旋再左旋
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	Node* bf = subRL->_bf;   //因为旋转后会改变bf,需要提前记录

	RotateR(subR); 
	RotateL(parent);

	subRL->_bf = 0;  //更新平衡因子
	if (bf == 0) //情况一:当h = 0,subRL为新增节点
	{
		subR->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == -1) //情况二:当h > 0,b插入,b的高度变为h,引发旋转
	{
		subR->_bf = 1;
		parent->_bf = 0;
	}
	else if(bf == 1) //情况三:当h > 0,c插入,c的高度变为h,引发旋转
	{
		subR->_bf = 0;
		parent->_bf = -1;
	}
	else  //平衡因子异常
	{
		assert(false);
	}
}

AVL右左双旋.png

5.5. 旋转总结

image.png

6. AVL树的验证

方法:先验证其为二叉搜索树(中序遍历得到有序序列),再验证其为平衡树(左右子树高度差的绝对值不超过1、平衡因子正常)。

int _Height(Node* root) //后序遍历
{
	if (root == nullptr)
		return 0;

	int leftHeight = _Height(root->_left);
	int rightHeight = _Height(root->_right);
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

bool _Isbalance(Node* root) //前序遍历  验证其为平衡树
{
	if (root == nullptr)
		return true;

	int leftHeight = _Height(root->_left);
	int rightHeight = _Height(root->_right);
	if (abs(rightHeight - leftHeight) >= 2)
	{
		cout << "不平衡" << endl; 
		return false;
	}

	if (rightHeight - leftHeight != root->_bf)
	{
		cout << root->_kv.first << ":平衡因子异常" << endl;
		return false;
	}
	return _Isbalance(root->_left) && _Isbalance(root->_right);
}

void _Inorder(Node* root) //中序遍历 验证其为二叉搜索树
{
	if (root == nullptr)
		return;

	_Inorder(root->_left);
	cout << root->_kv.first << "[" << root->_bf << "]" << endl;
	_Inorder(root->_right);
}
  • 不能采用层序遍历、看bf的值,原因:对于完全二叉树、满二叉树通过层序遍历可以推出树的正确结构,AVL树不一定为完全、满二叉树。在旋转时,bf可能更新异常,此时看bf的值,会造错误构。

  • 验证其为平衡二叉树,采用前序遍历,缺点:先求高度,再判平衡,递归式判子树平衡,导致重复求子树的高度,效率低——》解决方法:将前序改成后序,并将高度作为引用参数(引用,各个栈帧相互影响),边判平衡边求高度。

bool _Isbalance(Node* root, int& height) //后序遍历+引用参数
{
	if (root == nullptr) //空树也是AVL树
	{
		height = 0;
		return true;
	}

	int leftHeight = 0, rightHeight = 0;
	if(!_Isbalance(root->_left, leftHeight) || !_Isbalance(root->_right, rightHeight)) 
		return false;  //只要左右子树有一颗不是平衡树,该树就不是平衡树

	height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	return true;
}
int main()
{
	int a[] = { 8, 5, 2, 6, 3, 7, 4, 1 };
	AVLTree<int, int> t;
	for (auto& e : a)
	{
		t.insert(make_pair(e, e));
	}
	t.Inorder();
	cout << t.Isbalance() << endl;
	return 0;
}

image.png
image.png

7. AVL树的性能

  • AVL是一颗高度平衡的二叉搜索树,查询时效率高,时间复杂度为O(longn),但如果要对AVL树进行结构修改操作,性能非常低,如:插入时,为了维持平衡需要进行多次旋转,删除可能旋转要持续到根节点位置处。

  • 如果需要一种查询高效且数据有序的数据结构,数据的个数为静态的(不会插入元素,结构不会发生改变),可以考虑使用AVL树。如果一个结构需要经常的修改,AVL树不太适用。

8. 红黑树的概念

  1. 红黑树的概念:红黑树,是一种二叉搜索树,在每个节点上增加一个存储位来表示节点的颜色,red或者black,通过对每条路径上节点着色方式的控制,红黑树保证了最长路径不超过最短路径的二倍,其他路径的长度位于最短路径长度和最长路径长度之间,即:每条路径不会比其他路径长出两倍,因此它接近于平衡。

  2. 红黑树的性质
    a.每个节点的颜色不是红色就是黑色 ;
    b.根节点的颜色一定是黑色 ;
    c.如果有一个节点的颜色是红色,则它的孩子节点的颜色一定是黑色,即:没有连续的红色节点;
    d.每条路径都具有相同数量的黑色节点。
    e.每个叶子节点都是黑色的,叶子节点指的是空节点,又名为NIL节点,是为了避免路径(根节点到空节点)数错的误区。

  3. 问:只要满足以上的性质,为什么红黑树就能保证最长路径中节点个数不超过最短路径中节点个数的两倍?
    答:因为在极端情况下,最短路径:全黑,最长路径:一黑一红。

image.png

9. 红黑树的节点的定义

enum Color {  //枚举,一一列举出事物具有的所有可能
	Red,  //枚举常量,给枚举变量进行赋值
	Black,
};

template<class K, class V>//红黑树的节点(KV模型)
struct RBTreeNode {
	typedef RBTreeNode<K, V> Node;

	Node* _left;      //该节点的左孩子
	Node* _right;    //该节点的右孩子
	Node* _parent;  //该节点的父亲,便于向上更新
	pair<K, V> _kv;   
	Color _col;

	RBTreeNode(const pair<K, V>& kv, Color col = Red)  //构造函数
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(col)   //默认新插入节点的颜色为红色
	{ }
};

10. 红黑树的插入

方法:先按照二叉搜素树的方式插入,在更新颜色。

红黑树的插入.png

void RotateL(Node* parent)  //右右—左单旋
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
    
    Node* pphead = parent->_parent;

	parent->_right = subRL;  
	if (subRL)   
		subRL->_parent = parent;  
	subR->_left = parent;  
	parent->_parent = subR;   

		
	if (parent == _root) 
	{
		_root = subR;
		subR->_parent = nullptr; 
	}
	else
	{
		if (pphead->_left == parent) 
			pphead->_left = subR;
		else
			pphead->_right = subR;

    	subR->_parent = pphead;
	}
}

void RotateR(Node* parent)  //左左—右单旋
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	Node* pphead = parent->_parent;

	parent->_left = subLR;
	if (subLR)
		subLR->_parent = parent;
	subL->_right = parent;
	parent->_parent = subL;

	if (parent == _root)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (pphead->_left == parent)
			pphead->_left = subL;
		else
			pphead->_right = subL;

		subL->_parent = pphead;
	}
}

void RotateRL(Node* parent)  //右左—先右旋再左旋
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
    
	RotateR(subR);
	RotateL(parent);
}

void RotateLR(Node* parent)  //左右—先左旋再右旋
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	RotateL(subL);
	RotateR(parent);
}

bool insert(const pair<K, V>& kv)  //插入
{
	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 (parent == nullptr) //空树
	{
		_root = cur;
        _root->_col = Black;  //跟节点为黑
		return true;
	}

	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;  //记录当前节点的父亲

	//更新颜色
	//插入结束:1.插入节点的父亲是黑色,因为插入前该树就为红黑树 2.情况一处理完后,cur为根节点,且为黑色
	while (parent && parent->_col == Red) 
	{ //爷爷一定存在,因为c为红,p为红,所以p一定不是根节点,且一定有父节点
		Node* grandfather = parent->_parent; 
		if (parent == grandfather->_left)  //旋转需要确定方向
		{
			Node* uncle = grandfather->_right;  
			if (uncle && uncle->_col == Red) //情况一:叔叔存在且为红->无方向(p、u为g的任意边,c为p的任一边)
			{  //cur可能为新增节点,也可能一开始为黑色,cur的子树(下一层为红,下一层为新插入节点)在调整过程中将cur由黑变为红
				parent->_col = uncle->_col = Black; //p、u变为黑,g变为红
				grandfather->_col = Red;
				//g可能为根节点(更新结束),也可能为子树(继续向上更新)
				cur = grandfather;   
				parent = cur->_parent; 
			}
			else  //情况二:叔叔不存在 或者 叔叔存在且为黑
			{  //叔叔不存在,cur为新增节点 或 cur原来为黑,经子树调整由黑变红
				if (parent->_left == cur)  //左左——右单旋
				{
					RotateR(grandfather);
    				parent->_col = Black; //p变为黑,g变为红
					grandfather->_col = Red;
				}
				else    //左右——左右单旋 
				{
					RotateL(parent); 
					RotateR(grandfather);
					cur->_col = Black;  //c变黑,g变红
					grandfather->_col = Red;
				}
				break;  //更新结束:3.旋转+颜色处理后就是红黑树了
			}
		}
		else
		{
			Node* uncle = grandfather->_left;
			if (uncle && uncle->_col == Red)
			{
				parent->_col = uncle->_col = Black;
				grandfather->_col = Red;

				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				if (parent->_right == cur)  //右右——左单旋
				{
					RotateL(grandfather);
					parent->_col = Black;
					grandfather->_col = Red;
				}
				else   //右左——右左单旋
				{
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = Black;
					grandfather->_col = Red;
				}
				break;  
			}
		}
	}
	_root->_col = Black;  //g为根,颜色变为黑,更新结束
    return true;  //情况一,插入节点的父亲为黑,插入结束
}

10.1. 情况一

  1. 叔叔存在且为红色——将p、u变为黑色,g变为红色,在把g给c。若c为根节点,将c变为黑色,插入结束(break)。若c不为根节点,继续向上更新颜色,直到c为根 或则 c的父亲为黑色。

  2. 无方向——p、u是g的左右都可以,c是p的左右都可以,处理方式都是一样的。

if (uncle && uncle->_col == Red) //情况一:叔叔存在且为红->无方向(p、u为g的任意边,c为p的任一边)
{  //cur可能为新增节点,也可能一开始为黑色,cur的子树(下一层为红,下一层为新插入节点)在调整过程中将cur由黑变为红
	parent->_col = uncle->_col = Black; //p、u变为黑,g变为红
	grandfather->_col = Red;

	//g可能为根节点(更新结束),也可能为子树(继续向上更新)
	cur = grandfather;   
	parent = cur->_parent; 
}

红黑树插入情况一.png

10.2.情况二

  1. 叔叔不存在 或者 叔叔存在且为黑色——旋转(有方向,4种) + 颜色处理。

  2. a. p为g的左孩子,c为p的左孩子,左左,右单旋。b. p为g的右孩子,c为p的右孩子,右右,左单旋,将p变为黑色,将g变为红色—》将p变为黑色,将g变为红色。c. p为g的左孩子,c为p的右孩子,左右旋。d. p为g的右孩子,c为p的左孩子,右左旋——》将c变为黑色,g变为红色。

  3. 若叔叔不存在,c只可以是新增节点,因为若c不是新增节点,该树最短路径长度为1,最长路径长度为4,在插入前就已经违反了红黑树性质3。若叔叔存在且为红,c只能是原来为黑色,经子树调整由黑色变为红色。

  4. 情况二在插入前最短路径长度:最短路径长度=1:2,插入后,违反了红黑树性质4,所以需要做旋转处理。

if (parent->_left == cur)  //左左——右单旋
{
	RotateR(grandfather);
	parent->_col = Black; //p变为黑,g变为红
	grandfather->_col = Red;
}
else    //左右——左右单旋 
{
	RotateL(parent); 
	RotateR(grandfather);
	cur->_col = Black;  //c变黑,g变红
	grandfather->_col = Red;
}
break;  //更新结束:3.旋转+颜色处理后就是红黑树了

红黑树插入情况二.png

11. 红黑树的验证

方法:先验证其为二叉搜索树(中序遍历得到有序序列),再验证其是否满足红黑树的性质。

void Inorder() //验证其为二叉搜索树,中序遍历
{
	_Inorder(_root);
}

bool Balance() //验证其是否满足红黑树的性质
{
	if (_root && _root->_col == Red) //红黑树性质2,跟节点为黑色
		return false;

	int refvalue = 0;  //红黑树性质4,每条路径具有相同数量的黑节点
	Node* cur = _root; 
	while (cur)
	{
		if (cur->_col == Black)
			refvalue++;   //找参考值
		cur = cur->_left;
	}

	_Balance(_root, 0, refvalue);
}

//前序遍历,将每条路径黑色节点的数量于参考值比较,验证性质4
bool _Balance(Node* root, int blackcount, int& refvalue)
{
	if (root == nullptr)
	{
		if (blackcount != refvalue)
			return false;

		return true;
    }

	if (root->_col == Red && root->_parent->_col == Red)
		return false;   //验证红黑性质3,没有连续的红节点,孩子找爹

	if (root->_col == Black) //blackcount没有用引用,因为下层不能影响上次,blackcount表示的是当前节点到根节点路径上黑色节点的数量
    	blackcount++;
    return _Balance(root->_left, blackcount, refvalue) && _Balance(root->_right, blackcount, refvalue); //放在后面,从前往后
}

void _Inorder(Node* root) //中序遍历
{
	if (root == nullptr)
		return;

	_Inorder(root->_left);
	cout << root->_kv.first <<  endl;
	_Inorder(root->_right);
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"RBTree.h"

void test1()
{
	int a[] = { 5, 4, 8, 6, 2, 9, 1, 3, 7 };
 	RBTree<int, int> rb;
	for (auto& e : a)
	{
		rb.insert(make_pair(e, e));
	}
	rb.Inorder();

	cout << rb.Balance() << endl;
}

int main()
{
	test1();
	return 0;
}

image.png
image.png

12. 红黑树与AVL树的比较

  • 红黑树和AVL树都是高效的平衡二叉搜索树,增删查改的时间复杂度都为O(longn),但红黑树不要求绝对平衡,只需保证最长路径的长度不超过最短路径长度的两倍,相对而言,降低了插入、删除时旋转的次数,所以在经常进行增删查改的结构中红黑树的性能更优,在实际应用红黑树的更多。

  • AVL树的高度接近于longn,红黑树的高度接近于2longn,所以搜索效率AVL树略优于红黑树,但是几乎可以忽略不记,因为n足够大时,longn就足够小,两者的差距微乎其微。当插入同样得数据,AVL树的高度更低,通过多次旋转达到的。

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

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

相关文章

2024电工杯数学建模B题高质量成品论文,包括代码数据

2024电工杯数学建模B题高质量成品论文&#xff0c;包括代码数据 完整内容见文末名片 摘要 大学时期是学生们知识学习和身体成长的重要阶段&#xff0c;良好的饮食习惯对于促进生长发育和保证身体健康具有重要意义。针对当前大学生中存在的饮食结构不合理及不良饮食习惯的问题…

React useState数组新增和删除项

在React中&#xff0c;我们可以使用useState钩子来管理组件的状态&#xff0c;其中包括数组。如何在React函数组件中对数组进行增加和删除项的操作&#xff1f; 新增项时&#xff1a;我们可以对原数组进行解构&#xff0c;并把新增项一起加入到新数组&#xff1b; 删除项时&…

2024年全国大学生电工数学建模竞赛B题解析 | 数据处理 代码 论文分享

B 题&#xff1a;大学生平衡膳食食谱的优化设计及评价 1 数据预处理2 问题一2.1 问题1.12.1.1 评价体系的构建2.1.2 指标计算2.1.3 指标计算结果2.1.4 基于层次分析法的膳食营养评价模型2.1.5 评价模型的求解 2.2 问题1.22.2.1 食物与成分间拓扑关系的构建2.2.2 微调模型的建立…

IT行业已经饱和?2024年报考计算机类专业还有出路吗?

&#x1f446;点击关注 获取更多编程干货&#x1f446; “高薪”光环加持&#xff0c;IT行业一直是不少人心仪的职业选择&#xff0c;计算机专业一度成为最热门的本科专业。 然而&#xff0c;正因报考计算机专业、想要入行IT行业的人越来越多&#xff0c;“行业饱和”、“人才…

探数API统计分享-2017年-2021年中国各省人均消费支出统计

根据2017年至2021年的统计数据&#xff0c;我国各省&#xff08;市、区&#xff09;的人均消费支出情况各不相同。其中&#xff0c;上海的人均消费支出最高&#xff0c;达到了2021年的48879元&#xff0c;位居全国之首。紧随其后的是北京&#xff0c;人均消费支出为43640元。 …

Vivado 使用教程(个人总结)

Vivado 是 Xilinx 公司推出的一款用于 FPGA 设计的集成开发环境 (IDE)&#xff0c;提供了从设计输入到实现、验证、调试和下载的完整流程。本文将详细介绍 Vivado 的使用方法&#xff0c;包括项目创建、设计输入、约束文件、综合与实现、仿真、调试、下载配置等步骤。 一、创建…

如何生成Github Badge徽章图标

如何生成徽章Badge 什么是徽章(Badge)生成小徽章shields网站开源项目的徽章lib版本徽章代码测试覆盖度开源协议Github workflow的徽章 开源代码实践效果py-enumjs-enumerate 什么是徽章(Badge) 在开源项目的README中&#xff0c;经常会见到一些徽章(Badge)小图标&#xff0c;如…

抽烟行为检测:从传统巡查到智能算法

在当前人工智能和计算机视觉技术的迅猛发展下&#xff0c;基于视觉分析的抽烟行为检测算法成为一种高效的技术手段。此类算法通常依赖于深度学习模型&#xff0c;特别是卷积神经网络&#xff08;CNN&#xff09;&#xff0c;通过对摄像头捕捉的视频流进行实时分析&#xff0c;能…

租赁系统|北京租赁系统|租赁软件开发流程

在数字化时代的浪潮下&#xff0c;小程序成为了各行各业争相探索的新领域。租赁行业亦不例外&#xff0c;租赁小程序的开发不仅提升了用户体验&#xff0c;更为商家带来了更多商业机会。本文将详细解析租赁小程序的开发流程&#xff0c;为有志于进军小程序领域的租赁行业从业者…

AI爆文写作:如果你有一篇文章爆了,正确的做法是:自己抄袭自己,重复发,还可以继续爆!

爆款总是相似的&#xff0c;如果你有一篇文章爆了&#xff0c;正确的做法&#xff0c;就是重复发&#xff0c;让它继续爆下去。 以前我在小红书看到一个人&#xff0c;将一篇自己火的笔记&#xff0c;连续发了5次&#xff0c;每次点赞数据都不错。 公众号文章也是一样的。 我…

【LeetCode】【209】长度最小的子数组(1488字)

文章目录 [toc]题目描述样例输入输出与解释样例1样例2样例3 提示进阶Python实现前缀和二分查找滑动窗口 个人主页&#xff1a;丷从心 系列专栏&#xff1a;LeetCode 刷题指南&#xff1a;LeetCode刷题指南 题目描述 给定一个含有n个正整数的数组和一个正整数target找出该数组…

Spring Boot 3.3 正式发布,王炸级更新,应用启动速度直接起飞!

最新消息&#xff0c;Spring Boot 一次性发布了 3 个版本&#xff1a; 3.3.0 3.2.6 3.1.13 Spring Boot 3.3 正式发布了&#xff0c;3.1.x 在前几天也停止维护了。 最新的支持版本如下&#xff1a; 从路线图可以看到每个版本的终止时间&#xff0c;每个版本的生命周期只有…

【VUE】 如何关闭ESlint的自动修复功能

问题描述例如&#xff1a;原书写代码ESLint自动修复报错如下 方案一、在文件中添加屏蔽警告的代码html代码中JavaScript代码中 方案二、关闭ESLint的自动修复功能1、VSCode 扩展找到 ESLint 插件2、在设置中找到在 settings,json 中编辑3、将"autoFix": true改为&quo…

跟TED演讲学英文:Bring on the learning revolution! by Sir Ken Robinson

Bring on the learning revolution! Link: https://www.ted.com/talks/sir_ken_robinson_bring_on_the_learning_revolution Speaker: Sir Ken Robinson Date: February 2010 文章目录 Bring on the learning revolution!IntroductionVocabularySummaryTranscriptAfterword I…

ingress-nginx控制器安装(ingress ImagePullBackOff )

支持的版本&#xff08;查看自己的kubernetes版本替换安装过程中的版本选择合适的版本安装&#xff09; 安装过程&#xff1a; 这里不采用helm的方式&#xff0c;而是采用YAML manifest的方式来安装。 下载ingress-nginx的https://raw.githubusercontent.com/kubernetes/ingr…

开放式耳机什么牌子好用?五大高分力作安利,不容错过!

​开放式耳机如今备受瞩目&#xff0c;其独特的不入耳设计不仅避免了长时间佩戴对耳道的压力&#xff0c;还维护了耳朵的卫生健康&#xff0c;特别受运动爱好者和耳机发烧友青睐。然而&#xff0c;市场上的开放式耳机品质良莠不齐&#xff0c;让许多消费者在选择时陷入困惑。作…

滑动谜题 leetcode的BFS题目 启发如何写一个拼图编程呢

题目链接 题目要求&#xff0c;要将上面的拼板拼成123450 首先&#xff0c;转换为字符串&#xff0c;为什么要转换为字符串呢&#xff0c;因为处理会变得很简单比如示例一&#xff0c;转化为字符串是12345&#xff0c;目标字符串为123450&#xff0c;只需要证明通过某种交换&a…

Linux环境基础开发工具的使用(yum,vim,gcc/g++,make/Makefile,gdb)

Linux 软件包管理器-yum 什么是软件包及安装方式 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序。 但是这样太麻烦了, 于是有些人把一些常用的软件提前编译好, 做成软件包(可以理解成windows上的安装程序)放在一个服务器上, 通过包管理…

使用HTTP长连接减少文件描述符和端口占用

在当今互联网技术飞速发展的背景下&#xff0c;高并发处理能力已经成为衡量服务器性能的一个重要标准。面对高并发场景&#xff0c;服务器需要同时应对大量的请求&#xff0c;这就带来了一个棘手的问题&#xff1a;资源有限。具体来说&#xff0c;文件描述符和端口号&#xff0…

【全开源】点餐小程序系统源码(ThinkPHP+FastAdmin+UniApp)

基于ThinkPHPFastAdminUniApp开发的点餐微信小程序&#xff0c;类似肯德基&#xff0c;麦当劳&#xff0c;喜茶等小程序多店铺模式&#xff0c;支持子商户模式&#xff0c;提供全部前后台无加密源代码和数据库&#xff0c;支持私有化部署。 革新餐饮行业的智慧点餐解决方案 一…