数据结构之二叉搜索树和二叉平衡树学习笔记

二叉搜索树(Binary Search Tree)

具有下列性质的二叉树被称为二叉搜索树:

(1)、若它的左子树不为空,则左子树上所有结点的值均小于它的根结点的值;
(2)、若它的右子树不为空,则右子树上所有结点的值均大于它的根结点的值;
(3)、它的左、右子树也分别为二叉查找树。

一些典型特点:
- 而中序遍历二叉搜索树可得到一个关于key的有序序列,一个无序序列可以通过构造一棵二叉搜索树变成一个有序序列。

- 对二叉搜索树的搜索,插入,删除操作的次数最多不·超过等于树高,即操作的时间复杂度为O(log(n)).

搜索、插入、删除操作的算法思路:
- 搜索操作:

在搜索元素x的时候,我们可以将x和根节点比较,有以下三种情况:
(1) 如果x等于根节点,那么找到x,停止搜索 (终止条件);
(2) 如果x小于根节点,那么搜索左子树;
(3) 如果x大于根节点,那么搜索右子树;
  • 插入操作:
(1) 若二叉搜索树中存在该关键字,则不插入,所以插入的结点总是作为某一叶子节点的子结点;
(2) 先检索二叉搜索树,看查找树中是否含有该关键字,若不存在,记录应当要插入位置的父节点;
  • 删除:
在二叉搜索树中删除一个给定的结点p有三种情况:
(1)叶节点可以直接删除;
(2)结点p有左子树(右子树)之一,则把p的左子树(右子树)接到p的父节点上;
(3)左右子树同时存在,则找到结点p的中序直接后继结点s,把结点s的数据转移到结点p,然后删除结点s,由于结点s为p的右子树总最左的结点,因而s无左子树,所以直接把s的右子树接到s的父节点即可;

实现代码如下:

#include <iostream>
#include <cstdio>
using namespace std;
enum ResultCode { Underflow, Overflow, Success, Duplicate, NotPresent };

template<class T>
struct BTNode
{
    BTNode() { lChild = rChild = NULL; }
    BTNode(const T& x)
    {
        element = x;
        lChild = rChild = NULL;
    }
    BTNode(const T& x, BTNode<T>* l, BTNode<T>* r)
    {
        element = x;
        lChild = l;
        rChild = r;
    }
    T  element;
    BTNode<T>* lChild, *rChild;
};

template<class T>
class BST 
{
public:
    BST() { root = NULL; }
    bool IsEmpty()const;
    void Clear();
    bool Root();
    void MakeTree(const T& x, BST<T>& left, BST<T>& right);
    void BreakTree(T &x, BST<T>& left, BST<T>& right);
    int Size();
    void PreOrder(void(*Visit)(T& x));
    void InOrder(void(*Visit)(T& x));
    void PostOrder(void(*Visit)(T& x));
    void FreeTree();
    ResultCode Search(T &x)const;
    ResultCode Insert(T &x);
    ResultCode Remove(T& x);
protected:
    BTNode<T>* root;
private:
    ResultCode Search(BTNode<T> *p, T& x)const;
    void Clear(BTNode<T>* &t);
    void PreOrder(void(*Visit)(T& x), BTNode<T>*t);
    void InOrder(void(*Visit)(T& x), BTNode<T>*t);
    void PostOrder(void(*Visit)(T& x), BTNode<T>*t);
    void FreeTree(BTNode<T>* t);
    int Size(BTNode<T>* t);
};

template <class T>
bool BST<T>::Root()
{
    if (root)
    {
        cout << root->element << endl;
        return true;
    }
    else
        return false;
}

template <class T>
void BST<T>::MakeTree(const T& x, BST<T>& left, BST<T>& right)
{
    if (root || &left == &right)
        return;
    root = new BTNode<T>(x, left.root, right.root);
    left.root = right.root = NULL;
}

template <class T>
void BST<T>::BreakTree(T& x, BST<T>& left, BST<T>& right)
{
    if (!root || &left == &right || left.root || right.root)
        return;
    x = root->element;
    left.root = root->lChlid;
    right.root = root->rChild;
    delete root;
    root = NULL;
}

