AVL树的实现

AVL树是平衡二叉树中的一种,下面讨论讨论它的实现。一个AVL树满足基本的二叉查找树的要求,另外,每个AVL树的节点,它的左子树和右子树的高度差不超过1。

AVL树应为这个条件,从而保证了二叉查找树的高度是受控的,不会出现一般的二叉查找树那样可能随着不断的插入和删除操作,最后退化成一个链表。也是因为这个额外的要求,导致了AVL树的插入和删除操作和一般的二叉查找树不同,每次插入和删除的时候都需要检查左右子树的高度差,做出必要的更新。

下面,先来讨论一下插入操作的情况,插入操作可能发生在节点的左子树或者右子树,那么可以分为以下几种情况:

  • 插入前节点的左右子树高度一样,那么无论插入到左子树还是右子树,该节点本身仍然是满足AVL的条件的,但是需要继续往上查看节点的父节点,因为节点本身的高度增加了1
  • 插入前节点的左右子树高度相差1,那么当新节点被插入到先前矮一点的那个子树,那么节点的整体高度是不变的,因此,树仍然满足AVL的条件
  • 插入前节点的左右子树高度相差1,那么当新节点被插入到先前高一点的那个子树,那么节点的左右子树高度差就会大于1,此时就需要调整了,而调整的情况分为以下四种:
    1. 插入左孩子的左子树
    2. 插入左孩子的右子树
    3. 插入右孩子的左子树
    4. 插入右孩子的右子树

其中1和4是对称的,可以称之为新节点在外侧插入,2和3也是对称的可以称为新节点在内测插入。外侧插入要想调整平衡比较简单,只需要一次单旋转。而内侧插入则要麻烦一些需要一个双旋转。

我们先分析一下第一种情况,插入节点后新树如下所示:

  

上图里的K2是插入新节点到X子树中后,第一个出现不平衡的父节点,我们需要旋转它的左孩子K1使整棵树重新平衡,如下图所示。仔细分析一下,插入新节点之前,K1的左右子树的高度必定是一样的,因为如果K1的左子树比右子树高1,那么新节点插入X后,K1将是第一个出现不平衡的父节点而不是K2;而如果K1的左子树比右子树矮,那么插入新节点后,K1的左右子树高度将相同,那么K1还是平衡的而且高度不变,因此K2也肯定仍然是平衡的;因此只剩下一个可能那就是K1之前的左右子树高度是一样的,那么如此我们可以计算出如上图所示的,插入新节点之后各个节点的子树的高度。Z子树的高度必定是和Y子树高度一样,否则在插入任何新节点之前,K2节点就已经不满足平衡条件了。于是,我们选择K1节点,变换成如下图所示的形状后,树的整体高度和插入新节点之前是一样的,因此不需要再继续对原K2的父亲节点进行调整了。这种旋转,称为左旋转。


第四种情况和上面的是对称的,分析基本相似,就不多说了,一会看代码。

再来分析一下第二种情况,插入节点后的新树如下图所示:


上图中,由于新节点是插入到K3的左孩子的右子树中,因此K2整个子树是非空的。如果K2的左右子树不为空,那么新节点可以在左子树或者右子树,这两种情况没有区别,但是在新节点插入K2的子树之前,K2的左右子树的高度必定是相等的,因为如果不等,那么新节点插入到矮一点的子树后,K2的整体高度是不会变的,那么K3和K1也仍然是平衡的;而如果插入到高一点的子树一侧,那么K2就会是第一个不平衡的节点,而不是K3。因此,K2的左右子树必定是等高的,插入新节点后,其中一个的高度增加了1,但是K2子树本身仍然是平衡的,而因为K1在插入新节点后仍然平衡,我们可知K1的左右子树在插入新节点之前必定也是等高的,否则K1和K3要么在插入新节点后仍然保持平衡或者K1成为第一个不平衡的节点。因此我们可以计算出上图中4颗子树的高度。

经过第一次旋转,变成如下所示,仍然不平衡:


需要再经过一次旋转,变成如下所示,才可以恢复平衡:


经过两次旋转后,新树的高度仍然是h+2,因此不需要再继续对K2的父节点进行调整了。

