数据结构->二叉搜索树->平衡二叉搜索树->红黑树的C++实现

节点的定义:

RBTreeNode.h

//
// Created by 24588 on 2022/1/12.
//

#ifndef TEST_RBTREENODE_H
#define TEST_RBTREENODE_H

enum RBColor
{
    RED,BLACK
};

template <typename E>//E为可以比较的类型,< > == !=  =都要能用,如果传的是字典节点类型,那么就要重载运算符
struct RBTreeNode
{
    RBTreeNode* left;
    RBTreeNode* right;
    RBTreeNode* parent;
    E element;//可以比较的元素
    RBColor col;

    RBTreeNode(E e, RBColor c, RBTreeNode* l, RBTreeNode* r, RBTreeNode* p) :
        element(e),col(c),left(l),right(r),parent(p){}
};

#endif //TEST_RBTREENODE_H

红黑树的定义:

RBTree.h

//
// Created by 24588 on 2022/1/12.
//

#ifndef TEST_RBTREE_H
#define TEST_RBTREE_H

#include <iostream>
#include "RBTreeNode.h"

using namespace std;

//RB1:根节点和所有外部节点都是黑色的
//RB2:在根至外部节点路径上,没有连续两个节点是红色
//RB3:在根至外部节点路径上,黑色节点的数目都相同
template <typename T>
class RBTree
{
   public:
      //构造方法与析构方法
      RBTree() : m_root(nullptr){}
      ~RBTree() {clear();}
      //ADT
      bool isEmpty() const
      {
          return nullptr == m_root;
      }
      //外部接口
      void preOrder()
      {
          preOrder(m_root);
      }
      void inOrder()
      {
          inOrder(m_root);
      }
      void postOrder()
      {
          postOrder(m_root);
      }
      void search(const T& element)
      {
          RBTreeNode<T>* target = search(element, m_root);
          if (target == nullptr) cout << "not found" << endl;
          else cout << target->element << endl;
      }
      const T& findMin() const
      {
          return findMin(m_root)->element;
      }
      const T& findMax() const
      {
          return findMax(m_root)->element;
      }
      void clear()
      {
          clear(m_root);
      }
      void insert(const T &x)
      {
          insert(x, m_root);
      }
      void erase(const T &x)
      {
          RBTreeNode<T>* node = search(x, m_root);//现在root中找x,返回x的指针
          if (node == nullptr) return;//不存在要删除的节点
          erase(node, m_root);
      }
      void predecessor(const T& element)
      {
          RBTreeNode<T>* target = search(element, m_root);
          if (target == nullptr)
          {
              cout << "not found" << endl;
              return;
          }
          else
          {
              cout << predecessor(target)->element << endl;
          }
      }
      void successor(const T& element)
      {
          RBTreeNode<T>* target = search(element, m_root);
          if (target == nullptr)
          {
              cout << "not found" << endl;
              return;
          }
          else
          {
              cout << successor(target)->element << endl;
          }
      }
   private://私有成员变量
      RBTreeNode<T> *m_root;//RBTree的根节点
   private://私有成员方法
      RBTreeNode<T>* predecessor(RBTreeNode<T>* x);//找节点的前驱节点,值小于x中最大的那个
      RBTreeNode<T>* successor(RBTreeNode<T>* x);//找节点的后驱节点,值大于x中最小的那个
      //内部接口
      void preOrder(RBTreeNode<T>* t, ostream& out = cout) const;
      void inOrder(RBTreeNode<T>* t, ostream& out = cout) const;
      void postOrder(RBTreeNode<T>* t, ostream& out = cout) const;

      RBTreeNode<T>* search(const T& element,RBTreeNode<T>* root);

      RBTreeNode<T>* findMin(RBTreeNode<T> *t) const;
      RBTreeNode<T>* findMax(RBTreeNode<T> *t) const;

      void clear(RBTreeNode<T>* &t);

      void insert(const T &element, RBTreeNode<T>* &root);
      void erase(RBTreeNode<T>* node, RBTreeNode<T>* &root);
      //服务与insert与erase来保证红黑树平衡的方法
      void rotateRight(RBTreeNode<T> *gu, RBTreeNode<T> *&root);
      void rotateLeft(RBTreeNode<T> *gu, RBTreeNode<T> *&root);
      void insertBlance(RBTreeNode<T> *node, RBTreeNode<T> *&root);
      void eraseBlance(RBTreeNode<T> *cnode, RBTreeNode<T> *&root);

};

