【C++】手撕红黑树

news2024/12/24 8:44:49

> 作者简介:დ旧言~,目前大二,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:能直接手撕红黑树。

> 毒鸡汤:行到水穷处,坐看云起时。。

> 望小伙伴们点赞👍收藏✨加关注哟💕💕 

🌟前言  

相信大家肯定听过在C++大名鼎鼎的两颗树,这两颗树分别是AVL树和红黑树,学过的小伙伴听到都是瑟瑟发抖,像一些大厂中可能会考手撕AVL树或红黑树。学习这两棵树确实难度很大,正所谓难度越大动力就越大,那本篇我们学习这两棵树的一颗树--红黑树。

⭐主体

学习多态咱们按照下面的图解:

🌙红黑树的概念

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

🌙红黑树的性质

红黑树有以下五点性质:

  1. 每个结点不是红色就是黑色。
  2. 根结点是黑色的。
  3. 如果一个结点是红色的,则它的两个孩子结点是黑色的。
  4. 对于每个结点,从该结点到其所有后代叶子结点的简单路径上,均包含相同数目的黑色结点。
  5. 每个叶子结点都是黑色的(此处的叶子结点指定是空结点)。

问题探讨:

红黑树如何确保从根到叶子的最长可能路径不会超过最短可能路径的两倍?

问题分析:

红黑树第三条性质说明红黑树不能存在连续(父子相连)的红结点,可以存在连续的黑结点,又由于第四条性质每个路径上的黑结点个数都相同 ,所以对于最短路径来说一定是都是黑色结点,对于最长路径来说一定是黑色红色相间的路径,所以最长路径不超过最短路径长度的二倍

图解分析:

🌙红黑树的结点

三叉链结构,对比AVL数节点的定义,把平衡因子替换成节点颜色,采用枚举的方式:

编写代码:

// 定义红黑树结点
template<class K,class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;   // 左
	RBTreeNode<K, V>* _right;  // 右
	RBTreeNode<K, V>* _parent; // 父亲

	Color _col; 
	pair<K, V> _kv; //存储的键值对

	RBTreeNode(const pair<K,V>& kv) // 初始化列表
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_col(RED)
	{}
};

代码分析:

为什么构造结点时,默认将结点的颜色设置为红色?

  1. 如果默认颜色为黑,那么在插入中插入一个黑结点一定会让该路径上的黑结点数量加1,从而与其他路径上黑结点数量造成不一致,而一定会影响该棵红黑树
  2. 如果默认颜色为红,那么在插入中插入一个红结点,可能新插入结点的父结点为黑色结点则没有影响,也可能新插入结点的父结点为红结点,由于不能存在连续的(父子相连的)红色结点,而对该棵树造成影响
  3. 所以默认为红色比较黑色来说是好的

🌙红黑树的插入

红黑树插入结点的逻辑分为三步:

  1. 按二叉搜索树的插入方法,找到待插入位置。
  2. 将待插入结点插入到树中。
  3. 若插入结点的父结点是红色的,则需要对红黑树进行调整。

红黑树调整时具体应该如何调整,主要是看插入结点的叔叔(插入结点的父结点的兄弟结点),根据插入结点叔叔的不同,可将红黑树的调整分为三种情况。


情况一:插入结点的叔叔存在,且叔叔的颜色是红色。(cur为红,p为红,g为黑,u存在且为红)

分析:

  • 此时为了避免出现连续的红色结点,我们可以将父结点变黑,但为了保持每条路径黑色结点的数目不变,因此我们还需要将祖父结点变红,再将叔叔变黑。这样一来既保持了每条路径黑色结点的数目不变,也解决了连续红色结点的问题。
  • 但调整还没有结束,因为此时祖父结点变成了红色,如果祖父结点是根结点,那我们直接再将祖父结点变成黑色即可,此时相当于每条路径黑色结点的数目都增加了一个。
  • 但如果祖父结点不是根结点的话,我们就需要将祖父结点当作新插入的结点,再判断其父结点是否为红色,若其父结点也是红色,那么又需要根据其叔叔的不同,进而进行不同的调整操作。

图解:

情况二:cur为红,p为红,g为黑,u不存在/u为黑,gpc在同一侧

