AVL树的插入删除查找算法实现和分析-1(平衡因子法)


      最近在学习数据结构,正在学习的有平衡二叉树(AVL树)和红黑树,红黑树是平衡二叉树的变种,因此在学习红黑树之前最好先学习AVL树,就目前使用度而言,红黑树在各个领域使用的更加广泛,因为红黑树相对AVL树来说,是一种不严格的旋转策略,而AVL树是相对严格的平衡策略(一旦发现不平衡了,立即旋转),因此对大数据而言,AVL树的查找效率高,而插入和删除相对慢一些,而红黑树的插入和删除效率要高一些,因此要根据不同应用场景选择。

AVL树的最大高度是1.44 * log(N+2) - 1.328,红黑树的最大高度是2.00* log(N+1)。

这篇文章转载自:http://blog.csdn.net/ljianhui/article/details/9137177 

感谢这位作者,不过对于作者提出的导致复杂的原因中说明使用了二级指针使程序复杂,我个人的观点认为:对于程序而言,最重要的是性能和稳定,的确二级指针很容易出错,但是为了方便而使返回是指针的话,如果程序在某个地方出现了错误,返回了NULL指针,那么这个程序的这部分数据结构,可能就有内存泄露或段错误之类的未知错误,很容易导致程序死去,这样的话,诚然可以很快(也许)找到错误点,但如果数据结构是用于服务器开发, 总不可能让服务器在运行时候死掉吧,所以我个人还是觉得性能和稳定最重要,在用二级指针小心一点就是。(只是个人观点)


在最后我将附上我自己写的代码,我没有写注释,因为和原作者差不多的。在下一个AVL实现(高度法)我将注明我和作者不一样的地方,同时会注释的,希望大家去支持原作者。如果有版权的问题,可以找我。


至于什么是AVL树和AVL树的一些概念问题在这里就不多说了,下面是我写的代码,里面的注释非常详细地说明了实现的思想和方法。


因为在操作时真正需要的是子树高度的差,所以这里采用-1,0,1来表示左子树和右子树的高度差,而没有使用记录树的高度的方法。