template <typename T>
void RBTree<T>::preOrder(RBTreeNode<T> *t, ostream &out) const
{
    if (nullptr != t)
    {
        //out << t->element << ":" << t->col<<" ";//我用来看颜色画二叉树检验是不是红黑树的
        out << t->element <<" ";
        preOrder(t->left, out);
        preOrder(t->right, out);
    }
}

template <typename T>
void RBTree<T>::inOrder(RBTreeNode<T> *t, ostream &out) const
{
    if (nullptr != t)
    {
        inOrder(t->left, out);
        out << t->element << " ";
        inOrder(t->right, out);
    }
}

template <typename T>
void RBTree<T>::postOrder(RBTreeNode<T> *t, ostream &out) const
{
    if (nullptr != t)
    {
        postOrder(t->left, out);
        postOrder(t->right, out);
        out << t->element << " ";
    }
}

template <typename T>
RBTreeNode<T>* RBTree<T>::search(const T &element, RBTreeNode<T>* root)
{
    while(root != nullptr && root->element != element)
    {
        root = element < root->element ? root->left : root->right;
    }
    return root;//找到范围节点的指针,没找到返回nullptr
}

template <typename T>
void RBTree<T>::clear(RBTreeNode<T> *&t)
{
    if (t != nullptr)
    {
        clear(t->left);
        clear(t->right);
        delete t;
    }
    t = nullptr;
}

template <typename T>
RBTreeNode<T>* RBTree<T>::findMin(RBTreeNode<T> *t) const
{
    if (t != nullptr)
        while (t->left != nullptr)
            t = t->left;
    return t;
}

template <typename T>
RBTreeNode<T>* RBTree<T>::findMax(RBTreeNode<T> *t) const
{
    if (t != nullptr)
        while (t->right != nullptr)
            t = t->right;
    return t;
}

template <typename T>
RBTreeNode<T>* RBTree<T>::predecessor(RBTreeNode<T> *x)
{
    if (x->left != nullptr)
        return findMax(x->left);
    RBTreeNode<T>* y = x->parent;
    while (y != nullptr && y->left == x)
    {
        x = y;
        y = y->parent;
    }
    return y;
}

template <typename T>
RBTreeNode<T>* RBTree<T>::successor(RBTreeNode<T> *x)//大于x的最小值
{
    //1.若一个节点有右子树,那么该节点的后继节点是其右子树中值最小的节点
    if (x->right != nullptr)//如果有右子树,就在这里找
        return findMin(x->right);//右子树的都大于x,所以在右子树去找最小值
    //2.1 若该节点是其父节点的左孩子,那么该节点的后继结点即为其父节点
    //2.2 若该节点是其父节点的右孩子,那么需要沿着其父亲节点一直向树的顶端寻找,直到找到一个节点P,P节点是其父节点Q的左边孩子
    RBTreeNode<T>* target = x->parent;//没有右子树只能向上找有右子树的父节点
    while (target != nullptr && target->right == x)//x为右儿子,就一直往上,直到变成左孩子或者子节点指向根节点,退出
    {
        x = target;
        target = target->parent;
    }
    return target;//如果没有进入循环,说明x作为左子树,比他大的最小值就是父亲
}

template <typename T>
void RBTree<T>::rotateRight(RBTreeNode<T> *gu, RBTreeNode<T> *&root)//针对LL型
{
    //在AVL树的旋转方法基础上又加上了对父亲节点的修改
    RBTreeNode<T>* fu = gu->left;//gu是祖父,fu是父亲
    //步骤一:
    gu->left = fu->right;//首先让gu的左指向fu的右
    if (fu->right != nullptr)//先看fu的右是否存在
        fu->right->parent = gu;//存在的话,把父亲改为gu,和上一步对应
    //步骤二:将fu与gu的父亲联系起来
    fu->parent = gu->parent;//fu的父亲改为gu的父亲,即fu变成新的祖父,顶替掉他的位置
    if (gu->parent == nullptr)//当然如果gu的父亲本来是空的,即gu是根节点
        root = fu;//fu就成为新的根节点
    else//普通情况,gu不是根节点
    {
        //要去改gu的父节点的信息
        if (gu == gu->parent->left)//gu是左节点,那gu父亲的左变为fu
            gu->parent->left = fu;
        else
            gu->parent->right = fu;//gu是右节点,那gu父亲的右变为fu
    }
    //步骤三:
    fu->right = gu;//fu的右变为gu
    gu->parent = fu;//gu的父亲变为fu
}

