【c++底层结构】AVL树红黑树

news2024/11/6 9:28:34

【c++底层结构】AVL树&红黑树

  • 1.AVL树
    • 1.1 AVL树的概念
    • 1.2 AVL树结点的定义
    • 1.3 AVL树的插入
    • 1.4 AVL树的旋转
    • 1.5 AVL树的验证
    • 1.6 AVL树的性能
  • 2. 红黑树
    • 2.1 红黑树的概念
    • 2.2 红黑树的性质
    • 2.3 红黑树节点的定义
    • 2.4 红黑树的插入操作
    • 2.5 红黑树的验证
    • 2.6 红黑树与AVL树的比较
    • 2.7 红黑树的应用
    • 2.8 红黑树模拟实现STL中的map与set
  • 3 .map和set的模拟实现
    • 3.1 map的模拟实现
    • 3.2 set的模拟实现
    • 3.3 改造红黑树

1.AVL树

1.1 AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adeksib-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

  • 它的左右子树都是AVL树
  • 左右子树高度之差(平衡因子)的绝对值不超过1(-1/1/0)
    在这里插入图片描述 如果一颗二叉树是高度平衡的,它就是AVL、如果它有n个结点,其高度可保持在O(log2N),搜索时间复杂度O(log2N)

1.2 AVL树结点的定义

template<class K,class V>
class AVLTreeNode
{
	AVLTreeNode<K, V>* _left;   //该节点的左孩子   
	AVLTreeNode<K, V>* _right;  //该节点的右孩子
	AVLTreeNode<K, V>* _parent; //该节点的父结点
	pair<K, V> _kv;             //该节点的左孩子
	int _bf;//balance factor    //该节点的平衡因子

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

1.3 AVL树的插入

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

    1. 按照二叉搜索树的方式插入新节点
    1. 调整节点的平衡因子

1、更新平衡因子

1、cur == parent->_right parent->bf++
2、cur == parent->_left parent->bf–

2、如果更新完以后,平衡因子没有出现问题,(|bf|<=1)平衡结构没有受到影响,不需要处理
3、如果更新完以后,平衡出现问题,平衡结构受到影响,需要处理(旋转)

什么决定了是否要继续往上更新,parent所在的子树高度是否变化?变了继续更新,不变就不再更新。

1、parent->bf == 1 || parent->bf == -1 --> parent所在的子树变了,继续更新。(0->1 ||0->-1)
2、parent->bf==2||parent->bf == -2 -->parent所在的子树不平衡。需要处理这棵子树(旋转处理)。
3、parent->bf == 0 parent所在的子树高度不变,不用继续往上更新,插入结束。(-1->0,1->0,parent子树高度不变)

1.4 AVL树的旋转

旋转的原则:保持搜索树的性质
旋转的目的:左右均衡,降低整棵树的高度

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡
化。根据节点插入位置的不同,AVL树的旋转分为四种:

首先我们来分析两种简单的旋转(单旋)
我们可以将需要单旋的情况抽象为下图所示的抽象图
在这里插入图片描述
简单分析当h=1;h=2;h=3
在这里插入图片描述

  1. 新节点插入较高右子树的右侧—右右:左单旋
    在这里插入图片描述
void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		//链接parent和subRL ,并更新三叉链
		parent->_right = subRL;
		if(subRL!=nullptr)
			subRL->_parent = parent;

		Node* ppnode = parent->_parent;

		//链接subR和parent,并更新三叉链
		subR->_left = parent;
		parent->_parent = subR;

		//parent为根节点
		if (ppnode == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		//parent不为根节点
		else
		{
			//与ppnode链接
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR
			}
			else
			{
				ppnode->_right = subR;;
			}
			subR->_parent = ppnode;
		}
		//更新平衡因子
			parent->_bf = subR->_bf = 0;
	}

  1. 新节点插入较高左子树的左侧–左左:右单旋
    在这里插入图片描述

代码

