数据结构与算法:AVL树

目录

什么是AVL树

 AVL树的结构

 AVL树的增删查改

判断AVL树是否平衡

测试函数

AVL树的性能

简易AVL树代码 


什么是AVL树

首先还是介绍一下什么是AVL树

AVL树是一种自平衡二叉搜索树,它的每个节点都保存一个平衡因子(balance factor),用于判断是否需要进行旋转操作来保持树的平衡。平衡因子是右子树的高度减去左子树的高度(有时相反),因此它的值只可能是-1、0或1。如果插入或删除一个节点后,某个节点的平衡因子的绝对值大于1,就需要进行旋转操作来重新平衡这棵树。

AVL树的插入、删除和查找操作的时间复杂度都是O(log n),其中n是树中节点的数量。AVL树的性能比普通的二叉搜索树更稳定,因为它保证了树的高度始终是O(log n)级别的。

 AVL树的结构

通过之前二叉搜索树和set与map的学习,我们传入pair这个键值对,然后由于AVL树的特殊操作,我们也需要通过三叉链来实现

template<class K, class V>
struct AVLTreeNode {
	// 三叉链结构
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	AVLTreeNode(const pair<K,V>& kv)
	: _left(nullptr)
	,_right(nullptr)
	,_parent(nullptr)
	,_kv(kv)
	,_bf(0)
	{}
	
	// 键值对
	pair<K, V> _kv;
	// balance fator
	int _bf;
};

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



private:
	Node* _root;
};

 AVL树的增删查改

插入部分就跟讲的搜索二叉树插入一致

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

		if (_root == nullptr) {

			_root = new Node(kv);
			return true;
		}
	

		Node* parent = nullptr;
		Node* current = _root;

		while(current != nullptr) {
				
			parent = current;

			if (current->_kv.first > kv.first)
				current = current->_left;

			else if (current->_kv.first < kv.first)
				current = current->_right;
				
			else
				return false;
				
		}

		current = new Node(kv);
		// 判断current在parrent的左还是右
		if (parent->_kv.first < kv.first) {

			parent->_right = current;
			// parent赋值给current的父指针
			current->_parent = parent;
		}
		else {

			parent->_left = current;
			current->_parent = parent;
		}
		return true;
    }

我们从AVL树的定义知道我们在插入节点的时候需要控制这棵树的平衡程度,所以我们不仅要实现,插入,还得在插入后来判断是否平衡,以及平衡后如何旋转