template <class T>
void Visit(T &x)
{
    cout << x << " ";
}

template <class T>
void BST<T>::PreOrder(void(*Visit)(T& x))
{
    PreOrder(Visit, root);
}

template <class T>
void BST<T>::PreOrder(void(*Visit)(T& x), BTNode<T>* t)
{
    if (t)
    {
        Visit(t->element);
        PreOrder(Visit, t->lChild);
        PreOrder(Visit, t->rChild);
    }
}

template <class T>
void BST<T>::InOrder(void(*Visit)(T& x))
{
    InOrder(Visit, root);
}

template <class T>
void BST<T>::InOrder(void(*Visit)(T& x), BTNode<T>* t)
{
    if (t)
    {
        InOrder(Visit, t->lChild);
        Visit(t->element);
        InOrder(Visit, t->rChild);
    }
}

template <class T>
void BST<T>::PostOrder(void(*Visit)(T& x))
{
    PostOrder(Visit, root);
}

template <class T>
void BST<T>::PostOrder(void(*Visit)(T& x), BTNode<T>* t)
{
    if (t)
    {
        PostOrder(Visit, t->lChild);
        PostOrder(Visit, t->rChild);
        Visit(t->element);
    }
}

template <class T>
int BST<T>::Size()
{
    return Size(root);
}

template <class T>
int BST<T>::Size(BTNode<T>* t)
{
    if (!t) return 0;
    else
        return Size(t->lChild) + Size(t->rChild) + 1;
}

template <class T>
void BST<T>::FreeTree()
{
    return FreeTree(root);
}

template <class T>
void BST<T>::FreeTree(BTNode<T>* t)
{
    if (t == NULL)
    {
        return;
    }
    if (t->lChild != NULL)
    {
        FreeTree(t->lChild);
        t->lChild = NULL;
    }
    if (t->rChild != NULL)
    {
        FreeTree(t->rChild);
        t->rChild = NULL;
    }
    if (t != NULL)
    {
        free(t);
        t = NULL;
    }
}

template<class T>
ResultCode BST<T>::Search(T &x)const
{
    return Search(root, x);
}
template<class T>
ResultCode BST<T>::Search(BTNode<T>* p, T& x)const
{
    if (!p)return NotPresent;
    else if (x<p->element) return Search(p->lChild, x);
    else if (x>p->element) return Search(p->rChild, x);
    else {
        x = p->element;
        return Success;
    }
}

template<class T>
ResultCode BST<T>::Insert(T& x)
{
    BTNode<T> *p = root, *q = NULL;
    while (p)
    {
        q = p;
        if (x<p->element) p = p->lChild;
        else if (x>p->element)p = p->rChild;
        else {
            x = p->element;
            return Duplicate;
        }
    }
    p = new BTNode<T>(x);
    if (!root) root = p;
    else if (x<q->element) q->lChild = p;
    else q->rChild = p;
    return Success;
}

template<class T>
ResultCode BST<T>::Remove(T &x)
{
    BTNode<T> *c, *s, *r, *p = root, *q = NULL;
    while (p && p->element != x)
    {
        q = p;
        if (x<q->element)p = p->lChild;
        else p =p ->rChild;
    }
    if (!p)return NotPresent;
    x = p->element;
    if (p->lChild&&p->rChild)
    {
        s = p->rChild;
        r = p;
        while (s->lChild)
        {
            r = s;
            s = s->lChild;
        }
        p->element = s->element;
        p = s;
        q = r;
    }
    if (p->lChild)c = p->lChild;
    else c = p->rChild;
    if (p == root)root = c;
    else if (p == q->lChild) q->lChild = c;
    else q->rChild = c;
    delete p;
    return Success;
}