探讨:

如果u结点不存在,则cur一定是新增结点,因为如果cur不是新增结点:则cur和p一定有一个节点时黑色,就不满足每条路径都有相同的黑色结点的性质。

如果u结点存在,则其一定是黑色的,那么c节点原来的颜色一定是黑色,在其子树调整过程中变为了红色

分析:

  • 如果p为g的左孩子,cur为p的左孩子,则进行右单旋转
  • 如果p为g的右孩子,cur为p的右孩子,则进行左单旋转

①u不存在,cur为新增节点,进行右单旋

图解:

②u结点存在且为黑:

情况二: cur为红,p为红,g为黑,u不存在/u为黑,gpc不在同一侧

分析:

  • p为g的左孩子,cur为p的右孩子,对p做左单旋转,
  • p为g的右孩子,cur为p的左孩子,对p做右单旋转,

说明:

  • 这时候我们就需要进行双旋了

图解:

编写代码:

	// 插入结点
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr) // 若红黑树为空叔,则插入结点直接作为根结点
		{
			_root = new Node(kv);
			_root->_col = BLACK; // 根结点必须是黑色
			return true;         // 插入成功
		}

		// 1.采用二叉搜索树的方法找插入位置
		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;
			}
		}

		// 2.将待插入结点插入到树中
		cur = new Node(kv); // 根据所给值构造一个结点(必须是红色)
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		// 3.若插入结点的父结点是红色的,则需要对红黑树进行调整
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent; // parent是红色其父结点一定存在
			
			// parent为父的左孩子
			if (parent == grandfather->_left) 
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED)//情况一:叔叔存在且为红色
				{
					// 颜色调整
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况二:uncle不存在 + uncle存在且为黑
				{
					if (cur == parent->_left)// cur == parent->_left
					{
						RotateR(grandfather);  // 右单旋
						
						// 颜色调整
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else // cur == parent->_right
					{
						RotateL(parent); // 左单旋
						RotateR(grandfather); // 右单旋
						
						// 颜色调整
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break; // 子树旋转后,该子树的根变为黑色,无需往上处理
				}
			}
			else // parent是父的右孩子 
			{
				Node* uncle = grandfather->_left;
				
				// 情况一:叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 颜色调整
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况二:叔叔不存在或者存在且为黑
				{
					
					if (cur == parent->_right)
					{
						// 右左双旋
						RotateL(grandfather);
						
						// 颜色调整
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else 
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		// 根结点的颜色为黑色(可能被情况一变成了红色,需要变回黑色)
		_root->_col = BLACK;
		return true;
	}

	// 左单旋
	void RotateL(Node* parent)
	{
		++rotateSize;

		Node* subR = parent->_right;
		Node* subRL = subR->_left;

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

		subR->_left = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = subR;

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

	// 右单旋
	void RotateR(Node* parent)
	{
		++rotateSize;

		Node* subL = parent->_left;
		Node* subLR = subL->_right;

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

		subL->_right = parent;

		Node* ppnode = parent->_parent;
		parent->_parent = subL;

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

🌙红黑树的查找

红黑树的查找函数与二叉搜索树的查找方式一模一样,逻辑如下:

  • 若树为空树,则查找失败,返回nullptr。
  • 若key值小于当前结点的值,则应该在该结点的左子树当中进行查找。
  • 若key值大于当前结点的值,则应该在该结点的右子树当中进行查找。
  • 若key值等于当前结点的值,则查找成功,返回对应结点。
	// 查找元素
	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 NULL;
	}

🌙红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质

步骤一代码:

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

		_InOrder(root->_left);
		cout << root->_kv.first << endl;
		_InOrder(root->_right);
	}
	void InOrder()
	{
		_InOrder(_root);
	}

步骤二代码:

	// 判断是否为红黑树
	bool Check(Node* cur, int blackNum, int refBlackNum)
	{
		if (cur == nullptr)
		{
			if (refBlackNum != blackNum)
			{
				cout << "黑色节点的数量不相等" << endl;
				return false;
			}

			//cout << blackNum << endl;
			return true;
		}

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

		if (cur->_col == BLACK)
			++blackNum;

		return Check(cur->_left, blackNum, refBlackNum)
			&& Check(cur->_right, blackNum, refBlackNum);
	}
	bool IsBalance()
	{
		if (_root && _root->_col == RED)
			return false;

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

			cur = cur->_left;
		}

		return Check(_root, 0, refBlackNum);
	}

🌙红黑树与AVL树比较

红黑树与AVL树比较:

  1. 红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( )

  2. 红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数

  3. 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多

🌙全部代码

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

enum Color // 采用枚举定义颜色
{
	RED,   // 0 为红色
	BLACK, // 1 为黑色
};

// 定义红黑树结点
template<class K,class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;   // 左
	RBTreeNode<K, V>* _right;  // 右
	RBTreeNode<K, V>* _parent; // 父亲

	Color _col; 
	pair<K, V> _kv; //存储的键值对

	RBTreeNode(const pair<K,V>& kv) // 初始化列表
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_col(RED)
	{}
};

// 主体
template<class K,class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;

public:

	// 插入结点
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr) // 若红黑树为空叔,则插入结点直接作为根结点
		{
			_root = new Node(kv);
			_root->_col = BLACK; // 根结点必须是黑色
			return true;         // 插入成功
		}

		// 1.采用二叉搜索树的方法找插入位置
		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;
			}
		}

		// 2.将待插入结点插入到树中
		cur = new Node(kv); // 根据所给值构造一个结点(必须是红色)
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		// 3.若插入结点的父结点是红色的,则需要对红黑树进行调整
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent; // parent是红色其父结点一定存在
			
			// parent为父的左孩子
			if (parent == grandfather->_left) 
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED)//情况一:叔叔存在且为红色
				{
					// 颜色调整
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况二:uncle不存在 + uncle存在且为黑
				{
					if (cur == parent->_left)// cur == parent->_left
					{
						RotateR(grandfather);  // 右单旋
						
						// 颜色调整
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else // cur == parent->_right
					{
						RotateL(parent); // 左单旋
						RotateR(grandfather); // 右单旋
						
						// 颜色调整
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break; // 子树旋转后,该子树的根变为黑色,无需往上处理
				}
			}
			else // parent是父的右孩子 
			{
				Node* uncle = grandfather->_left;
				
				// 情况一:叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 颜色调整
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 情况二:叔叔不存在或者存在且为黑
				{
					
					if (cur == parent->_right)
					{
						// 右左双旋
						RotateL(grandfather);
						
						// 颜色调整
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else 
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		// 根结点的颜色为黑色(可能被情况一变成了红色,需要变回黑色)
		_root->_col = BLACK;
		return true;
	}

	// 左单旋
	void RotateL(Node* parent)
	{
		++rotateSize;

		Node* subR = parent->_right;
		Node* subRL = subR->_left;

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

		subR->_left = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = subR;

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

	// 右单旋
	void RotateR(Node* parent)
	{
		++rotateSize;

		Node* subL = parent->_left;
		Node* subLR = subL->_right;

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

		subL->_right = parent;

		Node* ppnode = parent->_parent;
		parent->_parent = subL;

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

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

		_InOrder(root->_left);
		cout << root->_kv.first << endl;
		_InOrder(root->_right);
	}
	void InOrder()
	{
		_InOrder(_root);
	}

	// 判断是否为红黑树
	bool Check(Node* cur, int blackNum, int refBlackNum)
	{
		if (cur == nullptr)
		{
			if (refBlackNum != blackNum)
			{
				cout << "黑色节点的数量不相等" << endl;
				return false;
			}

			//cout << blackNum << endl;
			return true;
		}

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

		if (cur->_col == BLACK)
			++blackNum;

		return Check(cur->_left, blackNum, refBlackNum)
			&& Check(cur->_right, blackNum, refBlackNum);
	}
	bool IsBalance()
	{
		if (_root && _root->_col == RED)
			return false;

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

			cur = cur->_left;
		}

		return Check(_root, 0, refBlackNum);
	}

	// 计算红黑树结点个数
	size_t Size()
	{
		return _Size(_root);
	}
	size_t _Size(Node* root)
	{
		if (root == NULL)
			return 0;

		return _Size(root->_left)
			+ _Size(root->_right) + 1;
	}

	// 查找元素
	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 NULL;
	}

	// 计算红黑树高度
	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;
	}
	int Height()
	{
		return _Height(_root);
	}


	int GetRotateSize()
	{
		return rotateSize;
	}

private:
	Node* _root = nullptr;
	int rotateSize = 0;
};

void TestRBTree1()
{
	//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	RBTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
	}
	t.InOrder();
	cout << t.IsBalance() << endl;
}