如图为AVL的平衡情况判断

		// 完成插入后开始更新平衡因子_bf
		
		while (parent!=nullptr) {	// 能不能current != _root
				
			// 插入后如果在父节点左边
			if (current == parent->_left)
				parent->_bf--;
			else
				parent->_bf++;
				
			// 如果父节点为0,跳出循环不再调整
			if (parent->_bf == 0) {
				break;
			}	
			// 如果父节点不为0,那么往上调整父节点的父节点
			else if (parent->_bf == 1 || parent->_bf == -1) {
				// 更新current
				current = parent;
				// 更新parent
				parent = parent->_parent;
			}
			// 为-2 2时需要旋转
			else if(parent->_bf == 2 || parent->_bf == -2){

				// 单旋转的场景
				if (parent->_bf == 2 && current->_bf == 1) {

					rotateL(parent);
				}
				else if (parent->_bf == -2 && current->_bf == -1) {

					rotateR(parent);
				}
				// 双旋转的场景
				else if (parent->_bf == 2 && current->_bf == -1) {
					
					rotateRL(parent);
				}
				else if (parent->_bf == -2 && current->_bf == 1) {
					
					rotateLR(parent);
				}
				
				// 只要通过了旋转 就已经实现了平衡因子
				break;
			}

			else {
				cout << " 代码有误" << endl;
				assert(false);
			}

如何实现AVL树的旋转呢?我们先试着分析一下 

首先我们先探讨一下平衡后的子树,需不需要向上更新平衡因子?

 注意各部分分析,需要结合代码与图像! 这一部分真的好难 红黑树怎么办啊

情况一:

较高的子树为最右子树

	// 左单旋转
	void rotateL(Node* root) {

		Node* parent = root;
		// 相对根的右边
		Node* subR = root->_right;
		// 相对根的右边的左边
		Node* subRL = subR->_left;
		Node* GrandParent = parent->_parent;

		// 旋转后的相对位置
		subR->_left = parent;
		parent->_right = subRL;

		if (subRL != nullptr)
			subRL->_parent = parent;
		// else时为空指针,空指针走->_parent会报错

		
		parent->_parent = subR;
		// 将subR设为新的父指针 
		parent = subR;
		
		// 传入的root一定是 _root 吗
		if (GrandParent == nullptr) {
			// 是根的话 就需要将parent的位置设为_root
			_root = parent;
			parent->_parent = GrandParent;
		}
			
		else {
			// 传入的为子树的根所以grandparent不为空
			
			if (GrandParent->_left == root)
				// 不是根的话就需要把grandpa的左右指向parent
				GrandParent->_left = parent;
			else
				GrandParent->_right = parent;

			// 实现连接
			parent->_parent = GrandParent;
		}

		parent->_bf = parent->_left->_bf = 0;

	}

同理这右单旋转也几乎一致

情况二:

较高子树为最左子树

// 右单旋转
	void rotateR(Node* root) {
	
		Node* parent = root;
		// 注释参照左旋转
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* GrandParent = parent->_parent;

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

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

			parent->_parent = GrandParent;

		}

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

情况三:

较高子树为右子树的左子树

	//  双旋 右+左
	void rotateRL(Node* root) {
		
		Node* parent = root;
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		int bf = subRL->_bf;

		// 因为对于parent来说,右树高度过高,需要调整
		
		// 相对root->right 这个节点左树过高需要右旋
		rotateR(root->_right);
		// 右旋后相对parent来说 右树高度过高需要左旋
		rotateL(root);
		
		// 通过 subRL 平衡因子来区分多种情况
		if (bf == 0) {
			
			// 插入的节点恰好为subRL
			subR->_bf = parent->_bf = subRL->_bf = 0;
		}
		else if (bf == -1) {
			// 往subRL的左树插入
			parent->_bf = subRL->_bf = 0;
			subR->_bf = 1;
		}
		else if (bf == 1) {
			// 往subRL的右树插入
			subRL->_bf = subR->_bf = 0;
			parent->_bf = -1;
		}
		else
			assert(false);
	}

情况四:

较高子树为左子树的右子树

	//  双旋 左+右
	void rotateLR(Node* root) {

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

		int bf = subLR->_bf;

		rotateL(root->_left);
		rotateR(root);

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

	}

到了这里insert的内容就完成了!这个模块的最终代码 

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

		if (_root == nullptr) {

			_root = new Node(kv);
			return true;
		}
	

		Node* parent = nullptr;
		Node* current = _root;

		while(current != nullptr) {
				
			parent = current;

			if (current->_kv.first > kv.first)
				current = current->_left;

			else if (current->_kv.first < kv.first)
				current = current->_right;
				
			else
				return false;
				
		}

		current = new Node(kv);
		// 判断current在parrent的左还是右
		if (parent->_kv.first < kv.first) {

			parent->_right = current;
			// parent赋值给current的父指针
			current->_parent = parent;
		}
		else {

			parent->_left = current;
			current->_parent = parent;
		}
		
		// 完成插入后开始更新平衡因子_bf
		
		while (parent!=nullptr) {	// 能不能current != _root
				
			// 插入后如果在父节点左边
			if (current == parent->_left)
				parent->_bf--;
			else
				parent->_bf++;
				
			// 如果父节点为0,跳出循环不再调整
			if (parent->_bf == 0) {
				break;
			}	
			// 如果父节点不为0,那么往上调整父节点的父节点
			else if (parent->_bf == 1 || parent->_bf == -1) {
				// 更新current
				current = parent;
				// 更新parent
				parent = parent->_parent;
			}
			// 为-2 2时需要旋转
			else if(parent->_bf == 2 || parent->_bf == -2){

				// 单旋转的场景
				if (parent->_bf == 2 && current->_bf == 1) {

					rotateL(parent);
				}
				else if (parent->_bf == -2 && current->_bf == -1) {

					rotateR(parent);
				}
				// 双旋转的场景
				else if (parent->_bf == 2 && current->_bf == -1) {
					
					rotateRL(parent);
				}
				else if (parent->_bf == -2 && current->_bf == 1) {
					
					rotateLR(parent);
				}
				
				// 只要通过了旋转 就已经实现了平衡因子
				break;
			}

			else {
				cout << " 代码有误" << endl;
				assert(false);
			}
		
				
		}

		return true;
 
	}
	// 左单旋转
	void rotateL(Node* root) {

		Node* parent = root;
		// 相对根的右边
		Node* subR = root->_right;
		// 相对根的右边的左边
		Node* subRL = subR->_left;
		Node* GrandParent = parent->_parent;

		// 旋转后的相对位置
		subR->_left = parent;
		parent->_right = subRL;

		if (subRL != nullptr)
			subRL->_parent = parent;
		// else时为空指针,空指针走->_parent会报错

		
		parent->_parent = subR;
		// 将subR设为新的父指针 
		parent = subR;
		
		// 传入的root一定是 _root 吗
		if (GrandParent == nullptr) {
			_root = parent;
			parent->_parent = GrandParent;
		}
			
		else {
			// 传入的为子树的根所以grandparent不为空
			
			if (GrandParent->_left == root)
				GrandParent->_left = parent;
			else
				GrandParent->_right = parent;

			// 实现连接
			parent->_parent = GrandParent;
		}

		parent->_bf = parent->_left->_bf = 0;

	}
	// 右单旋转
	void rotateR(Node* root) {
	
		Node* parent = root;
		// 注释参照左旋转
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* GrandParent = parent->_parent;

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

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

			parent->_parent = GrandParent;

		}

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


	//  双旋 右+左
	void rotateRL(Node* root) {
		
		Node* parent = root;
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		int bf = subRL->_bf;

		// 因为对于parent来说,右树高度过高,需要调整
		
		// 相对root->right 这个节点左树过高需要右旋
		rotateR(root->_right);
		// 右旋后相对parent来说 右树高度过高需要左旋
		rotateL(root);
		
		// 通过 subRL 平衡因子来区分多种情况
		if (bf == 0) {
			
			// 插入的节点恰好为subRL
			subR->_bf = parent->_bf = subRL->_bf = 0;
		}
		else if (bf == -1) {
			// 往subRL的左树插入
			parent->_bf = subRL->_bf = 0;
			subR->_bf = 1;
		}
		else if (bf == 1) {
			// 往subRL的右树插入
			subRL->_bf = subR->_bf = 0;
			parent->_bf = -1;
		}
		else
			assert(false);
	}

	//  双旋 左+右

	void rotateLR(Node* root) {

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

		int bf = subLR->_bf;

		rotateL(root->_left);
		rotateR(root);

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

	}