int main()
{
    BST<int> a, b, o, p, q, w, x, y, z;
    int size;  
    /*下面代码建立一个形如:
        4
      /   \
     2     6
    / \   / \
   1   3 5    7
    的二叉树
*/
    y.MakeTree(1, a, b);
    z.MakeTree(3, a, b);
    x.MakeTree(2, y, z);
    p.MakeTree(5, a, b);
    q.MakeTree(7, a, b);
    w.MakeTree(6, p, q);
    o.MakeTree(4, x, w);
    o.Root();                   
    o.PreOrder(Visit);          
    cout << endl;
    o.InOrder(Visit);          //中序遍历当前二叉,答案应该为'1 2 3 4 5 6 7 ' 
    cout << endl;                 
    o.PostOrder(Visit);
    cout << endl;
    size = o.Size();
    cout << size << endl;
    int v1 = 5,v2 = 8,v3 = 4,v4=-1;

    if (o.Search(v1) == Success)  //搜索元素5是否在树中,结果应该为Success
        cout << "Success" << endl;
    else
        cout << "No" << endl;

    if (o.Search(v2) == Success)  //搜索元素8是否在树中,结果应该为No
        cout << "Success" << endl;
    else
        cout << "No" << endl;

    o.Remove(v1);            //删除元素5

    o.InOrder(Visit);        //删除后进行中序遍历,结果应该为1,2,3,4,6,7
    cout << endl;                 

    o.Remove(v3);           //再删除元素4,结果应该为1,2,3,6,7
    o.InOrder(Visit);       
    cout << endl;

    o.Insert(v1);           //插入元素5,结果应该为1,2,3,5,6,7
    o.InOrder(Visit);
    cout << endl;

    o.Insert(v3);           //插入元素4,结果应该为1,2,3,4,5,6,7
    o.InOrder(Visit);
    cout << endl;

    o.Insert(v4);           //插入元素-1,结果应该为-1,1,2,3,4,5,6,7
    o.InOrder(Visit);
    cout << endl;
    return 0;
}

实际运行结果如下图:

Alt text

运行环境VS2015

平衡二叉树(Balanced Binary Tree)

平衡二叉树又称为AVL树
它具有以下性质:

(1)是一棵二叉搜索树(每个节点是惟一出现);

(2)每个节点左右子树的高度之差(平衡因子)相差最多为1,平衡因子定义为:左子树高度 - 右子树高度;
(3)左右两个子树都是一棵平衡二叉树;


- 搜索算法同上的二叉搜索树。

但是注意和二叉查找树相比,查找方法没有变法,不过根据存储的特性,AVL树能维持在一个O(logN)的稳定的时间,而二叉查找树则相当不稳定。


此处资料学习并参考自:
http://www.cppblog.com/cxiaojia/archive/2012/08/20/187776.html
- 插入、删除的关键实现原理—失衡旋转:

平衡二叉树在进行插入和删除元素后,平衡性质可能要被打破,也称为失衡,所以要对树进行调整以保证平衡二叉树的平衡性质被保持住。解决的方法就是旋转。

旋转又分为单旋转和双旋转,针对不同的情况选择不同的旋转方案:

 对于一个平衡的节点,由于任意节点最多有两个儿子,因此高度不平衡时,此节点的两颗子树的高度差2.容易看出,这种不平衡出现在下面四种情况:

Alt text

 1、6节点的左子树3节点高度比右子树7节点大2,左子树3节点的左子树1节点高度大于右子树4节点,这种情况成为左左。

  2、6节点的左子树2节点高度比右子树7节点大2,左子树2节点的左子树1节点高度小于右子树4节点,这种情况成为左右。

  3、2节点的左子树1节点高度比右子树5节点小2,右子树5节点的左子树3节点高度大于右子树6节点,这种情况成为右左。

  4、2节点的左子树1节点高度比右子树4节点小2,右子树4节点的左子树3节点高度小于右子树6节点,这种情况成为右右。

  从图2中可以可以看出,1和4两种情况是对称的,这两种情况的旋转算法是一致的,只需要经过一次旋转就可以达到目标,我们称之为单旋转。2和3两种情况也是对称的,这两种情况的旋转算法也是一致的,需要进行两次旋转,我们称之为双旋转。
  • 单旋转
  单旋转是针对于左左和右右这两种情况的解决方案,这两种情况是对称的,只要解决了左左这种情况,右右就很好办了。图3是左左情况的解决方案,节点k2不满足平衡特性,因为它的左子树k1比右子树Z深2层,而且k1子树中,更深的一层的是k1的左子树X子树,所以属于左左情况。