void TestRBTree2()
{
	srand(time(0));
	const size_t N = 100000;
	RBTree<int, int> t;
	for (size_t i = 0; i < N; i++)
	{
		size_t x = rand();
		t.Insert(make_pair(x, x));
	}
	cout << t.IsBalance() << endl;

}

🌟结束语

       今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

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

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

相关文章

HTML5、CSS3面试题(二)

上一章:HTML5、CSS3面试题&#xff08;一&#xff09; 哪些是块级元素那些是行内元素&#xff0c;各有什么特点 &#xff1f;&#xff08;必会&#xff09; 行内元素: a、span、b、img、strong、input、select、lable、em、button、textarea 、selecting 块级元素&#xff1…

VSCode + PicGo + Github 实现markdown图床管理

目录 PicGo客户端VSvode插件 PicGo客户端 PicGo 是一个图片上传管理工具 官网&#xff1a;https://molunerfinn.com/PicGo/ github图传使用说明&#xff1a;https://picgo.github.io/PicGo-Doc/zh/guide/config.html#GitHub图床 步骤&#xff1a; 1、创建一个github公开仓库…

Mac玩《幻兽帕鲁》为什么打不开D3DMetal?d3d错误怎么办 d3dxl error

我之前发了一篇讲Mac电脑玩Steam热门新游《幻兽帕鲁》的文章&#xff08;没看过的点这里&#xff09;&#xff0c;后来也看到很多朋友去尝试了&#xff0c;遇到了一些问题&#xff0c;无法进入《幻兽帕鲁》游戏&#xff0c;或者是玩的时候卡顿以及出现黑屏&#xff0c;通过我的…