template <typename T>
void RBTree<T>::rotateLeft(RBTreeNode<T> *gu, RBTreeNode<T> *&root)//针对RR型
{
    //同rotateRight
    RBTreeNode<T>* fu = gu->right;
    gu->right = fu->left;
    if (fu->left != nullptr)
        fu->left->parent = gu;
    fu->parent = gu->parent;
    if (gu->parent == nullptr)
        root = fu;
    else
    {
        if (gu == gu->parent->left)
            gu->parent->left = fu;
        else
            gu->parent->right = fu;
    }
    gu->parent = fu;
    fu->left = gu;
}


//插入方法的平衡:因为默认插入红色节点,所以如果父节点是红色时,才会不平衡。
//要去关注叔叔节点(父节点的兄弟节点)的颜色:
//如果是红色的,那简单,只用改色,父节点和叔叔节点从红改为黑,然后祖父就从黑变为红,类似新的红色节点,循环处理
//如果是黑色(空的也算黑),麻烦些,你插入红色后,如果只把父亲改为黑,祖父改为红,会违背RB3,要利用旋转纠正,把父节点改为黑,祖父改为红后,旋转就成了
template <typename T>
void RBTree<T>::insertBlance(RBTreeNode<T> *node, RBTreeNode<T> *&root) {
    //默认插入的红色节点
    RBTreeNode<T> *parent = node->parent, *gparent = nullptr;//父节点与祖父节点
    while (parent != nullptr && parent->col == RED)//父亲是红色时才可能不平衡,当然前提是父亲要有
    {
        gparent = parent->parent;//node的祖父节点
        //分情况处理
        if (parent == gparent->left)//如果父亲是左节点,初步判断是L类型不平衡
        {
            RBTreeNode<T> *uncle = gparent->right;//父亲节点的兄弟 叔叔节点uncle
            if (uncle != nullptr && uncle->col == RED)//Lr类型,叔叔存在且是红色节点,只用改色就可以
            {
                //如果叔叔是红色,那爷爷肯定是黑色,,
                parent->col = BLACK;//父亲从红改为黑
                uncle->col = BLACK;//叔叔从红改为黑
                gparent->col = RED;//祖父从黑色变为红色
                node = gparent;//现在node指向gparent,往上去查,因为祖父颜色变了,可能会引发新的不平衡,如果祖父的父节点不是根节点
                continue;
            }
            else //Rb类型,没有叔叔节点或者有但是个黑色节点,这要讨论旋转
            {
                if (node == parent->right)//如果是LRb类型,就先左旋,变成LLb类型
                {
                    rotateLeft(parent, root);
                    //旋转后 node变成了父亲,parent变成了儿子,所以交换了一下
                    RBTreeNode<T> *tmp = parent;
                    parent = node;
                    node = tmp;
                }
                parent->col = BLACK;
                gparent->col = RED;
                rotateRight(gparent, root);//然后右旋
            }
        }
        else if (parent == gparent->right)//如果父亲是左节点,初步判断是R类型不平衡
        {
            RBTreeNode<T> *uncle = gparent->left;
            if (uncle != nullptr && uncle->col == RED)//Rr类型,只用改色
            {
                parent->col = BLACK;
                uncle->col = BLACK;
                gparent->col = RED;
                node = gparent;
                continue;
            }
            else //Rb类型,要旋转
            {
                if (node == parent->left)//如果是RLb类型,就先右旋,变成RRb类型
                {
                    rotateRight(parent, root);
                    //旋转后 node变成了父亲,parent变成了儿子
                    RBTreeNode<T> *tmp = parent;
                    parent = node;
                    node = tmp;
                }
                parent->col = BLACK;
                gparent->col = RED;
                rotateLeft(gparent, root);//然后左旋
            }
        }
    }
    root->col = BLACK;//while循环过完后,tree是根节点
}