下面是左孩子单旋转,右孩子单旋转,左孩子双旋转,右孩子双旋转的代码片段:

        void RotateWithLeftChild(AVLNode*& subroot)
	{
		if( subroot == NULL || subroot->left == NULL )
			throw logic_error("Subroot or its left child is NULL");

		AVLNode* leftChild = subroot->left;
		subroot->left = leftChild->right;
		leftChild->right = subroot;
		subroot->height = max ( Height(subroot->left), Height(subroot->right) ) + 1;
		leftChild->height = max ( Height(leftChild->left) , Height(subroot) ) + 1;
		subroot = leftChild;
	}

	void RotateWithRightChild(AVLNode*& subroot)
	{
		if( subroot == NULL || subroot->right == NULL )
			throw logic_error("Subroot or its right child is NULL");

		AVLNode* rightChild = subroot->right;
		subroot->right = rightChild->left;
		rightChild->left = subroot;
		subroot->height = max ( Height(subroot->left), Height(subroot->right) ) + 1;
		rightChild->height = max( Height(subroot),Height(rightChild->right) ) + 1;
		subroot = rightChild;
	}

	void DoubleRotateWithLeftChild(AVLNode*& subroot)
	{
		RotateWithRightChild(subroot->left);
		RotateWithLeftChild(subroot);
	}

	void DoubleRotateWithRightChild(AVLNode*& subroot)
	{
		RotateWithLeftChild(subroot->right);
		RotateWithRightChild(subroot);
	}
下面是基于上面四个旋转操作产生的插入函数代码:

        void Insert(const T& v, AVLNode*& subroot)
	{
		if( subroot == NULL )
		{
			subroot = new AVLNode(v,NULL,NULL,0);
		}
		else if( v < subroot->key )
		{
			Insert(v,subroot->left);
			// Height of left child minus height of right child must be one of 0,1,2
			// Can't be any other value because of subroot must be a AVL tree before new node inserted
			if( Height(subroot->left) - Height(subroot->right) == 2 )
			{
				// In this case, subroot->left must exist and it is not the newly inserted node
				if( v < subroot->left->key )
				{
					// insert new node to the left subtree of left child
					RotateWithLeftChild(subroot);
				}
				else
				{
					// insert new node to the right subtree of left child
					DoubleRotateWithLeftChild(subroot);
				}
			}
		}
		else if( subroot->key < v)
		{
			Insert(v,subroot->right);
			if( Height(subroot->right) - Height(subroot->left) == 2 )
			{
				if( subroot->right->key < v)
				{
					RotateWithRightChild(subroot);
				}
				else
				{
					DoubleRotateWithRightChild(subroot);
				}
			}
		}
		else
		{
			throw logic_error("Duplicate key");
		}

		subroot->height = max( Height(subroot->left), Height(subroot->right) ) + 1;
	}

删除操作和插入操作类似,因为删除操作会导致子树的高度减小,所以也可能出现不平衡的情况,从而需要作出调整。除此之外,和普通的二叉查找树的删除操作一样,被删除的节点可能含有两个子节点,一个子节点或者无子节点,对于后两种情况,删除节点本身很简单,直接将它的子节点连接到它的父节点就可以了;而对于两个子节点的情况,就要复杂一些,我们可以用二叉查找树的删除操作一样的思路,找出这个节点的中序遍历的前驱节点,然后交换他们的内容,之后从节点的左子树中删除那个前驱节点。

另外,当删除了节点之后,节点的左右子树高度可能变化,下面是可能出现变化的组合(假设删除的节点在根节点的左侧,此处的根节点是指从删除节点往上第一个出现不平衡的父节点,右侧的情况是完全对称的):

  1. 删除前,左右子树高度一样,删除后,左子树高度减1,树还是平衡的,不需要调整,树的整体高度不变,对更上层的父节点无影响
  2. 删除前,左子树比右子树高1层,删除后,左右子树高度相同,树还是平衡的,不需要掉正,但是树的整体高度减小了1层,对更上层的父节点有影响
  3. 删除前,左子树比右子树矮1层,删除后,左子树比右子树高度矮2层,树不平衡了,需要调整,那么此时又可以分为以下三种情况:

a. 右孩子的左右子树本身的高度相同,此时,简单的以右孩子为中心旋转就可以恢复平衡,原树的整体高度不变