insert完结撒花~           一个插入模块240行代码真的心碎!

循环遍历查找即可

	// 查
	bool find(const pair<K, V>& kv) {

		Node* current = _root;
		while (current != nullptr) {

			if (current->_kv.first < kv.first)
				current = current->_left;
			else if (current->_kv.first > kv.first)
				current = current->_right;
			else
				return true;
		}

		return false;
	}
判断AVL树是否平衡

我们这里通过求子树高度,计算绝对值小于2来实现,因为平衡的本质就是高度差小

	bool _ifBalance(Node* root) {
		 
		// 通过叶子节点的高度差来实现
		
		if (root == nullptr)
			return true;
		
		int leftHeight = _height(root->_left);
		int rightHeight = _height(root->_right);

		if (rightHeight - leftHeight != root->_bf) {

			cout << root->_kv.first << " 平衡因子异常" << endl;
			return false;
		}
		// 求绝对值 以及进入左子树与右子树
		return abs(rightHeight - leftHeight) < 2
			&& _ifBalance(root->_left)
			&& _ifBalance(root->_right);


	}
	// 子树高度函数
	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;
	}

测试函数

void test1() {


	AVLTree<string, string> avl_tree;

	avl_tree.insert(make_pair("Hello", "2022044026"));

	avl_tree.inOrder();
	

}
// 验证是一个搜索树
void test2() {

	int arr[] = {16, 3, 7, 11, 9, 26, 18, 14, 15 };
	AVLTree<int, int> avl_tree;
	for (auto& e : arr) {

		avl_tree.insert(make_pair(e, e));
	}
	avl_tree.inOrder();
	cout << avl_tree.ifBalance() << endl;
}
// 随机数验证实现AVL的平衡
void test3() {

	const int N = 30;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

	for (size_t i = 0; i < N; i++)
		v.push_back(rand());

	AVLTree<int, int> tree;
	for (auto& e : v) {

		tree.insert(make_pair(e, e));
		// cout << "insert: " << e << " -> " << tree.ifBalance() << endl;
	}
		

	cout << tree.ifBalance() << endl;
	tree.inOrder();
}