代码如下:

  1. #define FALSE 0  
  2. #define TRUE 1  
  3. #define LH 1  
  4. #define EH 0  
  5. #define RH -1  
  6.   
  7. typedef struct AVLNode  
  8. {  
  9.     DataType cData;  
  10.     int nBf;        //结点的平衡因子,-1表示右子树的深度比左子树高1  
  11.                     //0表示左子树与右子树深度相等  
  12.                     //1表示左子树的深度比右子树高1  
  13.     struct AVLNode *LChild;  
  14.     struct AVLNode *RChild;  
  15. }AVLNode,*AVLTree;  
  16.   
  17. typedef int BOOL;  

  1. void R_Rotate(AVLTree *pAT)  
  2. {  
  3.     //对以*pAT为根的二叉树作右旋转处理,处理之后pAT指向新的树根结点  
  4.     //即旋转处理之前的左子树的根结点  
  5.     AVLTree lc = (*pAT)->LChild;  
  6.     (*pAT)->LChild = lc->RChild;  
  7.     lc->RChild = *pAT;  
  8.     *pAT = lc;  
  9. }  
  10.   
  11. void L_Rotate(AVLTree *pAT)  
  12. {  
  13.     //对以*pAT为根的二叉树作左旋转处理,处理之后pAT指向新的树根结点  
  14.     //即旋转处理之前的右子树的根结点  
  15.     AVLTree rc = (*pAT)->RChild;  
  16.     (*pAT)->RChild = rc->LChild;  
  17.     rc->LChild = *pAT;  
  18.     *pAT = rc;  
  19. }  
  20.   
  21. void LeftBalance(AVLTree *pAT)  
  22. {  
  23.     //对以指针pAT所指结点为根的二叉树作左平衡旋转处理,  
  24.     //本算法结束时指针pAT指向新的结点  
  25.     AVLTree lc = (*pAT)->LChild;    //lc指向*pAT的左子树根结点  
  26.     AVLTree rd = NULL;  
  27.     if(lc)  
  28.     switch(lc->nBf)     //检查*pAT的左子树的平衡度,并作相应平衡处理  
  29.     {  
  30.         case LH:    //新结点插入在*pAT的左孩子的左子树上,要作单右旋转处理  
  31.             (*pAT)->nBf = lc->nBf = EH;  
  32.             R_Rotate(pAT);  
  33.             break;  
  34.         case RH:    //新结点插入在*pAT的左孩子的右子树上,要作双旋转处理  
  35.             rd = lc->RChild;  
  36.             switch(rd->nBf) //修改*pAT及其左孩子的平衡因子  
  37.             {  
  38.                 case LH:  
  39.                     (*pAT)->nBf = RH;  
  40.                     lc->nBf = EH;  
  41.                     break;  
  42.                 case EH:  
  43.                     (*pAT)->nBf = lc->nBf = EH;  
  44.                     break;  
  45.                 case RH:  
  46.                     (*pAT)->nBf = EH;  
  47.                     lc->nBf = LH;  
  48.                     break;  
  49.                 default:;  
  50.             }  
  51.             rd->nBf = EH;  
  52.             L_Rotate(&(*pAT)->LChild);  //对*pAT的左子树作左平衡处理  
  53.             R_Rotate(pAT);      //对*pAT作右平衡处理  
  54.             break;  
  55.         default:;  
  56.     }  
  57. }  
  58.   
  59. void RightBalance(AVLTree *pAT)  
  60. {  
  61.     //对以指针pAT所指结点为根的二叉树作右平衡旋转处理,  
  62.     //本算法结束时指针pAT指向新的结点  
  63.     AVLTree rc = (*pAT)->RChild;  
  64.     AVLTree rd = NULL;  
  65.     if(rc)  
  66.     switch(rc->nBf)  
  67.     {  
  68.         case RH:  
  69.             (*pAT)->nBf = rc->nBf = EH;  
  70.             L_Rotate(pAT);  
  71.             break;  
  72.         case LH:  
  73.             rd = rc->LChild;  
  74.             switch(rd->nBf)  
  75.             {  
  76.                 case LH:  
  77.                     (*pAT)->nBf = EH;  
  78.                     rc->nBf = RH;  
  79.                     break;  
  80.                 case EH:  
  81.                     (*pAT)->nBf = rc->nBf = EH;  
  82.                     break;  
  83.                 case RH:  
  84.                     (*pAT)->nBf = LH;  
  85.                     rc->nBf = EH;  
  86.                     break;  
  87.             }  
  88.             rd->nBf = EH;  
  89.             R_Rotate(&(*pAT)->RChild);  
  90.             L_Rotate(pAT);  
  91.         default:;  
  92.     }  
  93. }  

  1. BOOL InsertATNode(AVLTree *pAT, DataType c)  
  2. {  
  3.     //若在平衡的二叉树pAT中不存在和c相同的关键字结点,  
  4.     //则插入一个以c为数据元素的新结点,并返回1,否则返回0  
  5.     //若因插入而使二叉排序树失去平衡,则作平衡旋转处理  
  6.     static int taller = FALSE;  //反映pAT树是否长高  
  7.     if(!(*pAT))  
  8.     {  
  9.         //插入新结点,树长高,taller为TRUE;  
  10.         (*pAT) = (AVLTree)malloc(sizeof(AVLNode));  
  11.         (*pAT)->cData = c;  
  12.         (*pAT)->LChild = (*pAT)->RChild = NULL;  
  13.         (*pAT)->nBf = EH;  
  14.         taller = TRUE;  
  15.     }  
  16.     else  
  17.     {  
  18.         if((*pAT)->cData == c)  
  19.         {  
  20.             //树中已存在和e相同关键字的结点,不插入  
  21.             taller = FALSE;  
  22.             return 0;  
  23.         }  
  24.         if(c < (*pAT)->cData)  
  25.         {  
  26.             //应该在*pAT的左子树中进行搜索  
  27.             if(!InsertATNode(&(*pAT)->LChild, c))   //未插入  
  28.                 return 0;  
  29.             if(taller)  //已插入到树pAT并且左子树长高  
  30.             {  
  31.                 switch((*pAT)->nBf) //检查*pAT的平衡因子  
  32.                 {  
  33.                     case LH:    //原本左子树比右子树高,作左平衡处理  
  34.                         LeftBalance(pAT);  
  35.                         taller = FALSE;  
  36.                         break;  
  37.                     case EH:    //原本左右子树等高,现左子树比右子树高1  
  38.                         (*pAT)->nBf = LH;  
  39.                         taller = TRUE;  
  40.                         break;  
  41.                     case RH:    //原本右子树比左子树高,现左右子树等高  
  42.                         (*pAT)->nBf = EH;  
  43.                         taller = FALSE;  
  44.                         break;  
  45.                 }  
  46.             }  
  47.         }  
  48.         else  
  49.         {  
  50.             //应该在*pAT的右子树中进行搜索  
  51.             if(!InsertATNode(&(*pAT)->RChild, c))   //未插入  
  52.                 return 0;  
  53.             if(taller)  //已插入到树pAT并且右子树长高  
  54.             {  
  55.                 switch((*pAT)->nBf) //检查*pAT的平衡因子  
  56.                 {  
  57.                     case LH:    //原本左子树比右子树高,现左右子树等高  
  58.                         (*pAT)->nBf = EH;  
  59.                         taller = FALSE;  
  60.                         break;  
  61.                     case EH:    //原本左右子树等高,现右子树比左子树高1  
  62.                         (*pAT)->nBf = RH;  
  63.                         taller = TRUE;  
  64.                         break;  
  65.                     case RH:    //原本右子树比左子树高,作右平衡处理  
  66.                         RightBalance(pAT);  
  67.                         taller = FALSE;  
  68.                         break;  
  69.                 }  
  70.             }  
  71.         }  
  72.     }  
  73.     return 1;  
  74. }  

  1. BOOL DeleteNode(AVLTree *pAT, DataType c)  
  2. {  
  3.     //若在平衡的二叉树pAT中存在和c相同的关键字结点,  
  4.     //则删除一个以c为数据元素的结点,并返回1,否则返回0  
  5.     //若因删除而使二叉排序树失去平衡,则作平衡旋转处理  
  6.     static int lower = FALSE;   //反映pAT树是否变矮  
  7.     if(!(*pAT))     //树为空或结点不存在返回0  
  8.         return 0;  
  9.     if(c == (*pAT)->cData)  
  10.     {  
  11.         //存在要删除的结点  
  12.         //查找用作替换的结点  
  13.         AVLTree Min = FindMin((*pAT)->RChild);  
  14.         if(Min != NULL) //存在右子树  
  15.         {  
  16.             //找到可以用作替换的点  
  17.             (*pAT)->cData = Min->cData;  
  18.             if(Min != (*pAT)->RChild)  
  19.             {  
  20.                 AVLTree Parent = GetParent((*pAT)->RChild, Min->cData);  
  21.                 Parent->LChild = Min->RChild;  
  22.             }  
  23.             else  
  24.             {  
  25.                 (*pAT)->RChild = Min->RChild;  
  26.             }  
  27.   
  28.             free(Min);  
  29.         }  
  30.         else //不存在右子树  
  31.         {  
  32.             //找不到删除的结点  
  33.             Min = *pAT;  
  34.             *pAT = (*pAT)->LChild;  
  35.             free(Min);  
  36.         }  
  37.         lower = TRUE;  
  38.     }  
  39.     else if(c < (*pAT)->cData)  
  40.     {  
  41.         //应该在*pAT的左子树中进行搜索  
  42.         if(!DeleteNode(&(*pAT)->LChild, c)) //未删除  
  43.             return 0;  
  44.   
  45.         if(lower)   //树变矮  
  46.         {  
  47.             switch((*pAT)->nBf)  
  48.             {  
  49.                 case LH:    //原本左子树比右子树高,现在等高  
  50.                     (*pAT)->nBf = EH;  
  51.                     lower = TRUE;  
  52.                     break;  
  53.                 case EH:    //原本左右子树等高,现右子树比左子树高1  
  54.                     (*pAT)->nBf = RH;  
  55.                     lower = FALSE;  
  56.                     break;  
  57.                 case RH:    //原本右子树比左子树高,则作右平衡处理  
  58.                     RightBalance(pAT);  
  59.                     lower = TRUE;  
  60.                     break;  
  61.             }  
  62.         }  
  63.     }  
  64.     else if(c > (*pAT)->cData)  
  65.     {  
  66.         //应该在*pAT的右子树中进行搜索  
  67.         if(!DeleteNode(&(*pAT)->RChild, c))  
  68.             return 0;  
  69.         if(lower)  
  70.         {  
  71.             switch((*pAT)->nBf)  
  72.             {  
  73.                 case LH:    //原本左子树比右子树高,则作左平衡处理  
  74.                     LeftBalance(pAT);  
  75.                     lower = TRUE;  
  76.                     break;  
  77.                 case EH:    //原本左右子树等高,现左子树比右子树高1  
  78.                     (*pAT)->nBf = LH;  
  79.                     lower = FALSE;  
  80.                     break;  
  81.                 case RH:    //原本右子树比左子树高,现左左子树等高  
  82.                     (*pAT)->nBf = EH;  
  83.                     lower = TRUE;  
  84.                     break;  
  85.             }  
  86.         }  
  87.     }  
  88.     return 1;  
  89. }  

  1. AVLTree FindMin(AVLTree AT)  
  2. {  
  3.     //查找AT中最小的元素  
  4.     while(AT && AT->LChild)  
  5.     {  
  6.         AT = AT->LChild;  
  7.     }  
  8.     return AT;  
  9. }  
  10.   
  11. AVLTree GetParent(AVLTree AT, DataType c)  
  12. {  
  13.     if(!AT || AT->cData == c)  
  14.         return NULL;  
  15.     if((AT->LChild && AT->LChild->cData == c) ||  
  16.             (AT->RChild && AT->RChild->cData == c))  
  17.         return AT;  
  18.     else if((AT->LChild && c < AT->LChild->cData))  
  19.         return GetParent(AT->LChild, c);  
  20.     else  
  21.         return GetParent(AT->RChild, c);  
  22. }  
  23.   
  24. AVLTree FindATNode(AVLTree AT, DataType c)  
  25. {  
  26.     if(!AT)  
  27.         return NULL;  
  28.     else if(AT->cData == c)  
  29.         return AT;  
  30.     else if(c < AT->cData)  
  31.     {  
  32.         return FindATNode(AT->LChild, c);  
  33.     }  
  34.     else  
  35.     {  
  36.         return FindATNode(AT->RChild, c);  
  37.     }  
  38. }  