b. 右孩子的左子树比右子树高度小1层,此时,简单的以右孩子为中心旋转就可以恢复平衡,原树的整体高度减小1

c. 右孩子的左子树比右子树高度大1层,此时,情况稍微复杂点,需要先以右孩子的左孩子为中心旋转,之后再以右孩子为中心旋转,才可以恢复平衡,原树的整体高度减小1


下面是删除有关的代码:

	void RemoveRotateWhenRightHigher(AVLNode*& subroot)
	{
		// subroot's right child must exist in this case
		if(subroot->right == NULL )
			throw logic_error("Right child is NULL");

		AVLNode* rightChild = subroot->right;
		int rightChildHeightDiff = Height(rightChild->left) - Height(rightChild->right);
		switch(rightChildHeightDiff)
		{
			case -1:
				RotateWithRightChild(subroot);
				break;
			case 0:
				RotateWithRightChild(subroot);
				break;
			case 1:
				// rightChild's left child shouldn't be NULL in this case
				if( rightChild->left == NULL )
					throw logic_error("RightChild's left child is NULL");

				RotateWithLeftChild(subroot->right);
				RotateWithRightChild(subroot);
				break;
			default:
				throw logic_error("RightChild subtree is not an AVL tree");
		}
	}

	void RemoveRotateWhenLeftHigher(AVLNode*& subroot)
	{
		// subroot's left child must exist in this case
		if( subroot->left == NULL )
			throw logic_error("Left child is NULL");

		AVLNode* leftChild = subroot->left;
		int leftChildHeightDiff = Height(leftChild->left) - Height(leftChild->right);
		switch(leftChildHeightDiff)
		{
		case -1:
			// leftChild's right child shouldn't be NULL in this case
			if( leftChild->right == NULL )
				throw logic_error("LeftChild's right child is NULL");

			RotateWithRightChild(subroot->left);
			RotateWithLeftChild(subroot);
			break;
		case 0:
			RotateWithLeftChild(subroot);
			break;
		case 1:
			RotateWithLeftChild(subroot);
			break;
		default:
			throw logic_error("LeftChild subtree is not an AVL tree");
		}
	}

	void Remove(const T& v, AVLNode* & subroot)
	{
		if( subroot == NULL )
		{
			throw logic_error("Can't find target key");
		}
		else
		{
			if( subroot->key < v)
			{
				// delete in right subtree
				Remove(v,subroot->right);
				if( Height(subroot->left) - Height(subroot->right) == 2)
				{
					RemoveRotateWhenLeftHigher(subroot);
				}
			}
			else if( v < subroot->key )
			{
				// delete in left subtree
				Remove(v,subroot->left);
				if( Height(subroot->right) - Height(subroot->left) == 2)
				{
					RemoveRotateWhenRightHigher(subroot);
				}
			}
			else
			{
				// find the target node
				if( subroot->left == NULL )
				{
					// only have right child or no child
					AVLNode* temp = subroot;
					subroot = subroot->right;
					delete temp;
					return; // must return now, no need to update new subroot's height, it may not exist
				}
				else if( subroot->right == NULL )
				{
					// only have left child
					AVLNode* temp = subroot;
					subroot = subroot->left;
					delete temp;
					return; // must return now, no need to update new subroot's height, it may not exist
				}
				else
				{
					// have both right and left child
					// find the max value in its left subtree
					// exchange the value with current node
					// then delete the new target in current left subtree
					AVLNode* maxLeftChild = FindMax(subroot->left);
					T newTarget = maxLeftChild->key;
					subroot->key = newTarget;

					Remove(newTarget,subroot->left);
					if( Height(subroot->right) - Height(subroot->left) == 2)
					{
						RemoveRotateWhenRightHigher(subroot);
					}
				}
			}

			// update current node's height
			// so we can update all its parent and make necessary adjustment
			subroot->height = max( Height(subroot->left), Height(subroot->right) ) + 1;
		}
	}

下面是完整的代码:
#ifndef _AVLBINARYSEARCHTREE_
#define _AVLBINARYSEARCHTREE_

#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <stack>
using namespace std;