AVL树的性能

简易AVL树代码 

ps:放在.h文件一面使用即可

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

template<class K, class V>
struct AVLTreeNode {
	// 三叉链结构
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	AVLTreeNode(const pair<K,V>& kv)
	: _left(nullptr)
	,_right(nullptr)
	,_parent(nullptr)
	,_kv(kv)
	,_bf(0)
	{}
	
	// 键值对
	pair<K, V> _kv;
	// balance fator
	int _bf;
};

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* current = _root;

		while(current != nullptr) {
				
			parent = current;

			if (current->_kv.first > kv.first)
				current = current->_left;

			else if (current->_kv.first < kv.first)
				current = current->_right;
				
			else
				return false;
				
		}

		current = new Node(kv);
		// 判断current在parrent的左还是右
		if (parent->_kv.first < kv.first) {

			parent->_right = current;
			// parent赋值给current的父指针
			current->_parent = parent;
		}
		else {

			parent->_left = current;
			current->_parent = parent;
		}
		
		// 完成插入后开始更新平衡因子_bf
		
		while (parent!=nullptr) {	// 能不能current != _root
				
			// 插入后如果在父节点左边
			if (current == parent->_left)
				parent->_bf--;
			else
				parent->_bf++;
				
			// 如果父节点为0,跳出循环不再调整
			if (parent->_bf == 0) {
				break;
			}	
			// 如果父节点不为0,那么往上调整父节点的父节点
			else if (parent->_bf == 1 || parent->_bf == -1) {
				// 更新current
				current = parent;
				// 更新parent
				parent = parent->_parent;
			}
			// 为-2 2时需要旋转
			else if(parent->_bf == 2 || parent->_bf == -2){

				// 单旋转的场景
				if (parent->_bf == 2 && current->_bf == 1) {

					rotateL(parent);
				}
				else if (parent->_bf == -2 && current->_bf == -1) {

					rotateR(parent);
				}
				// 双旋转的场景
				else if (parent->_bf == 2 && current->_bf == -1) {
					
					rotateRL(parent);
				}
				else if (parent->_bf == -2 && current->_bf == 1) {
					
					rotateLR(parent);
				}
				
				// 只要通过了旋转 就已经实现了平衡因子
				break;
			}

			else {
				cout << " 代码有误" << endl;
				assert(false);
			}
		
				
		}

		return true;
 
	}

	// 删

	// 删除的难度较大,不过其内部知识根 与 insert相似

	// 查
	bool find(const K& key) {

		Node* current = _root;
		while (current != nullptr) {

			if (current->_kv.first < key)
				current = current->_left;
			else if (current->_kv.first > key)
				current = current->_right;
			else
				return true;
		}

		return false;
	}


	// 提供外部调用的接口

	// 中序遍历 升序打印
	void inOrder() { _inOrder(_root); cout << endl; }
	// 判断平衡
	bool ifBalance() { return _ifBalance(_root); }
	// 返回树高度
	int Height() { cout << "树的高度为: "; return _height(_root); }
	// 返回树节点个数
	size_t size() { cout << "节点个数为:"; return _size(_root); }