现在对这种实现方法作一点小小的分析。

1、至于时间复杂度的分析就不必多说了,所有的算法的时间复杂度都为log2N。

2、从代码的数量可以看出这并不是一种好的实现方法,因为它的思路不太清晰和直观,操作实现比较复杂,还要用到二重指针,增加了程序出错的机会。

3、导致复杂的原因主要有两个,

1)第一个就是在高度信息储存方法上,不是采用每个结点保存自己作为一棵树的深度,而是保存着左右子树之差(左子树的深度 - 右子树的深度),从而产生了大量的判断和switch语句,让人眼花瞭乱,并且影响程序的效率和易读性,难以维护,所以用一个变量记录树的高度会让程序思路更清晰。

2)再者,在这里的旋转、插入的删除操作都要调节结点并改变指向结点的指针变量的值,在C语言中是没有引用的(C++有),所以就要用到双重指针,这无疑加大了程序的复杂度,而且使程序更容易出错。而避免这样的情况的做法是很简单的,只需要做一个简单的修改。就是让旋转插入等操作的返回值为指向结点的指针,而不是一个标志操作是否成功的状态值。而且这样做还有一个好处,就是通常最后插入的数据总是最先被访问,这样就可以根据返回的结点的指针马上访问该结点,而不用再在整棵树中查找。