Alt text

 为使树恢复平衡,我们把k2变成这棵树的根节点,因为k2大于k1,把k2置于k1的右子树上,而原本在k1右子树的Y大于k1,小于k2,就把Y置于k2的左子树上,这样既满足了二叉查找树的性质,又满足了平衡二叉树的性质。

  这样的操作只需要一部分指针改变,结果我们得到另外一颗二叉查找树,它是一棵AVL树,因为X向上一移动了一层,Y还停留在原来的层面上,Z向下移动了一层。整棵树的新高度和之前没有在左子树上插入的高度相同,插入操作使得X高度长高了。因此,由于这颗子树高度没有变化,所以通往根节点的路径就不需要继续旋转了。
  • 双旋转
  对于左右和右左这两种情况,单旋转不能使它达到一个平衡状态,要经过两次旋转。双旋转是针对于这两种情况的解决方案,同样的,这样两种情况也是对称的,只要解决了左右这种情况,右左就很好办了。图4是左右情况的解决方案,节点k3不满足平衡特性,因为它的左子树k1比右子树Z深2层,而且k1子树中,更深的一层的是k1的右子树k2子树,所以属于左右情况。

Alt text

为使树恢复平衡,我们需要进行两步,第一步,把k1作为根,进行一次右右旋转,旋转之后就变成了左左情况,所以第二步再进行一次左左旋转,最后得到了一棵以k2为根的平衡二叉树树。
  • 插入
 插入的方法和二叉查找树基本一样,区别是,插入完成后需要从插入的节点开始维护一个到根节点的路径,每经过一个节点都要维持树的平衡。维持树的平衡要根据高度差的特点选择不同的旋转算法。

插入代码如下:

//插入
template<class T>
void AVLTree<T>::insertpri(TreeNode<T>* &node,T x)
{
    if(node==NULL)//如果节点为空,就在此节点处加入x信息
    {
        node=new TreeNode<T>();
        node->data=x;
        return;
    }
    if(node->data>x)//如果x小于节点的值,就继续在节点的左子树中插入x
    {
        insertpri(node->lson,x);
        if(2==height(node->lson)-height(node->rson))
            if(x<node->lson->data)
                SingRotateLeft(node);
            else
                DoubleRotateLR(node);
    }
    else if(node->data<x)//如果x大于节点的值,就继续在节点的右子树中插入x
    {
        insertpri(node->rson,x);
        if(2==height(node->rson)-height(node->lson))//如果高度之差为2的话就失去了平衡,需要旋转
            if(x>node->rson->data)
                SingRotateRight(node);
            else
                DoubleRotateRL(node);
    }
    node->hgt=Max(height(node->lson),height(node->rson));
}
  • 删除
 删除的方法也和二叉查找树的一致,区别是,删除完成后,需要从删除节点的父亲开始向上维护树的平衡一直到根节点。

删除代码如下:

//删除
template<class T>
void AVLTree<T>::Deletepri(TreeNode<T>* &node,T x)
{
    if(node==NULL) return ;//没有找到值是x的节点
    if(x < node->data)
    {
         Deletepri(node->lson,x);//如果x小于节点的值,就继续在节点的左子树中删除x
         if(2==height(node->rson)-height(node->lson))
            if(node->rson->lson!=NULL&&(height(node->rson->lson)>height(node->rson->rson)) )
                DoubleRotateRL(node);
            else
                SingRotateRight(node);
    }

    else if(x > node->data)
    {
         Deletepri(node->rson,x);//如果x大于节点的值,就继续在节点的右子树中删除x
         if(2==height(node->lson)-height(node->rson))
            if(node->lson->rson!=NULL&& (height(node->lson->rson)>height(node->lson->lson) ))
                DoubleRotateLR(node);
            else
                SingRotateLeft(node);
    }

    else//如果相等,此节点就是要删除的节点
    {
        if(node->lson&&node->rson)//此节点有两个儿子
        {
            TreeNode<T>* temp=node->rson;//temp指向节点的右儿子
            while(temp->lson!=NULL) temp=temp->lson;//找到右子树中值最小的节点
            //把右子树中最小节点的值赋值给本节点
            node->data=temp->data;
            node->freq=temp->freq;
            Deletepri(node->rson,temp->data);//删除右子树中最小值的节点
            if(2==height(node->lson)-height(node->rson))
            {
                if(node->lson->rson!=NULL&& (height(node->lson->rson)>height(node->lson->lson) ))
                    DoubleRotateLR(node);
                else
                    SingRotateLeft(node);
            }
        }
        else//此节点有1个或0个儿子
        {
            TreeNode<T>* temp=node;
            if(node->lson==NULL)//有右儿子或者没有儿子
            node=node->rson;
            else if(node->rson==NULL)//有左儿子
            node=node->lson;
            delete(temp);
            temp=NULL;
        }
    }
    if(node==NULL) return;
    node->hgt=Max(height(node->lson),height(node->rson))+1;
    return;
}