void RotateR(Node * parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* ppnode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		parent->_left = subLR;
		if (subLR != nullptr)
			subLR->_parent = parent;

		if (ppnode == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode__left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
		parent->_bf = subL->_bf = 0;
	}

接下来我们继续分析双旋的情况。
抽象图如下
在这里插入图片描述

具象图如图下

在这里插入图片描述

  1. 新节点插入较高左子树的右侧—左右:先左单旋再右单旋
    在这里插入图片描述
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		RotateL(parent->_left);
		RotateR(parent);
		if (bf == 1)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
  1. 新节点插入较高右子树的左侧—右左:先右单旋再左单旋
    在这里插入图片描述
	RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(parent->_right);
		RotateL(parent);

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

1.5 AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  1. 验证其为二叉搜索树
    如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
  2. 验证其为平衡树
    每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
    节点的平衡因子是否计算正确
int _Height(PNode pRoot);
bool _IsBalanceTree(PNode pRoot)
{
	// 空树也是AVL树
	if (nullptr == pRoot) return true;
	// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差
	int leftHeight = _Height(pRoot->_pLeft);
	int rightHeight = _Height(pRoot->_pRight);
	int diff = rightHeight - leftHeight;
	// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者
	// pRoot平衡因子的绝对值超过1,则一定不是AVL树
	if (diff != pRoot->_bf || (diff > 1 || diff < -1))
	return false;
	// pRoot的左和右如果都是AVL树,则该树一定是AVL树
	return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot->_pRight);
}

1.6 AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证
查询时高效的时间复杂度,即 。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:
插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,
但一个结构经常修改,就不太适合。
全部代码

#pragma once
#include<assert.h>
#include<iostream>
#include<set>
#include<map>
#include<string>
using namespace std;
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;//balance factor

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{}
};
template <class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_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 (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;
		//更新平衡因子
		while (parent)
		{
			if (cur == parent->_right)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}

			if (parent->_bf == 1 || parent->_bf == -1)
			{
				//继续更新
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (parent->_bf == 0)
			{
				break;
			}
			else if(parent->_bf == 2 || parent->_bf == -2)
			{
				//旋转处理  1、让子树平衡,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)
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else
				{
					assert(false);
				}
				break;
			}
			//在插入前平衡结构已经出现问题
			else
			{
				assert(false);
			}
		}
		return true;
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	int Height()
	{
		return _Height(_root);
	}
private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}
	int _Height(Node* root)
	{
		if (root == NULL)
			return 0;

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		return leftH > rightH ? leftH + 1 : rightH + 1;
	}

	bool _IsBalance(Node* root)
	{
		if (root == NULL)
		{
			return true;
		}

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		if (rightH - leftH != root->_bf)
		{
			cout << root->_kv.first << "节点平衡因子异常" << endl;
			return false;
		}

		return abs(leftH - rightH) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		//链接parent和subRL ,并更新三叉链
		parent->_right = subRL;
		if(subRL!=nullptr)
			subRL->_parent = parent;

		Node* ppnode = parent->_parent;

		//链接subR和parent,并更新三叉链
		subR->_left = parent;
		parent->_parent = subR;

		//parent为根节点
		if (ppnode == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		//parent不为根节点
		else
		{
			//与ppnode链接
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;;
			}
			subR->_parent = ppnode;
		}
		//更新平衡因子
			parent->_bf = subR->_bf = 0;
	}
	void RotateR(Node * parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* ppnode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		parent->_left = subLR;
		if (subLR != nullptr)
			subLR->_parent = parent;

		if (ppnode == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
		parent->_bf = subL->_bf = 0;
	}
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		RotateL(parent->_left);
		RotateR(parent);
		if (bf == 1)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subLR->_bf = 0;
			subL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(parent->_right);
		RotateL(parent);

		if (bf == 1)
		{
			parent->_bf = -1;
			subRL->_bf = 0;
			subR->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subRL->_bf = 0;
			subR->_bf = 1;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subRL->_bf = 0;
			subR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	
private:
	Node* _root = nullptr;
};
void Test_AVLTree1()
{
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree <int, int> t1;
	for (auto e : a)
	{
		t1.Insert(make_pair(e,e));
	}
	t1.InOrder();
}
void Test_AVLTree2()
{
	srand(time(0));
	const size_t N = 5000000;
	AVLTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	//t.Inorder();

	cout << t.IsBalance() << endl;
	cout << t.Height() << endl;
}

2. 红黑树

2.1 红黑树的概念

红黑树,是一种二叉搜索树,但在每个节点上增加一个存储位表示节点的颜色,可以是Red或Black,通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径必其它路径长出两倍,因而是接近平衡的。
在这里插入图片描述

2.2 红黑树的性质

  1. 每个节点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,那么他的两个孩子结点是黑色的
  4. 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色结点
  5. 每个叶子节点都是黑色的(NIL结点)

2.3 红黑树节点的定义

// 节点的颜色
enum Color{RED, BLACK};
// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Color _col;
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}