vue 基于elementUI/antd-vue, h函数实现message中嵌套链接跳转到指定路由 (h函数点击事件的写法)

效果如图&#xff1a; 点击message 组件中的 工单管理&#xff0c; 跳转到工单管理页面。 以下是基于vue3 antd-vue 代码如下&#xff1a; import { message } from ant-design-vue; import { h, reactive, ref, watch } from vue; import { useRouter } from vue-router; c…

智慧公厕对于智慧城市管理的意义

近年来&#xff0c;智慧城市的概念不断被提及&#xff0c;而智慧公厕作为智慧城市管理的重要组成部分&#xff0c;其在监测、管理和养护方面发挥着重要的作用。智慧公厕不仅是城市市容提升的重要保障&#xff0c;还能提升城市环境卫生管理的质量&#xff0c;并有效助力创造清洁…

代码+视频,R语言使用BOOT重抽样获取cox回归方程C-index(C指数)可信区间

bootstrap自采样目前广泛应用与统计学中&#xff0c;其原理很简单就是通过自身原始数据抽取一定量的样本&#xff08;也就是取子集&#xff09;&#xff0c;通过对抽取的样本进行统计学分析&#xff0c;然后继续重新抽取样本进行分析&#xff0c;不断的重复这一过程N&#xff0…

python redis中blpop和lpop的区别