全部实现代码如下,测试见main函数:

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

template <class T>
struct AVLNode
{
    T key;
    int height;
    AVLNode(T e, AVLNode<T> *l = NULL, AVLNode<T> *r = NULL) :
        key(e), height(0), lChild(l), rChild(r){}
    AVLNode<T> *lChild, *rChild;
};

template <class T>
class AVLTree
{
protected:
    void InOrder(AVLNode<T> *&t) const;
    bool Insert(AVLNode<T> *&t, T key);
    bool Remove(AVLNode<T> *&t, T key);
    void LeftRotate(AVLNode<T> *&p);
    void RightRotate(AVLNode<T> *&p);
    void PreOrder(AVLNode<T> *&t) const;
public:
    AVLTree() { root = NULL; }
    ~AVLTree() { Clear(root); }
    int GetHeight(AVLNode<T> *t);
    void InOrder();
    void PreOrder();
    bool Insert(T key);
    bool Remove(T key);
    void Clear(AVLNode<T> *&t);
    AVLNode<T> *root;
};


template <class T>
void AVLTree<T>::Clear(AVLNode<T> *&t)
{
    if (!t)
        return;
    Clear(t->lChild);
    Clear(t->rChild);
    delete t;
}

template <class T>
int AVLTree<T>::GetHeight(AVLNode<T> *t)
{
    return t ? t->height : 0;
}

template <class T>
void AVLTree<T>::PreOrder(AVLNode<T> *&t) const
{
    if (!t)
        return;
    cout << t->key << " ";
    PreOrder(t->lChild);
    PreOrder(t->rChild);
}

template <class T>
void AVLTree<T>::PreOrder()
{
    if (!root)
        return;
    cout << "前序遍历为:" << endl;
    PreOrder(root);
    cout << endl;
}



template <class T>
void AVLTree<T>::InOrder(AVLNode<T> *&t) const
{
    if (!t)
        return;
    InOrder(t->lChild);
    cout << t->key << " ";
    InOrder(t->rChild);
}

template <class T>
void AVLTree<T>::InOrder()
{
    if (!root)
        return;
    cout << "中序遍历为:" << endl;
    InOrder(root);
    cout << endl;
}


template <class T>
inline void AVLTree<T>::RightRotate(AVLNode<T> *&p)
{
    AVLNode<T> *l = p->lChild;
    p->lChild = l->rChild;
    l->rChild = p;
    p->height = max(GetHeight(p->lChild), GetHeight(p->rChild)) + 1;
    l->height = max(GetHeight(l->lChild), p->height) + 1;
    p = l;
}

template <class T>
inline void AVLTree<T>::LeftRotate(AVLNode<T> *&p)
{
    AVLNode<T> *r = p->rChild;
    p->rChild = r->lChild;
    r->lChild = p;
    p->height = max(GetHeight(p->lChild), GetHeight(p->rChild)) + 1;
    r->height = max(GetHeight(r->rChild), p->height) + 1;
    p = r;
}


template <class T>
bool AVLTree<T>::Insert(T key)
{
    return Insert(root, key);
}