template <typename T>
void RBTree<T>::insert(const T & element, RBTreeNode<T> *&root)
{
    RBTreeNode<T> *node = new RBTreeNode<T>(element, RED, nullptr, nullptr, nullptr);//用element构建的RB节点
    RBTreeNode<T> *cur = root;//cur用来遍历root
    RBTreeNode<T> *parent = nullptr;//newNode的父亲
    //将红黑树当做一颗二叉搜索树去插入节点
    if (root == nullptr) // 如果是个空树
    {
        root = node;
        node->col = BLACK;//作为根节点,颜色改为黑色
    }
    else //root不是空树
    {
        while (cur != nullptr)//cur最终到空指针,parent是记录上一次,即父亲
        {
            parent = cur;
            if (node->element < cur->element)
                cur = cur->left;
            else if (node->element > cur->element)
                cur = cur->right;
            else //if (node->element < cur->element)
                return;//有现存的
        }
        node->parent = parent;
        //现在找到了父亲,接下来判断插入在左边还是右边
        if (node->element < parent->element)
            parent->left = node;
        else
            parent->right = node;
        //插入后可能不平衡,调用insertBlance函数判断并使平衡
        insertBlance(node, root);
    }
}
//eraseBlance方法的目的:给定node,创造出删掉node就平衡的状况
//具体步骤 看兄弟什么颜色:
//2.1 兄弟是红色:进行旋转涂色,去到兄弟为黑色那里处理
//2.2 兄弟是黑色,看看兄弟子节点是不是全部都是黑。
//(1)全黑的话,看父什么颜色进行对应处理;
//(2)不全黑,看兄在的位置,兄在左的话,看兄的左子是不是红色,进行对应处理;兄在右的话,看兄的右子是不是红色,进行对应处理。
template <typename T>
void RBTree<T>::eraseBlance(RBTreeNode<T> *node, RBTreeNode<T> *&root)//针对删除的是黑色叶子节点的平衡算法,就他最复杂
{
    while(node != root && node->col == BLACK)
    {
        RBTreeNode<T> *parent = node->parent;
        if (node == parent->left)
        {
            RBTreeNode<T> *brother = parent->right;
            //先检查brother是红色的吗? 如果是,就:以parent进行左旋,然后交换parent和brother的颜色
            //这样就变成了兄弟节点时黑色的情况
            if (brother->col == RED)
            {
                parent->col = RED;
                brother->col = BLACK;
                rotateLeft(parent,root);
                //旋转后,node的brother变了
                brother = parent->right;
            }
            //讨论兄弟节点是黑色的情况
            //情形1 兄弟的子节点都是空或者都是红(兄弟节点不可能有黑色非空孩子,因为node是黑色叶子节点)
            if (brother->left == nullptr && brother->right == nullptr)
            {
                //情形1.1 父节点为红色 : 简单,直接修改brother的颜色,改为红,然后父亲改为黑,等会你再删掉node就平衡了
                if (parent->col == RED)
                {
                    brother->col = RED;
                    parent->col = BLACK;
                    break;
                }
                    //情形1.2 父节点为黑色 : 复杂了一些,brother改为红后,父亲开始的树平衡了,但总体的树就不平衡了,要往上处理新的不平衡直到根节点,然后删去node
                else //if (parent->col == BLACK)
                {
                    brother->col = RED;
                    node = parent;
                }
            }
                //情形2 兄弟的子节点一个红一个空
            else
            {
                //情形2.1 左黑右红 : 以parent左旋,parent和brother互换颜色,然后brother的右节点从红改为黑
                if (brother->left == nullptr || brother->left->col == BLACK)
                {
                    brother->col = parent->col;
                    parent->col = BLACK;
                    brother->right->col = BLACK;
                    rotateLeft(parent,root);
                    break;
                }
                    //情形2.2 左红右黑
                else //if (brother->right->col == BLACK)
                {
                    brother->left->col = BLACK;
                    brother->col = RED;
                    rotateRight(brother, root);
                    brother = parent->right;
                    //转换为了左黑右红类型,照抄左黑右红的代码
                    brother->col = parent->col;
                    parent->col = BLACK;
                    brother->right->col = BLACK;
                    rotateLeft(parent,root);
                    break;
                }
            }
        }
        else //if (node == parent->right)
        {
            RBTreeNode<T> *brother = parent->left;
            //先检查brother是红色的吗? 如果是,就:以parent进行右旋,然后交换parent和brother的颜色
            //这样就变成了兄弟节点是红色的情况
            if (brother->col == RED)
            {
                parent->col = RED;
                brother->col = BLACK;
                rotateRight(parent,root);
                //旋转后,node的brother变了
                brother = parent->left;
            }
            //讨论兄弟节点是黑色的情况
            //情形1 兄弟的子节点都是空或者都是红(兄弟节点不可能有黑色非空孩子,因为node是黑色叶子节点)
            if (brother->left == nullptr && brother->right == nullptr)
            {
                //情形1.1 父节点为红色 : 简单,直接修改brother的颜色,改为红,然后父亲改为黑,等会你再删掉node就平衡了
                if (parent->col == RED)
                {
                    brother->col = RED;
                    parent->col = BLACK;
                    break;
                }
                    //情形1.2 父节点为黑色 : 复杂了一些,brother改为红后,父亲开始的树平衡了,但总体的树就不平衡了,要往上处理新的不平衡直到根节点,然后删去node
                else //if (parent->col == BLACK)
                {
                    brother->col = RED;
                    node = parent;
                }
            }
                //情形2 兄弟的子节点一个红一个空
            else
            {
                //情形2.1 左黑右红 : 以parent左旋,parent和brother互换颜色,然后brother的右节点从红改为黑
                if (brother->left == nullptr || brother->left->col == BLACK)
                {
                    brother->col = parent->col;
                    parent->col = BLACK;
                    brother->right->col = BLACK;
                    rotateRight(parent,root);
                    break;
                }
                    //情形2.2 左红右黑
                else //if (brother->right == BLACK)
                {
                    brother->left->col = BLACK;
                    brother->col = RED;
                    rotateLeft(brother, root);
                    brother = parent->right;
                    //转换为了左黑右红类型,照抄左黑右红的代码
                    brother->col = parent->col;
                    parent->col = BLACK;
                    brother->right->col = BLACK;
                    rotateRight(parent,root);
                    break;
                }
            }
        }
    }
}