我们在这里选择将默认的节点颜色给成红色,是因为插入新节点时,如果是黑色的话一定会违反上述红黑树性质的第四条,其次这个节点的插入会影响到每一条路径;如果是红的的话有可能会违反第三条,同时如果造成了影响,影响到的也只是局部。

2.4 红黑树的插入操作

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

  1. 按照二叉搜索树的规则插入新节点
   bool Insert(const pair<K, V>& kv)
   {
   	if (_root == nullptr)
   	{
   		_root = new Node(kv);
   		_root->_col = BLACK;
   		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 (parent->_kv.first > kv.first)
   	{
   		parent->_left = cur;
   	}
   	else
   	{
   		parent->_right = cur;
   	}
   	cur->_parent = parent;

   	//TODO 调整红黑树结构
   	
   	return true;
   }
  1. 检测新节点插入以后,红黑树的性质是否遭到破坏

因为新节点的默认颜色是红色,因此如果其双亲结点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;单当插入节点的双亲节点颜色为红色时,就违反了性质三不能有连续的红色节点,此时就需要对红黑树分情况讨论。
首先我们先对特殊情况进行一个分析、对红黑树的节点调整建立初步印象。
在这里插入图片描述
接下来我们对更一般的情况进行分析

  • 情况一:cur为红 ,p为红,g为黑,u存在且为红
  • 解决方法:parent 变黑 uncle变黑 grandfather变红 然后继续向上调整·
    在这里插入图片描述

在这里插入图片描述
将上面的步骤翻译为代码就是

if (uncle && uncle->_col == RED)
{
	parent->_col = BLACK;
	uncle->_col = BLACK;
	grandfather->_col = RED;

	//继续向上调整
	cur = grandfather;
	parent = cur->_parent;
}
  • 情况二: cur为红,p为红,g为黑,u不存在/u为黑(+单旋)
    在这里插入图片描述
    说明:u的情况有两种
    1、如果u结点不存在,则cur一定是新插入结点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4:每条路经黑色节点个数相同。
    2、如果u结点存在,则其一定是黑色,那么cur结点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改为红色

p为g的左孩子,cur为p的左孩子,则进行右单旋+变色(p变黑,g变红)
p为g的右孩子,cur为p的右孩子,则进行左单旋+变色(p变黑,g变红)

  • 情况三: cur为红,p为红,g为黑,u不存在/u为黑(+双旋)

p为g的左孩子,cur为p的右孩子,p做左单旋,g右单旋+变色(p变黑,g变红)
p为g的右孩子,cur为p的左孩子,p做右单旋,g左单旋+变色(p变黑,g变红)
在这里插入图片描述
在写代码的时候需要对up之间的关系进行分类,因为这会影响到旋转的方向
将上面三种情况翻译为代码如下:


while (parent != nullptr && parent->_col == RED)
{
	Node* grandfather = parent->_parent;
	if (grandfather->_left == parent)
	{
		Node* uncle = grandfather->_right;
		//情况1:uncle存在且为红,变色处理,并继续往上处理
		if (uncle && uncle->_col == RED)
		{
			parent->_col = BLACK;
			uncle->_col = BLACK;
			grandfather->_col = RED;

			//继续向上调整
			cur = grandfather;
			parent = cur->_parent;
		}
		//情况2+3:uncle不存在/uncle存在且为黑,旋转+变色
		else 
		{
		//      g
		//   p     u
		// c
			if (cur == parent->_left)
			{
				RotateR(grandfather);
				parent->_col = BLACK;
				grandfather->_col = RED;
			}
			//      g
			 //   p     u
			 //       c
			else
			{
				RotateL(parent);
				RotateR(grandfather);
				cur->_col = BLACK;
				//parent->_col = RED;
				grandfather->_col = RED;
			}
			break;
		}
	}
	else//(grandfather->_right == parent)
	{
	//           g
	//        u       p
	//                      c
		Node* uncle = grandfather->_left;
		//情况1:uncle存在且为红,变色处理,并继续往上处理
		if (uncle && uncle->_col == RED)
		{
			parent->_col = BLACK;
			uncle->_col = BLACK;
			grandfather->_col = RED;

			//继续向上调整
			cur = grandfather;
			parent = cur->_parent;
		}
		//情况2+3:uncle不存在/uncle存在且为黑,旋转+变色
		else
		{
			//           g
			//        u       p
			//                      c
			if (cur == parent->_right)
			{
				RotateL(grandfather);
				grandfather->_col = RED;
				parent->_col = BLACK;
			}
			else
			{
				//           g
				//        u       p
				//             c
				RotateR(parent);
				RotateL(grandfather);
				cur->_col = BLACK;
				grandfather->_col = RED;
			}
			break;
		}
	}
}
_root->_col = BLACK;

2.5 红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质(每条路径黑色节点数量相同)
bool _Check(Node* root, int blackNum, int benchmark)
{
	if (root == nullptr)
	{
		if (benchmark != blackNum)
		{
			cout << "某条路径黑色节点的数量不相等" << endl;
			return false;
		}

		return true;
	}

	if (root->_col == BLACK)
	{
		++blackNum;
	}

	if (root->_col == RED 
		&& root->_parent 
		&& root->_parent->_col == RED)
	{
		cout << "存在连续的红色节点" << endl;
		return false;
	}

	return _Check(root->_left, blackNum, benchmark)
		&& _Check(root->_right, blackNum, benchmark);
}
bool IsBalance()
{
	if (_root && _root->_col == RED)
	{
		cout << "根节点颜色是红色" << endl;
		return false;
	}
	int benchmark = 0;//基准点(最左路经黑色节点的数量)
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
			++benchmark;
		cur = cur->_left;
	}
	//连续红色结点
	return _Check(_root, 0, benchmark);
}