算法如有错误,还请各位读者指出,谢谢!

改进后的算法会在下一篇文章(AVL树的插入删除查找算法实现和分析-2(树高度法))中给出。


我自己写的代码, 在rhel6下可以运行,粘贴上来,在另外一个高度法,会进行注释的。 用C写的。有错记得提醒我哦,谢谢。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define LH 1
#define EH 0
#define RH -1

#define TRUE 1
#define FALSE 0

typedef struct avl_tree_s {
	int data;
	int bf;
	struct avl_tree_s *lchild;
	struct avl_tree_s *rchild;
}avl_tree_t;

avl_tree_t *get_min(avl_tree_t *tree)
{
        while(tree && tree->lchild) {
                tree = tree->lchild;
        }

        return tree;
}

avl_tree_t *get_parent(avl_tree_t *tree, int data) 
{
        if(!tree || tree->data == data) {
                return NULL;
        }

        if((tree->lchild && tree->lchild->data == data) 
                || (tree->rchild && tree->rchild->data == data)) {
                return tree;
        }else if(tree->data > data) {
                return get_parent(tree->lchild, data);
        }else {
                return get_parent(tree->rchild, data);
        }
        
        return NULL;                                
}


void l_roate(avl_tree_t **t) 
{
	avl_tree_t *r = NULL;
	if(!t || !(*t)) {
		return ;
	}
	
	r = (*t)->rchild;	
	(*t)->rchild = r->lchild;
	r->lchild = (*t);

	*t = r;	
}

