【数据结构】红黑树


在这里插入图片描述

1. 红黑树的概念

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

2. 红黑树的性质

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

满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍(最长路径 <= 最短路径×2 ),为什么会这样呢?-- 如下图
在这里插入图片描述
如果比较AVL树和红黑树的搜索,AVL树的查找效率更高为:logN;

在这里插入图片描述

但是在插入和删除时红黑树的效率更高,它不需要严格的平衡,旋转的次数更少。

3. 红黑树的实现

3.1 节点与树的定义

插入时,我们是插入黑节点好还是红节点好呢?选择破坏性质3还是性质4呢?

  • 如果插入黑色的,每条路径上的黑色节点数量就不相等了,每条路径都得改,非常麻烦。
  • 如果插入红色的,如果它的父亲是黑色的,那就没有破坏任何规则;若父亲是红色,那就存在了连续的红节点,最长路径可能就会超过最短路径的二倍,需要进行调整了。

那我们肯定选择调整次数少的方式,因此新增节点的颜色给红色(注意:当插入节点为根节点时,应该是黑色

  • 节点的结构
	//颜色
	enum Color
	{
		RED = 0,
		BLACK
	};

	template<class K, class V>
	struct TreeNode
	{
		pair<K, V>  _kv;
		TreeNode<K, V>* _left;
		TreeNode<K, V>* _right;
		TreeNode<K, V>* _parent;//前驱节点
		Color _col;

		TreeNode(const pair<K, V>& kv)
			:_kv(kv)
			, _left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			, _col(RED)//默认新节点为红色的
		{}
	};
  • 树的结构
	template<class K,class V>
	class RBTree
	{
		typedef TreeNode<K, V> Node;
	public:
		RBTree()
			:_root(nullptr)
		{}
		RBTree(const RBTree<K, V>& tree)
		{
			this->_root = Copy(tree._root);
		}
		RBTree<K, V>& operator=(RBTree<K, V> tree)
		{
			swap(this->_root, tree._root);//现代写法
			return *this;
		}
		~RBTree()
		{
			Destroy(_root);
		}

		bool insert(const pair<K, V>& kv)
		{

		}

		Node* find(const pair<K, V>& kv)
		{

		}

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

	private:
		void Destroy(Node* root)
		{
			if (root == nullptr)
				return;
			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
		}

		Node* Copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;
			_root->_left = Copy(root->_left);
			_root->_right = Copy(root->_right);
			return _root;
		}
		
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;
			_InOrder(root->_left);
			cout << root->_kv.first << " ";
			_InOrder(root->_right);
		}

		Node* _root;
	};

3.2 查找

对于查找功能,搜索树都是一样的,非常简单就不赘述了。

		Node* find(const pair<K, V>& kv)
		{
			Node* cur = _root;

			while (cur)
			{
				if (cur->_kv.first < kv.first)
					cur = cur->_right;
				else if (cur->_kv.first > kv.first)
					cur = cur->_left;
				else
					return  cur;
			}
			return nullptr;
		}

3.3 插入(重点)

在插入时,我们要先按照搜索树的规则找到插入位置,然后将新节点插入;新节点插入后,要检查红黑树的规则有没有被破坏注意:如果新插入的节点就是根节点,需要遵守规则2,根节点应为黑色

		bool insert(const pair<K, V>& kv)
		{
			if (_root == nullptr)
			{
				_root = new Node(kv);
				//如果插入的节点是头节点,需要遵守规则2(根是黑的)
				//需要将默认的红色改为黑
				_root->_col = BLACK;
				return true;
			}
			
			Node* cur = _root;
			Node* parent = cur->_parent;
			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);
			cur->_parent = parent;
			//父亲连孩子
			if (parent->_kv.first < kv.first)
				parent->_right = cur;
			else
				parent->_left = cur;

			//检查是否违反红黑树的规则
			//....
		}

检测新节点插入后,红黑树的性质是否造到破坏;因为新节点的默认颜色是红色,因此,

  • 如果其父亲节点的颜色是黑色,没有违反红黑树任何规则,则不需要调整
  • 当其父亲节点颜色为红色时,就违反了性质三:不能有连在一起的红色节点