全部代码

#pragma once
#include<assert.h>
#include<iostream>
#include<set>
#include<map>
#include<string>
using namespace std;
enum Color
{
	RED,
	BLACK,
};
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Color _col;
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}
};
template<class K,class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur ->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			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 (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;
 
		while (parent != nullptr && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//情况1:uncle存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况2+3:uncle不存在/uncle存在且为黑,旋转+变色
				else 
				{
				//      g
				//   p     u
				// c
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//      g
					 //   p     u
					 //       c
					else
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						//parent->_col = RED;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else//(grandfather->_right == parent)
			{
			//           g
			//        u       p
			//                      c
				Node* uncle = grandfather->_left;
				//情况1:uncle存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况2+3:uncle不存在/uncle存在且为黑,旋转+变色
				else
				{
					//           g
					//        u       p
					//                      c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						//           g
						//        u       p
						//             c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
	bool IsBalance()
	{
		if (_root && _root->_col == RED)
		{
			cout << "根节点颜色是红色" << endl;
			return false;
		}
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				++benchmark;
			cur = cur->_left;
		}
		//连续红色结点
		return _Check(_root, 0, benchmark);
	}
	int Height()
	{
		return _Height(_root);
	}
private:
	void _Destroy (Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}
	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;
		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);
		return leftH > rightH ? leftH + 1 : rightH + 1;
	}
	bool _Check(Node* root, int blackNum, int benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark != blackNum)
			{
				cout << "某条路径黑色节点的数量不相等" << endl;
				return false;
			}

			return true;
		}

		if (root->_col == BLACK)
		{
			++blackNum;
		}

		if (root->_col == RED
			&& root->_parent
			&& root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		return _Check(root->_left, blackNum, benchmark)
			&& _Check(root->_right, blackNum, benchmark);
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		//链接parent和subRL ,并更新三叉链
		parent->_right = subRL;
		if (subRL != nullptr)
			subRL->_parent = parent;

		Node* ppnode = parent->_parent;

		//链接subR和parent,并更新三叉链
		subR->_left = parent;
		parent->_parent = subR;

		//parent为根节点
		if (ppnode == nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		//parent不为根节点
		else
		{
			//与ppnode链接
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}
	}
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* ppnode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		parent->_left = subLR;
		if (subLR != nullptr)
			subLR->_parent = parent;

		if (ppnode == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}
	}
private:
	Node* _root;
};
void Test_RBTree1()
{
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	RBTree <int, int> t1;
	for (auto e : a)
	{
		t1.Insert(make_pair(e, e));
	}
	t1.InOrder();
}
void Test_RBTree2()
{
	srand(time(0));
	const size_t N = 5000000;
	RBTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(make_pair(x, x));
		cout << t.IsBalance() << endl;
	}

	//t.Inorder();
}