void r_roate(avl_tree_t **t) 
{
	avl_tree_t *l = NULL;
	if(!t || !(*t)) {
		return ;
	}
	
	l = (*t)->lchild;	
	(*t)->lchild = l->rchild;
	l->rchild = (*t);

	*t = l;	
}

void left_balance(avl_tree_t **t)
{
	avl_tree_t *l = NULL;
	avl_tree_t *lr = NULL;
	if(!t || !(*t)) {
		return ;
	}

	l = (*t)->lchild;

        if(!l) {
                return;
        }

	switch(l->bf) {
		case LH:
			(*t)->bf = l->bf = EH;
			r_roate(t);
			break;
		case RH:
			lr = l->rchild;	
			switch(lr->bf) {
				case LH:
					(*t)->bf = RH;
					l->bf = EH;
					break;
				case EH:
					(*t)->bf = l->bf = EH;
					break;
				case RH:
					(*t)->bf = EH;		
					l->bf = LH;
					break;
			}	

			lr->bf = EH;
			l_roate(&((*t)->lchild));
			r_roate(t);
	}
	
}

void right_balance(avl_tree_t **t)
{
	avl_tree_t *r = NULL;
	avl_tree_t *rl = NULL;
	if(!t || !(*t)) {
		return ;
	}

	r = (*t)->rchild;

        if(!r) {
                return;
        }

	switch(r->bf) {
		case RH:		
			(*t)->bf = EH;			
			r->bf = EH;
			l_roate(t);
			break;
		case LH:
			rl = r->lchild;
			switch(rl->bf) {
				case LH:
					(*t)->bf = EH;
					r->bf = RH;	
					break;
				case EH:
					(*t)->bf = r->bf = EH;
					break;
				case RH:	
					(*t)->bf = LH;
					r->bf = EH;
					break;
			}	
			rl->bf = EH;
	
			r_roate(&((*t)->rchild));
			l_roate(t);
	}
}

int *del_node(avl_tree_t **tree, int data)
{
        avl_tree_t *node = NULL;       
        avl_tree_t *min = NULL;
        avl_tree_t *parent = NULL;
        static int lower = FALSE;

        if(!tree || !(*tree)) {
                return FALSE;
        }

        node = *tree;

        if(node->data == data) {
                //find
                //get the replace node
                min = get_min(node->rchild);
                if(min != NULL) {
                        node->data = min->data;
                        if(min == node->rchild) {
                               (*tree)->rchild = min->rchild;
                        }else {
                                parent = get_parent(node->rchild, min->data);
                                if(parent)
                                        parent->lchild = min->rchild;
                        }
                }else {
                        min = node;
                        (*tree) = node->lchild;
                }
                free(min);

                lower = TRUE;
        }
        else if(node->data > data) {
                //left
                if(del_node(&(node->lchild), data) != TRUE) {
                        return FALSE;
                }

                node = *tree;   //tree可能变化,重新赋值

                if(lower == TRUE) {
                       switch(node->bf) {
                               case LH:
                                        node->bf = EH;
                                        lower = TRUE;
                                        break;
                               case EH:
                                        node->bf = RH;
                                        lower = FALSE;
                                        break;
                               case RH:
                                        right_balance(tree);
                                        lower = TRUE;
                                        break;
                       } 
                }
        }else {
                //right
                if(del_node(&(node->rchild), data) != TRUE) {
                        return FALSE;
                }
                node = *tree;   //tree可能变化,重新赋值

                if(lower == TRUE) {
                       switch(node->bf) {
                               case LH:
                                        left_balance(tree);
                                        lower = TRUE;
                                        break;
                               case EH:
                                        node->bf = LH;
                                        lower = FALSE;
                                        break;
                               case RH:
                                        node->bf = EH;
                                        lower = TRUE;
                                        break;
                       } 
                }
        }

        return TRUE;
}