template <typename T>
class AVLBinarySearchTree
{
private:
	struct AVLNode
	{
		T key;
		AVLNode* left;
		AVLNode* right;
		int height;
		AVLNode(const T& v,AVLNode* l = NULL,AVLNode* r = NULL, int h = 0) : key(v),left(l),right(r),height(h) {}
	};

public:
	AVLBinarySearchTree() : root(NULL) {}
	~AVLBinarySearchTree() { Clear(); }
	AVLBinarySearchTree(const AVLBinarySearchTree& rhs) : root(NULL) { operator=(rhs); }
	const AVLBinarySearchTree& operator= (const AVLBinarySearchTree& rhs)
	{
		if( this != &rhs )
		{
			this->Clear();
			this->Copy(root,rhs.root);
		}
		return *this;
	}

	bool IsEmpty() const
	{
		return root == NULL;
	}

	void PrintTree() const
	{
		PrintTree(root);
	}

	void PreOrderTraverseNonRecursive()
	{
		if(root != NULL)
		{
			AVLNode* cur = root;
			stack<AVLNode*> rights;

			while( cur != NULL || !rights.empty() )
			{
				if( cur != NULL )
				{
					PrintNode(cur);
					if( cur->right != NULL )
						rights.push(cur->right);
					cur = cur->left;
				}
				else
				{
					cur = rights.top();
					rights.pop();
				}
			}
		}
	}

	void PrintNode(AVLNode* node)
	{
		if(node != NULL)
		{
			cout << "[" << node->key << "," << node->height << "], ";
		}
		else
		{
			cout << "[], ";
		}
	}

	const T& FindMin() const
	{
		if(IsEmpty())
			throw logic_error("Tree is empty");

		AVLNode* targetNode = FindMin(root);
		return targetNode->key;
	}

	const T& FindMax() const
	{
		if(IsEmpty())
			throw logic_error("Tree is empty");

		AVLNode* targetNode = FindMax(root);
		return targetNode->key;
	}

	bool Contains( const T& v) const
	{
		return Contains(root,v);
	}

	void Clear()
	{
		Clear(root);
		root= NULL;
	}

	void Insert(const T& v)
	{
		Insert(v,root);
	}

	void Remove(const T& v)
	{
		Remove(v,root);
	}

	int Height() const
	{
		if( root == NULL )
			return -1;
		else
			return root->height;
	}

private:
	void RotateWithLeftChild(AVLNode*& subroot)
	{
		if( subroot == NULL || subroot->left == NULL )
			throw logic_error("Subroot or its left child is NULL");

		AVLNode* leftChild = subroot->left;
		subroot->left = leftChild->right;
		leftChild->right = subroot;
		subroot->height = max ( Height(subroot->left), Height(subroot->right) ) + 1;
		leftChild->height = max ( Height(leftChild->left) , Height(subroot) ) + 1;
		subroot = leftChild;
	}

	void RotateWithRightChild(AVLNode*& subroot)
	{
		if( subroot == NULL || subroot->right == NULL )
			throw logic_error("Subroot or its right child is NULL");

		AVLNode* rightChild = subroot->right;
		subroot->right = rightChild->left;
		rightChild->left = subroot;
		subroot->height = max ( Height(subroot->left), Height(subroot->right) ) + 1;
		rightChild->height = max( Height(subroot),Height(rightChild->right) ) + 1;
		subroot = rightChild;
	}

	void DoubleRotateWithLeftChild(AVLNode*& subroot)
	{
		RotateWithRightChild(subroot->left);
		RotateWithLeftChild(subroot);
	}

	void DoubleRotateWithRightChild(AVLNode*& subroot)
	{
		RotateWithLeftChild(subroot->right);
		RotateWithRightChild(subroot);
	}

	void RemoveRotateWhenRightHigher(AVLNode*& subroot)
	{
		// subroot's right child must exist in this case
		if(subroot->right == NULL )
			throw logic_error("Right child is NULL");

		AVLNode* rightChild = subroot->right;
		int rightChildHeightDiff = Height(rightChild->left) - Height(rightChild->right);
		switch(rightChildHeightDiff)
		{
			case -1:
				RotateWithRightChild(subroot);
				break;
			case 0:
				RotateWithRightChild(subroot);
				break;
			case 1:
				// rightChild's left child shouldn't be NULL in this case
				if( rightChild->left == NULL )
					throw logic_error("RightChild's left child is NULL");

				RotateWithLeftChild(subroot->right);
				RotateWithRightChild(subroot);
				break;
			default:
				throw logic_error("RightChild subtree is not an AVL tree");
		}
	}