template <class T>
bool AVLTree<T>::Insert(AVLNode<T> *&t, T key)
{
    if (!t)
    {
        t = new AVLNode<T>(key);
        if (!t)
            return false;
    }
    if (key < t->key)
    {
        if (!Insert(t->lChild, key))
            return false;
        if (2 == GetHeight(t->lChild) - GetHeight(t->rChild))
        {
            if (key < t->lChild->key)
            {
                RightRotate(t);
            }
            else
            {
                LeftRotate(t->lChild);
                RightRotate(t);
            }
        }
    }
    else if (key > t->key)
    {
        if (!Insert(t->rChild, key))
            return false;
        if (2 == GetHeight(t->rChild) - GetHeight(t->lChild))
        {
            if (key > t->rChild->key)
            {
                LeftRotate(t);
            }
            else
            {
                RightRotate(t->rChild);
                LeftRotate(t);
            }
        }
    }
    t->height = max(GetHeight(t->lChild), GetHeight(t->rChild)) + 1;
    return true;
}



template <class T>
bool AVLTree<T>::Remove(T key)
{
    return Remove(root, key);
}
template <class T>
bool AVLTree<T>::Remove(AVLNode<T> *&t, T key)
{
    if (!t)
        return false;
    if (key < t->key)
    {
        if (!Remove(t->lChild, key))
            return false;
        if (GetHeight(t->rChild) - GetHeight(t->lChild) == 2)
        {
            AVLNode<T> *r = t->rChild;
            if (GetHeight(r->lChild) > GetHeight(r->rChild))
            {
                RightRotate(t->rChild);
                LeftRotate(t);
            }
            else
                RightRotate(t);
        }
    }
    else if (key > t->key)
    {
        if (!Remove(t->rChild, key))
            return false;
        if (GetHeight(t->lChild) - GetHeight(t->rChild) == 2)
        {
            AVLNode<T> *l = t->lChild;
            if (GetHeight(l->rChild) > GetHeight(l->lChild))
            {
                LeftRotate(t->lChild);
                RightRotate(t);
            }
            else
                LeftRotate(t);
        }
    }
    else
    {
        if (t->lChild && t->rChild)
        {
            if (GetHeight(t->lChild) > GetHeight(t->rChild))
            {
                AVLNode<T> *s = t->lChild;
                //while (s->lChild)
                    //s = s->lChild;
                t->key = s->key;
                Remove(t->lChild, s->key);
            }
            else
            {
                AVLNode<T> *s = t->rChild;
                //while (s->lChild L)
                //  s = s->lChild;
                t->key = s->key;
                Remove(t->rChild, s->key);
            }
        }
        else
        {
            AVLNode<T> *p = t;
            t = t->lChild ? t->lChild : t->rChild;
            delete p;
        }
    }
    return true;
}


int main(void)
{
    AVLTree<int> T;
    for (int i = 1; i <= 7; i++)
        T.Insert(i);      
/*
下面代码建立一个形如:
        4
      /   \
     2     6
    / \   / \
   1   3 5    7
的二叉树
*/

    T.InOrder();      // 进行中序遍历,结果应该为:1,2,3,4,5,6,7
    cout << endl;   

    T.PreOrder();     //进行先序遍历,结果应为:4,2,1,3,6,5,7
    cout << endl;

    T.Remove(5);     //移除元素5,再进行中序遍历,结果应为:1,2,3,4,6,7
    T.InOrder();

    T.Insert(8);    //插入元素8
    T.InOrder();    //此时要经过失衡旋转,再进行中序遍历结果应为:1,2,3,4,6,7,8
    cout << endl;

    T.PreOrder();   //进行先序遍历,结果应为:4,2,1,3,7,6,8
    cout << endl;

    T.Insert(9);   //在插入元素9,进行中序遍历,结果应为:1,2,3,4,6,7,8,9
    T.InOrder();
    cout << endl;

    bool v = T.Remove(9);  //删除元素9,再进行中序遍历,结果应为:1,2,3,4,6,7,8
    T.InOrder();
    cout << endl;
    T.PreOrder();        //再进行先序遍历,结果应为:4,2,1,3,7,6,8
    cout << endl;



    cout << endl;
    return 0;
}

运行结果如下,运行环境为VS2015:
Alt text

学习及资料参考:
《数据结构—使用C++语言描述》(第2版) 人民邮电出版社 陈慧南
《数据结构(C++语言版)》(第3版)清华大学出版社 邓俊辉
C小加博客之一步一步写平衡二叉树(AVL树)http://www.cppblog.com/cxiaojia/archive/2012/08/20/187776.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值