template <typename T>
void RBTree<T>::erase(RBTreeNode<T> * node, RBTreeNode<T> *&root)
{

    //分四种情况 左右都空,左有右空,左空右有,左有都有 只有删的是黑色节点才需要平衡
    //最终目的是找真正要删的rnode,它有两种情况,什么都没有,或有一棵树
    //node有两棵树时会转换目标为上面的情况,

    //node是要删除的节点,rnode是真正要删的,因为删掉node不一定真的干掉,而是把他的值换成别的,然后删去原来那个
    //cnode是rnode的顶替节点,其实就是孩子节点,把rnode删掉了,需要节点代替它的位置
    RBTreeNode<T>* rnode = nullptr;
    RBTreeNode<T>* cnode = nullptr;
    if (node->left == nullptr && node->right == nullptr)
    {
        //若node左右没有子树,rnode = node直接删除
        rnode = node;
        if (rnode->col == RED)//如果是红色,直接删
        {
            if (rnode == rnode->parent->left)
                rnode->parent->left = nullptr;
            else
                rnode->parent->right = nullptr;
            delete rnode;
        }
        else //if (rnode->col == BLACK) //被删节点是黑色
        {
            if (rnode->parent == nullptr)//删的是根节点,这也不用考虑平衡了
            {
                root = nullptr;
                delete rnode;
            }
            else//普通情况,被删节点是黑色而且无子节点,这个是最复杂的最难的
            {
                //RBTreeNode<T> * temp =
                eraseBlance(rnode,root);
                //平衡后还不是真的平衡,只是创造删掉rnode后才平衡的状况
                //切断关系 并删除
                if (rnode == rnode->parent->left)
                    rnode->parent->left = nullptr;
                else
                    rnode->parent->right = nullptr;
                delete rnode;
                //这才真的平衡了
            }
        }
    }
    else if (node->left != nullptr && node->right == nullptr)
    {
        //若node只有一个子树,让子节点cnode代替node后(),rnode = node,删掉node
        //这种情况下 node只能为黑,cnode为红
        rnode = node;
        cnode = node->left;
        //修改rnode父节点与cnode的关系
        cnode->parent = rnode->parent;
        if (cnode->parent == nullptr)
            root = cnode;
        else if (rnode == rnode->parent->left)
            rnode->parent->left = cnode;
        else //if (rnode == rnode->parent->right)
            rnode->parent->right = cnode;
        cnode->col = BLACK;//cnode从红改为黑
        delete rnode;
    }
    else if (node->left == nullptr && node->right != nullptr)
    {
        //若node只有一个子树,让子节点cnode代替node后(),rnode = node,删掉node
        //这种情况下 node只能为黑,cnode为红
        rnode = node;
        cnode = node->right;
        //修改rnode父节点与cnode的关系
        cnode->parent = rnode->parent;
        if (cnode->parent == nullptr)
            root = cnode;
        else if (rnode == rnode->parent->left)
            rnode->parent->left = cnode;
        else //if (rnode == rnode->parent->right)
            rnode->parent->right = cnode;
        cnode->col = BLACK;//cnode从红改为黑
        delete rnode;
    }
    else if (node->left != nullptr && node->right != nullptr)
    {
        //若左右两棵树都有,则去左子树找前驱节点或去右子树找后继结点,两个都可以
        //然后把这个节点的值赋给node,去删除这个节点
        //默认去左边找前驱节点,rnode那么就有两种情况 什么都没有,或有左子树(不会有右子树,前驱节点就是一直往右走)
        //情况就和上面类似了
        rnode = predecessor(node);//找前驱节点
        node->element = rnode->element;//node的值改为前驱节点的值,现在要把前驱节点删掉
        erase(rnode,root);//把rnode当成新的node,等待它的只有无子树和只有左子树情况
    }
}