	void RemoveRotateWhenLeftHigher(AVLNode*& subroot)
	{
		// subroot's left child must exist in this case
		if( subroot->left == NULL )
			throw logic_error("Left child is NULL");

		AVLNode* leftChild = subroot->left;
		int leftChildHeightDiff = Height(leftChild->left) - Height(leftChild->right);
		switch(leftChildHeightDiff)
		{
		case -1:
			// leftChild's right child shouldn't be NULL in this case
			if( leftChild->right == NULL )
				throw logic_error("LeftChild's right child is NULL");

			RotateWithRightChild(subroot->left);
			RotateWithLeftChild(subroot);
			break;
		case 0:
			RotateWithLeftChild(subroot);
			break;
		case 1:
			RotateWithLeftChild(subroot);
			break;
		default:
			throw logic_error("LeftChild subtree is not an AVL tree");
		}
	}

	void Insert(const T& v, AVLNode*& subroot)
	{
		if( subroot == NULL )
		{
			subroot = new AVLNode(v,NULL,NULL,0);
		}
		else if( v < subroot->key )
		{
			Insert(v,subroot->left);
			// Height of left child minus height of right child must be one of 0,1,2
			// Can't be any other value because of subroot must be a AVL tree before new node inserted
			if( Height(subroot->left) - Height(subroot->right) == 2 )
			{
				// In this case, subroot->left must exist and it is not the newly inserted node
				if( v < subroot->left->key )
				{
					// insert new node to the left subtree of left child
					RotateWithLeftChild(subroot);
				}
				else
				{
					// insert new node to the right subtree of left child
					DoubleRotateWithLeftChild(subroot);
				}
			}
		}
		else if( subroot->key < v)
		{
			Insert(v,subroot->right);
			if( Height(subroot->right) - Height(subroot->left) == 2 )
			{
				if( subroot->right->key < v)
				{
					RotateWithRightChild(subroot);
				}
				else
				{
					DoubleRotateWithRightChild(subroot);
				}
			}
		}
		else
		{
			throw logic_error("Duplicate key");
		}

		subroot->height = max( Height(subroot->left), Height(subroot->right) ) + 1;
	}

	void Remove(const T& v, AVLNode* & subroot)
	{
		if( subroot == NULL )
		{
			throw logic_error("Can't find target key");
		}
		else
		{
			if( subroot->key < v)
			{
				// delete in right subtree
				Remove(v,subroot->right);
				if( Height(subroot->left) - Height(subroot->right) == 2)
				{
					RemoveRotateWhenLeftHigher(subroot);
				}
			}
			else if( v < subroot->key )
			{
				// delete in left subtree
				Remove(v,subroot->left);
				if( Height(subroot->right) - Height(subroot->left) == 2)
				{
					RemoveRotateWhenRightHigher(subroot);
				}
			}
			else
			{
				// find the target node
				if( subroot->left == NULL )
				{
					// only have right child or no child
					AVLNode* temp = subroot;
					subroot = subroot->right;
					delete temp;
					return; // must return now, no need to update new subroot's height, it may not exist
				}
				else if( subroot->right == NULL )
				{
					// only have left child
					AVLNode* temp = subroot;
					subroot = subroot->left;
					delete temp;
					return; // must return now, no need to update new subroot's height, it may not exist
				}
				else
				{
					// have both right and left child
					// find the max value in its left subtree
					// exchange the value with current node
					// then delete the new target in current left subtree
					AVLNode* maxLeftChild = FindMax(subroot->left);
					T newTarget = maxLeftChild->key;
					subroot->key = newTarget;

					Remove(newTarget,subroot->left);
					if( Height(subroot->right) - Height(subroot->left) == 2)
					{
						RemoveRotateWhenRightHigher(subroot);
					}
				}
			}

			// update current node's height
			// so we can update all its parent and make necessary adjustment
			subroot->height = max( Height(subroot->left), Height(subroot->right) ) + 1;
		}
	}