此时需要对红黑树分情况来讨论:
在这里插入图片描述

那违反规则后如何调整呢?又分为一下几种情况:

  1. 若新增节点为红,父亲为红,爷爷为黑,叔叔存在且为红,需要变色处理

在这里插入图片描述

但是当爷爷节点不是子树而是根时,需要将其变成黑色;若是子树,则保持它的红色,继续向上调整。

在这里插入图片描述

在这里插入图片描述

  1. 若新增节点为红,父亲为红,爷爷为黑,叔叔不存在或者叔叔存在且为黑,需要旋转+变色处理

右单旋的情况

  • 父亲在爷爷的左边、新增在父亲的左边
  • 如果叔叔不存在,旋转后将父亲变黑,爷爷变红
  • 如果叔叔存在且为黑,旋转后将父亲变黑,爷爷变红

在这里插入图片描述
新增节点为红色的原因如下:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

左右双旋的情况

  • 父亲在爷爷的左边、新增在父亲的右边
  • 如果叔叔不存在,旋转后将新增节点变黑,爷爷变红
  • 如果叔叔存在且为黑,旋转后将新增节点变黑,爷爷变红

在这里插入图片描述

在这里插入图片描述

由于旋转的具体代码我们在AVL树种已经写过,这里就不给出具体步骤了。

	private:
		void RotateR(Node* parent)
		{
			Node* SubL = parent->_left;
			Node* SubLR = SubL->_right;

			//降级连兵
			parent->_left = SubLR;
			if (SubLR)
				SubLR->_parent = parent;
			Node* parentParent = parent->_parent;
			//升级连将
			SubL->_right = parent;
			parent->_parent = SubL;

			//将连将
			SubL->_parent = parentParent;
			if (parentParent == nullptr)
			{
				_root = SubL;
			}
			else
			{
				if (parentParent->_left == parent)
					parentParent->_left = SubL;
				else
					parentParent->_right = SubL;
			}
		}

		void RotateL(Node* parent)
		{
			Node* SubR = parent->_right;
			Node* SubRL = SubR->_left;

			//降级连兵
			parent->_right = SubRL;
			if (SubRL)
				SubRL->_parent = parent;
			Node* parentParent = parent->_parent;
			//升级连将
			SubR->_left = parent;
			parent->_parent = SubR;

			//将连将
			SubR->_parent = parentParent;
			if (parentParent == nullptr)
				_root = SubR;
			else
			{
				if (parentParent->_left == parent)
					parentParent->_left = SubR;
				else
					parentParent->_right = SubR;
			}
		}

当父亲在爷爷左侧时,大体框架就是这样:

在这里插入图片描述

父亲在爷爷的右侧时,也是类似的,我们仅给出旋转时的图:

新增在父亲的右侧,构成“直”树,采用左单旋

在这里插入图片描述


右左双旋
新增在父亲的左侧,构成“弯”树,采用右左双旋

在这里插入图片描述