//如果采用pair类型,重载的运算符
template <class K, class E>
ostream& operator<<(ostream& out, const pair<K,E> &x)
{
    out << "(" << x.first << "," << x.second << ")";return out;
}

template <class K, class E>
bool operator<(const pair<K,E> &x, const pair<K,E> &y)
{
    return x.first < y.first;
}

#endif //TEST_RBTREE_H

测试:

RBTree.cpp

//
// Created by 24588 on 2022/1/12.
//

#include <iostream>
#include "RBTree.h"
using namespace std;

int main()
{
    int arr[]= {10, 40, 30, 60, 90, 70, 20, 50, 80};
    int size = (sizeof(arr)) / (sizeof(arr[0])) ;
    auto* tree=new RBTree<int>();

    for(int i=0; i < size; i++)
    {
        tree->insert(arr[i]);
    }

    cout << "preOrder" ;tree->preOrder();cout << endl;
    cout << "inOrder" ;tree->inOrder();cout << endl;
    cout << "postOrder" ;tree->postOrder();cout << endl;

    cout << "max : " << tree->findMin() << endl;
    cout << "max : " << tree->findMax() << endl;
    cout << "successor :" ;tree->successor(100);
    cout << "successor :" ;tree->successor(50);
    cout << "predecessor :" ;tree->predecessor(100);
    cout << "predecessor :" ;tree->predecessor(50);

    for (int i = 0 ; i < size; i++)
    {
        cout << "erase " << arr[i] << ":\t";
        tree->erase(arr[i]);
        tree->inOrder();cout << endl;
    }

    return 0;
}

结果

preOrder30 10 20 60 40 50 80 70 90
inOrder10 20 30 40 50 60 70 80 90
postOrder20 10 50 40 70 90 80 60 30
max : 10
max : 90
successor :not found
successor :60
predecessor :not found
predecessor :40
erase 10:       20 30 40 50 60 70 80 90
erase 40:       20 30 50 60 70 80 90
erase 30:       20 50 60 70 80 90
erase 60:       20 50 70 80 90
erase 90:       20 50 70 80
erase 70:       20 50 80
erase 20:       50 80
erase 50:       80
erase 80:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值