2.6 红黑树与AVL树的比较

红黑树和AVL树都是搞笑的平衡二叉树,增删查改的时间复杂度都是O( log2N),红黑树不追求绝对平衡,其
只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构
中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

2.7 红黑树的应用

  1. C++ STL库 – ma
  2. Java 库
  3. linux内核
  4. 其他一些库

2.8 红黑树模拟实现STL中的map与set

迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代器,需要考
虑以前问题:
begin()与end()
STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一
个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点
(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?能否给成nullptr呢?答案是行不通
的,因为对end()位置的迭代器进行–操作,必须要能找最后一个元素,此处就不行,因此最好的方式是
将end()放在头结点的位置
在这里插入图片描述

  • operator++()与operator–()
    在这里插入图片描述
/*
template<class T, class Ref, class Ptr>
typedef __RBTreeIterator<T, Ref, Ptr> Self;
*/
Self& operator++()
	{
		if (_node->_right)
		{
			// 1、右不为空,下一个就是右子树的最左节点
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}

			_node = subLeft;
		}
		else
		{
			// 2、右为空,沿着到根的路径,找孩子是父亲左的那个祖先
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	Self& operator--()
	{
		if (_node->_left)
		{
			// 1、左不为空,找左子树最右节点
			Node* subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}

			_node = subRight;
		}
		else
		{
			// 2、左为空,孩子是父亲的右的那个祖先
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

3 .map和set的模拟实现

首先map和set的底层结构就是红黑树,因此在map和set中分别封装一棵红黑树,然后将其接口包装下即可。为了减少代码的冗余,我们通常会引入一个新的模版参数,来区分是map还是set的调用。详情见下图:
在这里插入图片描述

3.1 map的模拟实现

#pragma once

#include "RBTree.h"

namespace bit
{
	template<class K, class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;

		iterator begin()
		{
			return _t.begin();
		}

		iterator end()
		{
			return _t.end();
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
			return ret.first->second;
		}

		pair<iterator, bool> insert(const pair<const K, V>& kv)
		{
			return _t.Insert(kv);
		}
	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};

	void test_map1()
	{
		map<string, string> dict;
		dict.insert(make_pair("sort", "排序"));
		dict.insert(make_pair("string", "ַ字符串"));
		dict.insert(make_pair("count", "计数"));
		dict.insert(make_pair("string", "(字符串)")); //插不进去

		map<string, string>::iterator it = dict.begin();
		while (it != dict.end())
		{
			cout << it->first << ":" << it->second << endl;
			/*it->first = "1111";
			it->second = "111";*/

			++it;
		}
		cout << endl;

		for (auto& kv : dict)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
		cout << endl;
	}

	void test_map2()
	{
		string arr[] = { "你", "今", "天", "真", "好", "看", };
		map<string, int> countMap;
		for (auto& e : arr)
		{
			countMap[e]++;
		}

		for (auto& kv : countMap)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
	}
}

3.2 set的模拟实现

#pragma once
#include"RBTree.h"
namespace Maria
{
	template <class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K, K, SetKeyOfT>::Iterator iterator;

		iterator begin()
		{
			return _t.begin();
		}

		iterator end()
		{
			return _t.end();
		}
		pair<iterator,bool> Insert(const K& key)
		{
			return _t.Insert(key);
		}
	private:
		RBTree < K,K,SetKeyOfT> _t;
	};
	void test_set()
	{
		set<int> s;
		s.Insert(3);
		s.Insert(1);
		s.Insert(2);

		set<int>::iterator it = s.begin();
		while (it != s.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
}

3.3 改造红黑树

#pragma once
#include<iostream>
using namespace std;

enum Colour
{
	RED,
	BLACK,
};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Colour _col;

	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)
	{}
};

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;

	__RBTreeIterator(Node* node)
		:_node(node)
	{}

	__RBTreeIterator(const __RBTreeIterator<T, T&, T*>& it)
		:_node(it._node)
	{}

	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}

	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}
	Self& operator--()
	{
		if (_node->_left)
		{
			// 1、左不为空,找左子树最右节点
			Node* subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}

			_node = subRight;
		}
		else
		{
			// 2、左为空,孩子是父亲的右的那个祖先
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}
	Self& operator++()
	{
		if (_node->_right)
		{
			// 1、右不为空,下一个就是右子树的最左节点
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}

			_node = subLeft;
		}
		else
		{
			//2、右为空、沿着到根的路径,找孩子是父亲左的那个祖先
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
};