private:

	// 左单旋转
	void rotateL(Node* root) {

		Node* parent = root;
		// 相对根的右边
		Node* subR = root->_right;
		// 相对根的右边的左边
		Node* subRL = subR->_left;
		Node* GrandParent = parent->_parent;

		// 旋转后的相对位置
		subR->_left = parent;
		parent->_right = subRL;

		if (subRL != nullptr)
			subRL->_parent = parent;
		// else时为空指针,空指针走->_parent会报错


		parent->_parent = subR;
		// 将subR设为新的父指针 
		parent = subR;

		// 传入的root一定是 _root 吗
		if (GrandParent == nullptr) {
			// 是根的话 就需要将parent的位置设为_root
			_root = parent;
			parent->_parent = GrandParent;
		}

		else {
			// 传入的为子树的根所以grandparent不为空

			if (GrandParent->_left == root)
				// 不是根的话就需要把grandpa的左右指向parent
				GrandParent->_left = parent;
			else
				GrandParent->_right = parent;

			// 实现连接
			parent->_parent = GrandParent;
		}

		parent->_bf = parent->_left->_bf = 0;

	}
	// 右单旋转
	void rotateR(Node* root) {

		Node* parent = root;
		// 注释参照左旋转
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* GrandParent = parent->_parent;

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

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

		parent->_parent = subL;
		parent = subL;

		if (GrandParent == nullptr) {

			_root = parent;
			parent->_parent = GrandParent;
		}
		else {
			if (GrandParent->_left == root)
				GrandParent->_left = parent;
			else
				GrandParent->_right = parent;

			parent->_parent = GrandParent;

		}

		parent->_bf = parent->_right->_bf = 0;

	}

	//  双旋 右+左
	void rotateRL(Node* root) {

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

		int bf = subRL->_bf;

		// 因为对于parent来说,右树高度过高,需要调整

		// 相对root->right 这个节点左树过高需要右旋
		rotateR(root->_right);
		// 右旋后相对parent来说 右树高度过高需要左旋
		rotateL(root);

		// 通过 subRL 平衡因子来区分多种情况
		if (bf == 0) {

			// 插入的节点恰好为subRL
			subR->_bf = parent->_bf = subRL->_bf = 0;
		}
		else if (bf == -1) {
			// 往subRL的左树插入
			parent->_bf = subRL->_bf = 0;
			subR->_bf = 1;
		}
		else if (bf == 1) {
			// 往subRL的右树插入
			subRL->_bf = subR->_bf = 0;
			parent->_bf = -1;
		}
		else
			assert(false);
	}
	//  双旋 左+右
	void rotateLR(Node* root) {

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

		int bf = subLR->_bf;

		rotateL(root->_left);
		rotateR(root);

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

	}



	void _inOrder(Node* root) {

		if (root == nullptr)
			return;

		_inOrder(root->_left);
		// cout << root->_kv.first << " " << root->_kv.second << endl;
		cout << root->_kv.first << " ";
		_inOrder(root->_right);
	}

	bool _ifBalance(Node* root) {
		 
		// 通过叶子节点的高度差来实现
		
		if (root == nullptr)
			return true;
		
		int leftHeight = _height(root->_left);
		int rightHeight = _height(root->_right);

		if (rightHeight - leftHeight != root->_bf) {

			cout << root->_kv.first << " 平衡因子异常" << endl;
			return false;
		}
		// 求绝对值 以及进入左子树与右子树
		return abs(rightHeight - leftHeight) < 2
			&& _ifBalance(root->_left)
			&& _ifBalance(root->_right);


	}

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

	size_t _size(Node* root) {

		if (root == nullptr)
			return 0;

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

private:
	// 一定需要初始化 不然代码运行有bug
	Node* _root = nullptr;


};


// 测试区

void test1() {


	AVLTree<string, string> avl_tree;

	avl_tree.insert(make_pair("Hello", "2022044026"));

	avl_tree.inOrder();

}
// 验证是一个搜索树
void test2() {

	int arr[] = {16, 3, 7, 11, 9, 26, 18, 14, 15 };
	AVLTree<int, int> avl_tree;
	for (auto& e : arr) {

		avl_tree.insert(make_pair(e, e));
	}
	avl_tree.inOrder();
	cout << avl_tree.ifBalance() << endl;
}
// 随机数验证实现AVL的平衡
void test3() {

	const int N = 30000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

	for (size_t i = 0; i < N; i++)
		v.push_back(rand()+i);

	AVLTree<int, int> tree;
	for (auto& e : v) {

		tree.insert(make_pair(e, e));
		// cout << "insert: " << e << " -> " << tree.ifBalance() << endl;
	}
		
	cout << tree.ifBalance() << endl;
	// tree.inOrder();
	cout << tree.Height() << endl;
	cout << tree.size() <<  endl;
	cout << tree.find(10) << endl;
}

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值