	int Height(AVLNode* subroot) const
	{
		return subroot == NULL ? -1 : subroot->height;
	}

	void Copy(AVLNode* &subroot,AVLNode* source)
	{
		if(source != NULL)
		{
			subroot = new AVLNode(source->key,NULL,NULL,source->height);
			Copy(subroot->left,source->left);
			Copy(subroot->right,source->right);
		}
	}

	void Clear(AVLNode* subroot)
	{
		if(subroot != NULL )
		{
			Clear(subroot->left);
			Clear(subroot->right);
			delete subroot;
		}
	}

	void PrintTree(AVLNode* subroot) const
	{
		if( subroot != NULL )
		{
			PrintTree(subroot->left);
			cout << subroot->key << ",";
			PrintTree(subroot->right);
		}
	}

	AVLNode* FindMin(AVLNode* subroot) const
	{
		if( subroot == NULL )
			return NULL;

		while( subroot->left != NULL)
			subroot = subroot->left;

		return subroot;
	}

	AVLNode* FindMax(AVLNode* subroot) const
	{
		if( subroot == NULL )
			return NULL;

		while( subroot->right != NULL )
			subroot = subroot->right;

		return subroot;
	}

	bool Contains(AVLNode* subroot,const T& v) const
	{
		if( subroot == NULL )
			return false;
		else if ( v < subroot->key )
			return Contains(subroot->left,v);
		else if ( subroot->key < v)
			return Contains(subroot->right,v);
		else
			return true;
	}

private:
	AVLNode* root;
};

#endif


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AVL树是一种自平衡二叉搜索树,可以用来实现字典类型。在实现字典类型时,我们可以将键值对存储在AVL树的节点中,其中键作为节点的关键字,值则作为节点的附加数据。 下面是一个简单的AVL树实现字典类型的Python代码: ```python class AVLNode: def __init__(self, key, val): self.key = key self.val = val self.left = None self.right = None self.height = 1 class AVLTree: def __init__(self): self.root = None def insert(self, key, val): self.root = self._insert(self.root, key, val) def _insert(self, node, key, val): if not node: return AVLNode(key, val) if key < node.key: node.left = self._insert(node.left, key, val) elif key > node.key: node.right = self._insert(node.right, key, val) else: node.val = val node.height = 1 + max(self._height(node.left), self._height(node.right)) balance = self._get_balance(node) if balance > 1 and key < node.left.key: return self._right_rotate(node) if balance < -1 and key > node.right.key: return self._left_rotate(node) if balance > 1 and key > node.left.key: node.left = self._left_rotate(node.left) return self._right_rotate(node) if balance < -1 and key < node.right.key: node.right = self._right_rotate(node.right) return self._left_rotate(node) return node def search(self, key): node = self._search(self.root, key) if node: return node.val else: return None def _search(self, node, key): if not node: return None if key == node.key: return node if key < node.key: return self._search(node.left, key) else: return self._search(node.right, key) def _height(self, node): if not node: return 0 return node.height def _get_balance(self, node): if not node: return 0 return self._height(node.left) - self._height(node.right) def _left_rotate(self, node): new_root = node.right node.right = new_root.left new_root.left = node node.height = 1 + max(self._height(node.left), self._height(node.right)) new_root.height = 1 + max(self._height(new_root.left), self._height(new_root.right)) return new_root def _right_rotate(self, node): new_root = node.left node.left = new_root.right new_root.right = node node.height = 1 + max(self._height(node.left), self._height(node.right)) new_root.height = 1 + max(self._height(new_root.left), self._height(new_root.right)) return new_root ``` 在这个实现中,我们定义了AVLNode类来表示AVL树的节点。每个节点包含一个键、一个值、左右子树指针以及节点高度。AVLTree类是AVL树实现,包含了插入、搜索、左旋和右旋等基本操作。 在insert操作中,我们首先按照二叉搜索树的规则找到要插入的位置。然后更新节点高度,并计算平衡因子。如果平衡因子超过了1或-1,我们就需要进行旋转来保持AVL树的平衡。 在search操作中,我们按照二叉搜索树的规则搜索键值对应的节点,并返回其值。 这个AVL树实现可以用来实现字典类型。我们可以将键值对存储在AVL树的节点中,并通过搜索操作来查找键对应的值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值