// 仿函数
template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}
public:
	typedef __RBTreeIterator<T, T&, T*> Iterator;
	typedef __RBTreeIterator<T, const T&, const T*> const_Iterator;

	Iterator begin()
	{
		Node* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}

		return Iterator(cur);
	}

	Iterator end()
	{
		return Iterator(nullptr);
	}


	Node* Find(const K& key)
	{
		Node* cur = _root;
		KeyOfT kot;
		while (cur)
		{
			if (kot(cur->_data) < key)
			{
				cur = cur->_right;
			}
			else if (kot(cur->_data) > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}

		return nullptr;
	}

	pair<Iterator,bool>  Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(Iterator(_root),true);
		}

		KeyOfT kot;
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return make_pair(Iterator(cur),false );
			}
		}

		cur = new Node(data);
		Node* newnode = cur;
		if (kot(parent->_data) > kot(data))
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况2+3:u不存在/u存在且为黑,旋转+变色
				{
					//     g
					//   p   u
					// c 
					if (cur == parent->_left)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//     g
						//   p   u
						//     c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						//parent->_col = RED;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else // (grandfather->_right == parent)
			{
				//    g
				//  u   p
				//        c
				Node* uncle = grandfather->_left;
				// 情况1:u存在且为红,变色处理,并继续往上处理
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况2+3:u不存在/u存在且为黑,旋转+变色
				{
					//    g
					//  u   p
					//        c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						//    g
						//  u   p
						//    c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;

		return make_pair(Iterator(newnode), true);
	}

	bool IsBalance()
	{
		if (_root && _root->_col == RED)
		{
			cout << "根节点颜色是红色" << endl;
			return false;
		}

		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				++benchmark;
			cur = cur->_left;
		}

		// 连续红色节点
		return _Check(_root, 0, benchmark);
	}

	int Height()
	{
		return _Height(_root);
	}

private:
	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

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

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

		int leftH = _Height(root->_left);
		int rightH = _Height(root->_right);

		return leftH > rightH ? leftH + 1 : rightH + 1;
	}

	bool _Check(Node* root, int blackNum, int benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark != blackNum)
			{
				cout << "某条路径黑色节点的数量不相等" << endl;
				return false;
			}

			return true;
		}

		if (root->_col == BLACK)
		{
			++blackNum;
		}

		if (root->_col == RED
			&& root->_parent
			&& root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		return _Check(root->_left, blackNum, benchmark)
			&& _Check(root->_right, blackNum, benchmark);
	}

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

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

		Node* ppnode = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

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

			subR->_parent = ppnode;
		}
	}

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

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

		Node* ppnode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

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