python redis中lpop()方法是获取并删除左边第一个对象。 def lpop(self,name: str,count: Optional[int] None,) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]:"""Removes and returns the first elements of the list name.By de…

Java在线租车汽车租赁系统设计与实现(Idea+Springboot+mysql)

博主介绍&#xff1a;黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者&#xff0c;CSDN博客专家&#xff0c;在线教育专家&#xff0c;CSDN钻石讲师&#xff1b;专注大学生毕业设计教育和辅导。 所有项目都配有从入门到精通的基础知识视频课程&#xff…

17 deque

容器适配器 适配器 适配器是一种射击模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结)&#xff0c;该种模式是将一个类的接口转换成客户希望的另一个接口 STL库中的stack和queue的结构 虽然stack和queue也可以存放元素&#xff0c;但…

【STL】deque双端开口容器

1.关于deque容器说明 deque容器与vector容器差不多&#xff0c;但deque是双端开口容器&#xff0c;可以在两端插入和删除元素 push_front( )//在头部插入push_back( )//在尾部插入pop_front( )//在头部删除pop_back( ) //在尾部删除 其他相应函数与vector差不多&#xff0c;…

2024 年(第 12 届)“泰迪杯”数据挖掘挑战赛—— C 题:竞赛论文的辅助自动评阅完整思路与源代码分享

一、问题背景 近年来我国各领域各层次学科竞赛百花齐放&#xff0c;层出不穷&#xff0c;学生参与度也越来越高。随着参赛队伍的增 加&#xff0c;评阅论文的工作量急剧增加&#xff0c;这对评阅论文的人力要求也越来越大。因此引入机器辅助评阅成为竞赛主办方的现实需求。 在…

AI智能客服的开发流程

实现智能客服涉及多个步骤&#xff0c;包括数据收集、模型训练、部署和优化。以下是一个基本的实现智能客服的流程&#xff0c;希望对大家有所帮助。 1.数据收集&#xff1a; 收集与客服相关的数据&#xff0c;包括对话记录、常见问题、知识库等。 数据可以来自历史的客服对话…

成都伊理威:开抖音小店到底能赚钱吗

在数字时代的浪潮中&#xff0c;抖音如同一颗璀璨的新星&#xff0c;吸引了无数创业者的目光。不少人心中盘旋着同一个问题&#xff1a;“开抖音小店&#xff0c;真的能赚钱吗?”事实上&#xff0c;答案并非简单的“能”或“不能”&#xff0c;而是一个充满变数的命题。 开设抖…

蓝桥杯真题|02普及-真题

目录 [蓝桥杯 2017 省 B] 日期问题 题目描述 输入格式 输出格式 输入输出样例 代码及思路 [蓝桥杯 2021 省 B] 时间显示 题目描述 输入格式 输出格式 输入输出样例 说明/提示 代码及思路 [蓝桥杯 2017 省 B] 日期问题 题目描述 小明正在整理一批历史文献。这些历…

uniapp+uview 学习笔记(一)—— H5开发

文章目录 前言一、开发步骤1.创建项目2.安装组件库并导入使用3.封装请求4.国际化5.打包 总结 前言 本文主要介绍使用uniapp框架和uview组件库进行H5开发&#xff0c;需要用到的开发工具为HBuilder X。 一、开发步骤 1.创建项目 打开HBuilder X&#xff0c;在顶部栏目选择 新…

使用map和set实现简单的词频统计

一、运行效果图 二、代码示例 #include <iostream> #include <fstream> #include <sstream> #include <string> #include <map> #include <set> #include <vector> #include <algorithm> using namespace std;class TextQuer…

【Leetcode每日一题】 递归 - 两两交换链表中的节点(难度⭐)(38)

1. 题目解析 题目链接&#xff1a;24. 两两交换链表中的节点 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 一、理解递归函数的含义 首先&#xff0c;我们需要明确递归函数的任务&#xff1a;给定一个链表&#xf…

Matlab|【免费】基于半不变量的概率潮流计算

目录 主要内容 部分代码 结果一览 下载链接 主要内容 该程序主要内容是基于半不变量法的概率潮流&#xff0c;包含蒙特卡洛模拟法、半不变量法&#xff0b;Gram-Charlier级数展开以及半不变量法Cornish-Fisher级数展开三种方法以及效果对比&#xff0c;模型考虑了…

【AIGC工具】图片转3d模型

一个在线可以将图片转成3d模型的工具网站 有图形化界面 直接上传图片即可 网站地址&#xff1a; https://huggingface.co/spaces/Zhengyi/CRM

一文读懂systemd

文章目录 背景知识为何而来替代了什么init的缺点 systemd的系统架构图systemd (PID 1):systemd-journald:systemd-logind:systemd-udevd:systemd-networkd:systemd-resolved:systemd-timedated: systemd包括了哪些systemctljournalctlloginctlhostnamectltimedatectllocalectlm…