具体的代码实现如下:

				//grandfather->_right == parent
				else
				{
					Node* uncle = grandfather->_left;
					//如果叔叔存在且为红,仅需要变色
					if (uncle && uncle->_col == RED)
					{
						parent->_col = BLACK;
						uncle->_col = BLACK;
						grandfather->_col = RED;
						//继续调整
						cur = grandfather;
						parent = cur->_parent;
					}
					//如果叔叔不存在 或者存在且为黑,需要旋转+变色
					else
					{
						//    g
						// u     p
						//           c
						if (parent->_right == cur)
						{
							RotateL(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						//      g
						//   u      p
						//	      c
						else
						{
							RotateR(parent);
							RotateL(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}
						break;
					}

那红黑树的插入就实现完了,完整代码如下:

		bool insert(const pair<K, V>& kv)
		{
			if (_root == nullptr)
			{
				_root = new Node(kv);
				//如果插入的节点是头节点,需要遵守规则2(根是黑的)
				//需要默认的红色改为黑
				_root->_col = BLACK;
				return true;
			}

			Node* cur = _root;
			Node* parent = cur->_parent;
			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);
			cur->_parent = parent;
			//父亲连孩子
			if (parent->_kv.first < kv.first)
				parent->_right = cur;
			else
				parent->_left = cur;
			//-----------------------------------------------------------------------
			//检查是否违反红黑树的规则
			//如果父亲存在且为红,则违反规则
			while (parent && parent->_col == RED)
			{
				Node* grandfather = parent->_parent;
				if (grandfather->_left == parent)
				{
					//根据爷爷节点找叔叔
					Node* uncle = grandfather->_right;

					//如果叔叔存在且为红,仅需要变色
					if (uncle && uncle->_col == RED)
					{
						parent->_col = BLACK;
						uncle->_col = BLACK;
						grandfather->_col = RED;
						//然后继续向上调整
						cur = grandfather;
						parent = cur->_parent;
					}
					//如果叔叔不存在 或者存在且为黑,需要旋转+变色
					else
					{
						//判断单旋(直树)还是双旋(弯树)
						//单旋
						if (parent->_left == cur)
						{	
							//       g
							//     p   u
							//	c
							RotateR(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						//双旋
						else
						{
							//        g
							//     p     u
							//	     c
							RotateL(parent);
							RotateR(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;

						}
						break;//旋转后已经符合规则,无需再调整了
					}
				}
				//grandfather->_right == parent
				else
				{
					Node* uncle = grandfather->_left;
					//如果叔叔存在且为红,仅需要变色
					if (uncle && uncle->_col == RED)
					{
						parent->_col = BLACK;
						uncle->_col = BLACK;
						grandfather->_col = RED;
						//继续调整
						cur = grandfather;
						parent = cur->_parent;
					}
					//如果叔叔不存在 或者存在且为黑,需要旋转+变色
					else
					{
						//    g
						// u     p
						//           c
						if (parent->_right == cur)
						{
							RotateL(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						//      g
						//   u      p
						//	      c
						else
						{
							RotateR(parent);
							RotateL(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}
						break;
					}
				}
			}
			//父亲不存在,或者父亲为黑时,无需判断,直接将根变黑
			_root->_col = BLACK;
			return true;
		}

3.4 红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质
    • 是否存在连续的红节点
    • 每条路径黑色节点的个数是否相等

在检查每条路径上黑节点的个数时,首先要有一个黑节点个数的标准值,可以是最左侧或者最右侧;同时还要有一个变量或者容器来存储当前路径中表黑节点的个数。

		bool IsRBTree()
		{
			Node* root = _root;
			if (root == nullptr)//空树也是
				return true;
			
			if (root->_col == RED)
			{
				cout << "根节点为红色,违反规则" << endl;
				return false;
			}

			//先统计最左侧路径上有多少黑节点作为标准
			Node* cur = root;
			int blackNum = 0;
			while (cur)
			{
				if (cur->_col == BLACK)
					blackNum++;
				cur = cur->_left;
			}
			//k用来记录路径中黑节点的个数
			int k = 0;
			//在依据标准黑节点的个数,比较其它路径
			return _IsRBTree(root, blackNum, k);
		}

有了黑节点的标准值后,我们才可正式开始比较每条路径黑节点的个数

		bool _IsRBTree(Node* root, int blackNum, int k)
		{
			//一条路径走完,比较黑节点的个数
			if (root == nullptr)
			{
				if (blackNum != k)
				{
					cout << "路径黑节点个数不相等,违反规则" << endl;
					return false;
				}
				return true;
			}
			Node* parent = root->_parent;
			if (parent && root->_col == RED && parent->_col == RED)
			{
				cout << "存在连续的红节点,违反规则" << endl;
				return false;
			}

			//统计黑节点
			if (root->_col == BLACK)
				k++;
			//统计完根,在依次统计左右子树
			return _IsRBTree(root->_left, blackNum, k)
				&& _IsRBTree(root->_right, blackNum, k);
		}

4. 红黑树与AVL树的比较

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

5.完整代码

namespace my
{
	//颜色
	enum Color
	{
		RED = 0,
		BLACK
	};

	template<class K, class V>
	struct TreeNode
	{
		pair<K, V>  _kv;
		TreeNode<K, V>* _left;
		TreeNode<K, V>* _right;
		TreeNode<K, V>* _parent;//前驱节点
		Color _col;

		TreeNode(const pair<K, V>& kv)
			:_kv(kv)
			, _left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			, _col(RED)//默认新节点为红色的
		{}
	};

	template<class K,class V>
	class RBTree
	{
		typedef TreeNode<K, V> Node;
	public:
		RBTree()
			:_root(nullptr)
		{}
		RBTree(const RBTree<K, V>& tree)
		{
			this->_root = Copy(tree._root);
		}
		RBTree<K, V>& operator=(RBTree<K, V> tree)
		{
			swap(this->_root, tree._root);//现代写法
			return *this;
		}
		~RBTree()
		{
			Destroy(_root);
		}

		bool insert(const pair<K, V>& kv)
		{
			if (_root == nullptr)
			{
				_root = new Node(kv);
				//如果插入的节点是头节点,需要遵守规则2(根是黑的)
				//需要默认的红色改为黑
				_root->_col = BLACK;
				return true;
			}

			Node* cur = _root;
			Node* parent = cur->_parent;
			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);
			cur->_parent = parent;
			//父亲连孩子
			if (parent->_kv.first < kv.first)
				parent->_right = cur;
			else
				parent->_left = cur;

			//检查是否违反红黑树的规则
			//如果父亲存在且为红,则违反规则
			while (parent && parent->_col == RED)
			{
				Node* grandfather = parent->_parent;
				if (grandfather->_left == parent)
				{
					//根据爷爷节点找叔叔
					Node* uncle = grandfather->_right;

					//如果叔叔存在且为红,仅需要变色
					if (uncle && uncle->_col == RED)
					{
						parent->_col = BLACK;
						uncle->_col = BLACK;
						grandfather->_col = RED;
						//然后继续向上调整
						cur = grandfather;
						parent = cur->_parent;
					}
					//如果叔叔不存在 或者存在且为黑,需要旋转+变色
					else
					{
						//判断单旋(直树)还是双旋(弯树)
						//单旋
						if (parent->_left == cur)
						{	
							//       g
							//     p   u
							//	c
							RotateR(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						//双旋
						else
						{
							//        g
							//     p     u
							//	     c
							RotateL(parent);
							RotateR(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;

						}
						break;//旋转后已经符合规则,无需再调整了
					}
				}
				//grandfather->_right == parent
				else
				{
					Node* uncle = grandfather->_left;
					//如果叔叔存在且为红,仅需要变色
					if (uncle && uncle->_col == RED)
					{
						parent->_col = BLACK;
						uncle->_col = BLACK;
						grandfather->_col = RED;
						//继续调整
						cur = grandfather;
						parent = cur->_parent;
					}
					//如果叔叔不存在 或者存在且为黑,需要旋转+变色
					else
					{
						//    g
						// u     p
						//           c
						if (parent->_right == cur)
						{
							RotateL(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						//      g
						//   u      p
						//	      c
						else
						{
							RotateR(parent);
							RotateL(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}
						break;
					}
				}
			}
			//父亲不存在,或者父亲为黑时,无需判断,直接将根变黑
			_root->_col = BLACK;
			return true;
		}

		Node* find(const pair<K, V>& kv)
		{
			Node* cur = _root;

			while (cur)
			{
				if (cur->_kv.first < kv.first)
					cur = cur->_right;
				else if (cur->_kv.first > kv.first)
					cur = cur->_left;
				else
					return  cur;
			}
			return nullptr;
		}

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

		bool IsRBTree()
		{
			Node* root = _root;
			if (root == nullptr)//空树也是
				return true;
			
			if (root->_col == RED)
			{
				cout << "根节点为红色,违反规则" << endl;
				return false;
			}

			//先统计最左侧路径上有多少黑节点作为标准
			Node* cur = root;
			int blackNum = 0;
			while (cur)
			{
				if (cur->_col == BLACK)
					blackNum++;
				cur = cur->_left;
			}
			//k用来记录路径中黑节点的个数
			int k = 0;
			//在依据标准黑节点的个数,比较其它路径
			return _IsRBTree(root, blackNum, k);
		}

		int Size()
		{
			return _Size(_root);
		}

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

	private:
		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;
		}

		int _Size(Node* root)
		{
			if (root == nullptr)
				return 0;

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

		bool _IsRBTree(Node* root, int blackNum, int k)
		{
			//一条路径走完,比较黑节点的个数
			if (root == nullptr)
			{
				if (blackNum != k)
				{
					cout << "路径黑节点个数不相等,违反规则" << endl;
					return false;
				}
				return true;
			}
			Node* parent = root->_parent;
			if (parent && root->_col == RED && parent->_col == RED)
			{
				cout << "存在连续的红节点,违反规则" << endl;
				return false;
			}

			//统计黑节点
			if (root->_col == BLACK)
				k++;
			//统计完根,在依次统计左右子树
			return _IsRBTree(root->_left, blackNum, k)
				&& _IsRBTree(root->_right, blackNum, k);
		}

		void RotateR(Node* parent)
		{
			Node* SubL = parent->_left;
			Node* SubLR = SubL->_right;

			//降级连兵
			parent->_left = SubLR;
			if (SubLR)
				SubLR->_parent = parent;
			Node* parentParent = parent->_parent;
			//升级连将
			SubL->_right = parent;
			parent->_parent = SubL;

			//将连将
			SubL->_parent = parentParent;
			if (parentParent == nullptr)
			{
				_root = SubL;
			}
			else
			{
				if (parentParent->_left == parent)
					parentParent->_left = SubL;
				else
					parentParent->_right = SubL;
			}
		}

		void RotateL(Node* parent)
		{
			Node* SubR = parent->_right;
			Node* SubRL = SubR->_left;

			//降级连兵
			parent->_right = SubRL;
			if (SubRL)
				SubRL->_parent = parent;
			Node* parentParent = parent->_parent;
			//升级连将
			SubR->_left = parent;
			parent->_parent = SubR;

			//将连将
			SubR->_parent = parentParent;
			if (parentParent == nullptr)
				_root = SubR;
			else
			{
				if (parentParent->_left == parent)
					parentParent->_left = SubR;
				else
					parentParent->_right = SubR;
			}
		}
		void Destroy(Node* root)
		{
			if (root == nullptr)
				return;
			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
		}

		Node* Copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;
			_root->_left = Copy(root->_left);
			_root->_right = Copy(root->_right);
			return _root;
		}

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

		Node* _root;
	};
}
  • 23
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
红黑树是一种自平衡的二叉查找树,它在插入和删除节点时能够保持树的平衡。红黑树的概念可以参考。在Java中实现红黑树,可以按照以下步骤进行: 1. 首先将红黑树当作一颗二叉查找树,将新节点插入到适当的位置上。 2. 将插入的节点着色为"红色"。 3. 根据红黑树的特性,通过一系列的旋转和着色等操作,使树重新保持红黑树的性质。 具体的插入过程可以参考中提供的代码。在代码中,使用了左旋转、右旋转和颜色翻转等操作来重新平衡红黑树。 首先,如果节点的右子树是红色而左子树是黑色,可以通过左旋转操作将其变为左子树为红色,右子树为黑色的情况。 其次,如果节点的左子树和左子树的左子树都是红色,可以通过右旋转操作将其修正为上述情况。 最后,如果节点的左子树和右子树都是红色,可以通过颜色翻转操作将其修正为左子树和右子树都为黑色的情况。 在插入完节点后,需要将根节点的颜色设置为黑色,以确保红黑树的性质满足。 这样,通过以上的步骤,就能够实现对红黑树的插入操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Java数据结构红黑树的真正理解](https://download.csdn.net/download/weixin_38622475/12770272)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [Java高阶数据结构红黑树](https://blog.csdn.net/qq15035899256/article/details/126678970)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Java数据结构——红黑树](https://blog.csdn.net/weixin_30699463/article/details/95256212)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值