private:
	Node* _root = nullptr;
};

在这里插入图片描述

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

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

相关文章

linux之iptables的理解与使用

1. 前言 iptables是一个用于Linux操作系统的防火墙软件&#xff0c;它可以对网络流量进行过滤、修改和重定向&#xff0c;从而控制网络通信。iptables是Linux内核中的一个子系统&#xff0c;它可以通过在命令行输入规则来配置网络防火墙。iptables可以对入站和出站的流量进行控…

初识mysql数据库之事务的隔离性

目录 一、理解隔离性 二、隔离级别 1. 不同的隔离级别的简单概述 2. 查看隔离级别 2.1 查看全局隔离级别 2.2 查看会话隔离级别 3. 设置隔离界别 4. 读未提交&#xff08;Read Uncommitted&#xff09; 4.1 读未提交测试 5. 读提交&#xff08;Read Committed&#x…

Windows 10 安装 PostgreSQL 12.x 报错 ‘psql‘ 不是内部或外部命令 由于找不到文件libintl-9.dll等问题

目录 序言一、问题总结问题 1 psql 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。问题 2 “由于找不到文件libintl-9.dll&#xff0c;无法继续执行代码&#xff0c;重新安装程序可能会解决此问题。“1、卸载2、安装3、安装 Stack Builder &#xff08;这个可…

Easyexcel简介及写、读操作

Easyexcel简介及写、读操作 一、背景二、简介三、引入依赖四、代码实现1.创建实体类2.写入excel操作3.读取文件操作3.1 指定excel对应索引3.2 设置监听器3.3 执行读取操作 一、背景 作为一个经常进行数据分析的后端人员&#xff0c;免不了面对各种报表&#xff0c;且在日常的工…

onTouchEvent浅析