void show_first(avl_tree_t *tree)
{
	if(tree) {
		printf("%d ", tree->data);
		show_first(tree->lchild);
		show_first(tree->rchild);
	}
}

void show_center(avl_tree_t *tree)
{
	if(tree) {
		show_center(tree->lchild);
		printf("%d ", tree->data);
		show_center(tree->rchild);
	}
}

void show_after(avl_tree_t *tree)
{
	if(tree) {
		show_after(tree->lchild);
		show_after(tree->rchild);
		printf("%d ", tree->data);
	}
}

void del_tree(avl_tree_t *tree)
{
	if(tree) {
		free(tree);
		tree = NULL;
	}
}

void destroy_tree(avl_tree_t *tree)
{
	if(tree) {
		destroy_tree(tree->lchild);
		destroy_tree(tree->rchild);
		del_tree(tree);
	}
}

int avl_insert(avl_tree_t **tree, int data, int *taller)
{
	if(tree == NULL || !taller) {
		return FALSE;
	}

	if(*tree == NULL) {
		(*tree) = (avl_tree_t *)calloc(sizeof(avl_tree_t), 1);				
		if((*tree) == NULL) {
			*taller = 0;
			return FALSE;
		}
		(*tree)->data = data; 
		(*tree)->bf = EH;
		*taller = TRUE;
			
		return TRUE;
	}

	if((*tree)->data == data) {
		*taller = FALSE;
		return FALSE;
	}else if ((*tree)->data > data) {
		//left	
		if(avl_insert(&((*tree)->lchild), data, taller) != TRUE) {
			return FALSE;
		}

		if(*taller == TRUE) {
			switch((*tree)->bf) {
				case LH:
					left_balance(tree);
					*taller = FALSE;
					break;
				case EH:
					(*tree)->bf = LH;
					*taller = TRUE;
					break;
				case RH:
					(*tree)->bf = EH;
					*taller = FALSE;
					break;
			}			
		}	
	}else if ((*tree)->data < data) {
		if(avl_insert(&((*tree)->rchild), data, taller) != TRUE) {
			return FALSE;
		}

		if(*taller == TRUE) {
			switch((*tree)->bf) {
				case LH:
					(*tree)->bf = EH;
					*taller = FALSE;
					break;
				case EH:
					(*tree)->bf = RH;
					*taller = TRUE;
					break;
				case RH:
					right_balance(tree);
					*taller = FALSE;
					break;	
			}
		}

	}

	return TRUE;
}

int avl_tree_init(avl_tree_t **tree, int *data, int n)
{
	int i = 0;
	int taller = 0;

	if( !data || n <= 0 || !tree) {
		return -1;	
	}

	for(i = 0; i < n; i++) {
		avl_insert(tree, data[i], &taller);
		printf("xianxu:");
		show_first(*tree);
		printf("\nzhongxu:");
		show_center(*tree);
		printf("\n");
	}	

	return 0;
}

int main()
{
	int data[10] = {3, 2, 1, 4, 5, 6, 7, 10, 9, 8};
	avl_tree_t *tree = NULL;		
	
	avl_tree_init(&tree, data, 10);

	show_first(tree);
	printf("\n");
	show_center(tree);
	printf("\n");
	show_after(tree);
	printf("\n");

        del_node(&tree, 7);
        //del_node(&tree, 5);
        del_node(&tree, 4);

        printf("-------\n");
        show_first(tree);
	printf("\n");
	show_center(tree);
	printf("\n");
	show_after(tree);
	printf("\n");

	destroy_tree(tree);	
	tree = NULL;

	return 0;
	
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值