我们接着上次的自定义星星来作讲解 当 onTouchEvent 返回 super.onTouchEvent ( false ) 时 public boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:Log.d("ACTION_DOWN","ACTION_DOWN:"event.getA…

Ansible-playbook(剧本)

Ansible-playbook(剧本) 一、playbook的构成 &#xff08;1&#xff09;Tasks&#xff1a;任务&#xff0c;即通过 task 调用 ansible 的模板将多个操作组织在一个 playbook 中运行&#xff08;2&#xff09;Variables&#xff1a;变量&#xff08;3&#xff09;Templates&am…

mybatis复杂环境搭建-多对一的处理-一对多的处理

复杂环境搭建&#xff1a; 1.1建表&#xff1a; CREATE table teacher( id int(10) not null, name varchar(30) default null, primary key(id) )engineInnoDB default charsetutf8mb3;INSERT INTO teacher (id, name) VALUES (1, 何老师);create table student( id int(10)…

用户端App自动化测试

一、自动化用例录制 1、Appium Inspctor 功能介绍 UI 分析录制用例元素查找测试Attcah 已有的 session云测试 2、用例录制 1&#xff09;获取 app 的信息 2&#xff09;配置待测应用 3、获取 app 的信息 1&#xff09;app 入口&#xff0c;两种方式获取&#xff1a; * 通…

Linux系统中MySQL主从复制

本节主要学习了MySQL Replication概述&#xff0c;优点&#xff0c;复制类型&#xff0c;复制方式&#xff0c;复制过程和复制过程的限制&#xff0c;部署MySQL主从异步复制的流程及问题解决。 目录 一、MySQL Replication概述 1、优点 二、MySQL复制类型 1.异步复制&#…

#vue3报错 Cannot read properties of null (reading ‘isCE‘)#

场景&#xff1a;使用 npm 安装依赖包的时候&#xff0c;如如安装 npm i xlsx npm i file-saver 重新运行报错 Cannot read properties of null (reading isCE)# 解决办法&#xff1a; 使用的vite vue 在vite.config.ts添加如下配置&#xff1a; dedupe: [ vue ]

LeetCode使用最小花费爬楼梯(动态规划)

使用最小花费爬楼梯&#xff08;动态规划&#xff09; 题目描述算法流程(方法一)编程代码优化代码算法流程&#xff08;方法二&#xff09;编程代码代码优化 链接: 使用最小花费爬楼梯 题目描述 算法流程(方法一) 编程代码 class Solution { public:int minCostClimbingStair…

【雕爷学编程】Arduino动手做(104)---16X16点阵汉字屏模块2

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

8种工程设计优化问题Matlab代码:多种智能优化算法求解对比

目录 一、8种工程设计优化问题 二、8种工程设计优化问题部分问题介绍 2.1 焊接梁设计 2.2 拉力压缩弹簧设计 2.3 三杆衍架设计问题 2.4 压力容器设计 2.5 减速器设计问题 三、多种智能优化算法分别求解部分工程设计优化问题 3.1 焊接梁设计 3.2 拉力压缩弹簧设计 3.…

数据结构基础之二叉树

文章目录 二叉树性质二叉树分类遍历二叉树如何判断是否为完全二叉树 二叉树是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式&#xff0c;即使是一般的树也能简单地转换为二叉树&#xff0c;而且二叉树的存储结构及其算法都较为简单&#xff0c;因此…

企业选择租用CRM还是一次性买断CRM?分别有哪些优势?

CRM是企业管理客户关系&#xff0c;提升销售业绩&#xff0c;实现业务增长的重要工具。市场上的CRM系统销售方式主要有两种——租用型和买断型。那么&#xff0c;租用CRM好还是一次性买断CRM好&#xff1f;本文将从以下几个方面进行分析&#xff1a; 1、什么是租用型CRM和买断…

shell中按照特定字符分割字符串,并且在切分后的每段内容后加上特定字符(串),然后再用特定字符拼接起来

文件中的内容&#xff0c;可以这么写&#xff1a; awk -F, -v OFS, {for(i1;i<‌NF;i){$i$i"_suffix"}}1 input.txt-F,&#xff1a;设置输入字段分隔符为逗号&#xff08;,&#xff09;&#xff0c;这将使awk按照逗号分割输入文本。-v OFS‘,’&#xff1a;设置输…

数据结构:栈和队列的实现和图解二者相互实现

文章目录 写在前面栈什么是栈栈的实现 队列什么是队列队列的实现 用队列实现栈用栈模拟队列 写在前面 栈和队列的实现依托的是顺序表和链表&#xff0c;如果对顺序表和链表不清楚是很难真正理解栈和队列的 下面为顺序表和链表的实现和图解讲解 手撕图解顺序表 手撕图解单链表 …

VMware虚拟机中配置静态IP

目录 环境原因基础概念VMnet网络IPV4网络私有地址范围Vmnet8的作用网路通信的过程解决方法1&#xff1a;修改k8s组件重新启动解决方法2&#xff1a;配置静态IP系统网卡设置设置虚拟机网关修改虚拟机网卡 环境 本机系统&#xff1a;windows11虚拟机系统&#xff1a;CentOS-7-x8…

【AutoGluon_03】保存模型并调用模型

在训练好autogluon模型之后&#xff0c;可以将模型进行保存。之后当有新的数据需要使用autogluon进行预测的时候&#xff0c;就可以直接加载原来训练好的模型进行训练。 import pandas as pd from sklearn.model_selection import train_test_split from autogluon.tabular im…

第九章:stack类

系列文章目录 文章目录 系列文章目录前言stack的介绍stack的使用成员函数使用stack 总结 前言 stack是容器适配器&#xff0c;底层封装了STL容器。 stack的介绍 stack的文档介绍 stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除…