数据结构 关于树的知识点

一种非线性的数据结构,是以分支关系定义的层次结构。

比如人类社会中的族谱,社会机构关系

重点

  • 二叉树的存储,相关操作
  • 树,森林,二叉树的转换关系

树的基本术语

  • :是n个节点的有限级,0 == n为空树,不讨论
  • 根结点:树的最顶层结点,一棵树仅有一个
  • 子树:一棵树除根节点外,剩余的互不相交的有限级,每一个集合本身又是一颗树,被称为根的子树
  • 结点的度:树的结点包含一个数组元素及若干个指向其子树的分支,结点拥有的子树成为结点的度
  • 叶子结点:结点的度为0,被称为叶子结点或终端结点
  • 分支结点:结点的度部不为0,被称为分支结点或非终端结点,也被称为内部结点
  • 树的度:是指树内各结点度的最大值
  • 密度:一颗树中结点的总数
  • 孩子,双亲,兄弟,祖先结点:结点的子树被称为该结点的孩子,该结点是孩子结点的双亲结点,共同双亲的互为兄弟结点,从双亲结点往上都被称为孩子结点的祖先结点
  • 层数,深度,高度,密度:从根节点开始定义,根为第一层,根的孩子为第二层,树中结点的最大层数被称为树的深度或高度,所有结点的数量
  • 有序树和无序树:将树种结点的各子树看成从左到右是有序次,即不能交换则称该树为有序树,否则称为无序树
  • 森林:若干棵互不相交的竖的集合称为森林,对树中每个结点而言,其指数集合就是森林

就逻辑结构而言,任何一颗树都是一个二元组Tree = (root,F),其中root是数据元素,称为树的根结点,F是若干棵子树构成的森林

二叉树的定义和性质(背)

定义

一种特殊的树形结构,每个结点最有有两颗子树(二叉树不存在度大于2的结点),二叉树的子树有左右之分,顺序不能颠倒

满二叉树:若一棵树的层数为k,则它的结点数是2^k-1,则这棵树是满二叉树

完全二叉树:若一棵树的层数为k,则它的前k-1层的结点数是2^(k-1)-1,第k层的结点按照从左往右顺序排序

性质:

  • **在二叉树的第i层上最多有2^(i-1)个结点 **
  • 深度为k的二叉树,最多有2^k - 1个结点
  • 对于任何一颗二叉树,如果叶子结点数为n0,度为2结点的数量为n2,则n0 = n2+1
  • 具有n个结点的完全二叉树的深度为log2(n)+1
  • 如果对一颗有n个结点完全二叉树,结点按照从上到下,从左到右的顺序排序为1~n。
    • i>1时 i/2就是它的双亲结点
    • 2*i 是 i 的左子树,2*i+1 是 i 的右子树

二叉树不同型式

二叉树的遍历

  • 前序:根 左 右
    1. 判断二叉树是否为空,若二叉树为空,则不操作
    2. 访问根结点
    3. 前序遍历左子树
    4. 前序遍历右子树
  • 中序:左 根 右
    1. 判断二叉树是否为空,若二叉树为空,则不操作
    2. 中序遍历左子树
    3. 访问根结点
    4. 中序遍历右子树
  • 后序:左 右 根
    1. 判断二叉树是否为空,若二叉树为空,则不操作
    2. 后序遍历左子树
    3. 后序遍历右子树
    4. 访问根结点
  • 层序:从上到下 从左到右,需要与队列结构配合

二叉树的顺序存储

前提:由于顺序存储需要根据元素的相对位置确定关系,所以先把二叉树补成完全二叉树,之前空的点可以用特殊值代表,并把完全二叉树按照层序遍历结果存储到数组中。

只有补成了完全二叉树才能根据性质5的公式对二叉树进行相关操作

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

#define PARENT_TREE(i) 	((i + 1) / 2 - 1)
#define LEFT_TREE(i) 	((i + 1) * 2 - 1)
#define RIGHT_TREE(i) 	((i + 1) * 2)

typedef struct TreeArray
{
	char *arr;
	size_t cnt;
} TreeArray;

// 创建树
TreeArray *create_tree(const char *str)
{
	TreeArray *tree = malloc(sizeof(TreeArray));
	tree->cnt = strlen(str);
	tree->arr = malloc(tree->cnt);
	strncpy(tree->arr, str, tree->cnt);
	return tree;
}

// 初始化树
void init_tree(TreeArray *tree, const char *str)
{
	free(tree->arr);
	tree->cnt = strlen(str);
	tree->arr = malloc(tree->cnt);
	strncpy(tree->arr, str, tree->cnt);
}

// 销毁树
void destroy_tree(TreeArray *tree)
{
	free(tree->arr);
	free(tree);
}

// 清空树
void clear_tree(TreeArray *tree)
{
	free(tree->arr);
	tree->cnt = 0;
}

// 判断是否是空树
bool empty_tree(TreeArray *tree)
{
	return 0 == tree->cnt || '#' == tree->arr[0];
}

// 计算树的深度
size_t hight_tree(TreeArray *tree)
{
	size_t hight = 1;
	while (pow(2, hight) < tree->cnt)
		hight++;
	return hight;
}

// 访问根结点
char root_tree(TreeArray *tree)
{
	if (empty_tree(tree))
		return '#';

	return tree->arr[0];
}

// 访问指定的结点
char value_tree(TreeArray *tree, size_t index)
{
	if (index >= tree->cnt)
		return '#';

	return tree->arr[index];
}

// 给指定的位置赋值
bool assign_tree(TreeArray *tree, size_t index, char value)
{
	if (index >= tree->cnt)
		return false;

	tree->arr[index] = value;
	return true;
}

// 访问双亲结点
char parent_tree(TreeArray *tree, size_t index)
{
	if (0 == index || PARENT_TREE(index) >= tree->cnt)
		return '#';

	return tree->arr[PARENT_TREE(index)];
}

// 访问左子树结点
char left_child_tree(TreeArray *tree, size_t index)
{
	if (LEFT_TREE(index) >= tree->cnt)
		return '#';

	return tree->arr[LEFT_TREE(index)];
}

// 访问右子树结点
char right_child_tree(TreeArray *tree, size_t index)
{
	if (RIGHT_TREE(index) >= tree->cnt)
		return '#';
	return tree->arr[RIGHT_TREE(index)];
}

// 访问左兄弟结点
char left_sigling_tree(TreeArray *tree, size_t index)
{
	int left = LEFT_TREE(PARENT_TREE(index));
	if (0 == index || left >= tree->cnt || left == index)
		return '#';

	return tree->arr[left];
}

// 访问右兄弟结点
char right_sigling_tree(TreeArray *tree, size_t index)
{
	int right = RIGHT_TREE(PARENT_TREE(index));
	if (0 == index || right >= tree->cnt || right == index)
		return '#';

	return tree->arr[right];
}

// 插入子树 lr(left true,right false)
bool insert_tree(TreeArray *tree, size_t index, bool lr, char value)
{
	size_t pos = lr ? LEFT_TREE(index) : RIGHT_TREE(index);
	if (pos >= tree->cnt || '#' != tree->arr[pos])
		return false;

	tree->arr[pos] = value;
	return true;
}

bool delete_tree(TreeArray *tree, size_t index, bool lr)
{
	// 计算出要删除的子树的下标
	size_t pos = lr ? LEFT_TREE(index) : RIGHT_TREE(index);
	// 判断要删除的子树下标是否合法
	if (pos >= tree->cnt)
		return false;

	// 计算出要删除结点的左右子树下标
	size_t pos_left = LEFT_TREE(pos), pos_right = RIGHT_TREE(pos);
	// 判断删除的结点是否有左子树,如果有则删除失败
	if (pos_left < tree->cnt && '#' != tree->arr[pos_left])
		return false;
	// 判断删除的结点是否有右子树,如果有则删除失败
	if (pos_right < tree->cnt && '#' != tree->arr[pos_right])
		return false;

	tree->arr[pos] = '#';
	return true;
}

// 前序遍历二叉树
void _pre_order_tree(TreeArray *tree, int index)
{
	// 根
	if (index >= tree->cnt || '#' == tree->arr[index])
		return;
	printf("%c ", tree->arr[index]);
	// 左
	_pre_order_tree(tree, LEFT_TREE(index));
	// 右
	_pre_order_tree(tree, RIGHT_TREE(index));
}

// 前序遍历
void pre_order_tree(TreeArray *tree)
{
	_pre_order_tree(tree, 0);
	printf("\n");
}

void _in_order_tree(TreeArray *tree, int index)
{
	if (index >= tree->cnt || '#' == tree->arr[index])
		return;
	// 左
	_in_order_tree(tree, LEFT_TREE(index));
	// 根
	printf("%c ", tree->arr[index]);
	// 右
	_in_order_tree(tree, RIGHT_TREE(index));
}

// 中序遍历
void in_order_tree(TreeArray *tree)
{
	_in_order_tree(tree, 0);
	printf("\n");
}

void _post_order_tree(TreeArray *tree, int index)
{
	if (index >= tree->cnt || '#' == tree->arr[index])
		return;
	// 左
	_post_order_tree(tree, LEFT_TREE(index));
	// 右
	_post_order_tree(tree, RIGHT_TREE(index));
	// 根
	printf("%c ", tree->arr[index]);
}

// 后序遍历
void post_order_tree(TreeArray *tree)
{
	_post_order_tree(tree, 0);
	printf("\n");
}

// 层序遍历
void level_order_tree(TreeArray *tree)
{
	for (int i = 0; i < tree->cnt; i++)
	{
		if ('#' != tree->arr[i])
			printf("%c ", tree->arr[i]);
	}
	printf("\n");
}

int main(int argc, const char *argv[])
{
	TreeArray *tree = create_tree("FCEADHG##B###M");
	level_order_tree(tree);
	printf("---------------------\n");
	printf("%d\n", hight_tree(tree));
	printf("%c\n", right_sigling_tree(tree, 4));
	insert_tree(tree, 0, true, 'X');
	printf("%d\n", delete_tree(tree, 1, false));
	return 0;
}

二叉树的链式存储

  1. 需要给每个度不为2的结点补充一些空白的子结点,使树中除了空白结点,其他结点的度全为2,然后以前,中,后序的方式遍历二叉树,然后以遍历的结果创建二叉树
  2. 不需要对二叉树做任何改变,以前序+中序或中序+后的序的遍历结果创建二叉树
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct TreeNode
{
	char data;
	struct TreeNode *left;
	struct TreeNode *right;
} TreeNode;

TreeNode *create_node(char data)
{
	TreeNode *node = malloc(sizeof(TreeNode));
	node->data = data;
	node->left = NULL;
	node->right = NULL;
	return node;
}

// 方式1
TreeNode *create_tree(const char **str)
{
	if ('#' == **str)
		return NULL;
	TreeNode *root = create_node(**str);
	*str += 1;
	root->left = create_tree(str);
	*str += 1;
	root->right = create_tree(str);
	return root;
}

// 方式二
TreeNode *pre_in_create_tree(char *pre, char *in, int len)
{
	if (len <= 0)
		return NULL;

	TreeNode *root = create_node(*pre);

	int pos = 0;
	while (*pre != in[pos])
		pos++;

	root->left = pre_in_create_tree(pre + 1, in, pos);
	root->right = pre_in_create_tree(pre + 1 + pos, in + 1 + pos, len - 1 - pos);
	return root;
}

// 方式二
TreeNode *post_in_create_tree(char *post, char *in, int len)
{
	if (len <= 0)
		return NULL;

	TreeNode *root = create_node(post[len - 1]);

	int pos = 0;
	while (post[len - 1] != in[pos])
		pos++;

	root->left = post_in_create_tree(post, in, pos);
	root->right = post_in_create_tree(post + pos, in + pos + 1, len - 1 - pos);
}

size_t height_tree(TreeNode *root)
{
	if (NULL == root)
		return 0;

	size_t lh = height_tree(root->left);
	size_t rh = height_tree(root->right);

	return lh > rh ? lh + 1 : rh + 1;
}

size_t density_tree(TreeNode *root)
{
	if (NULL == root)
		return 0;

	return 1 + density_tree(root->left) + density_tree(root->right);
}

void pre_order_tree(TreeNode *root)
{
	if (NULL == root)
		return;
	printf("%c ", root->data);
	pre_order_tree(root->left);
	pre_order_tree(root->right);
}

void in_order_tree(TreeNode *root)
{
	if (NULL == root)
		return;

	in_order_tree(root->left);
	printf("%c ", root->data);
	in_order_tree(root->right);
}

void post_order_tree(TreeNode *root)
{
	if (NULL == root)
		return;

	post_order_tree(root->left);
	post_order_tree(root->right);
	printf("%c ", root->data);
}

void level_tree(TreeNode *root)
{
	size_t density = density_tree(root), front = 0, rear = 0;
	TreeNode *queue[density];
	queue[rear++] = root;

	while (front != rear)
	{
		TreeNode *node = queue[front++];
		printf("%c ", node->data);
		if (NULL != node->left)
			queue[rear++] = node->left;
		if (NULL != node->right)
			queue[rear++] = node->right;
	}
}

int main(int argc, const char *argv[])
{
	const char *pre_str = "FCA##DB###EH##GM###";
	TreeNode *pre_root = create_tree(&pre_str);
	pre_order_tree(pre_root);
	printf("\n");

	/*
	char post[] = "ADCHGEF";
	char in[] = "ACDFHEG";
	TreeNode* root = post_in_create_tree(post,in,7);
	pre_order_tree(root);
	printf("%d\n",height_tree(root));
	printf("%d\n",density_tree(root));
	*/
	level_tree(pre_root);

	return 0;
}

搜索二叉树

又名有序二叉树,二叉查找树

左子树不空,左子树上所有结点的值小于根节点

右子树不空,右子树上所有结点的值大于根节点

优点

缺点

  • 二叉搜索树的元素添加的顺序会影响二叉搜索树的形状

    二叉搜索树的形状会影响他的操作效率

    在极端情况下二叉搜索树可能会成单支装分布,遍历速度接近于单项链表,这种情况出现的原因就是因为添加的元素基本有序

代码

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

typedef struct TreeNode
{
	int val;
	struct TreeNode* left;
	struct TreeNode* right;
	size_t height;
	size_t density;
}TreeNode;

TreeNode* create_node(int val)
{
	TreeNode* root = malloc(sizeof(TreeNode));
	root->val = val;
	root->left = NULL;
	root->right = NULL;
	root->height = 1;
	root->density = 1;
	return root;
}

void auto_tree(TreeNode* root)
{
	if(NULL == root)
		return;
	int lh = 0 , rh = 0 , ld = 0 , rd = 0;
	if(NULL != root->left)
	{
		auto_tree(root->left);
		lh = root->left->height;
		ld = root->left->density;
	}

	if(NULL != root->right)
	{
		auto_tree(root->right);
		rh = root->right->height;
		rd = root->right->density;
	}
	root->height = lh > rh ? lh+1 : rh+1;
	root->density = ld + rd + 1;
}

void _add_tree(TreeNode** root,TreeNode* node)
{
	if(NULL == *root)
	{
		*root = node;
		return;
	}

	if((*root)->val > node->val)
		_add_tree(&(*root)->left,node);
	else
		_add_tree(&(*root)->right,node);
	
	auto_tree(*root);
}

void add_tree(TreeNode** root,int val)
{
	_add_tree(root,create_node(val));	
}

TreeNode** _query_tree(TreeNode** root,int val)
{
	if(NULL == *root)
		return root;

	if(val == (*root)->val)
		return root;
	if(val <= (*root)->val)
		return _query_tree(&(*root)->left,val);
	else
		return _query_tree(&(*root)->right,val);

}

bool query_tree(TreeNode* root,int val)
{
	TreeNode** node = _query_tree(&root,val);
	return NULL != *node;
}

void _del_tree(TreeNode** root)
{
	TreeNode *temp = *root;
	if(NULL == temp->left)
		*root = temp->right;
	else if(NULL == temp->right)
		*root = temp->left;
	else	
	{
		*root = temp->left;
		_add_tree(root,temp->right);
	}
	free(temp);
}

bool del_tree(TreeNode** root,int val)
{
	TreeNode** node =_query_tree(root,val);
	if(NULL == *node)
		return false;

	_del_tree(node);
	auto_tree(*root);
	return true;
}

bool modify_tree(TreeNode** root,int old,int val)
{
	TreeNode** node = _query_tree(root,old);
	if(NULL == *node)
		return false;

	_del_tree(node);
	_add_tree(root,create_node(val));
}

void _in_order_tree(TreeNode* root)
{
	if(NULL == root)
		return;

	_in_order_tree(root->left);
	printf("%d ",root->val);
	_in_order_tree(root->right);
}

void in_order_tree(TreeNode* root)
{
	_in_order_tree(root);
	printf("\n");
}

void level_order_tree(TreeNode* root)
{
	if(NULL == root)
		return;

	TreeNode* queue[root->density];
	int front = 0 , rear = 0;
	queue[rear++] = root;

	while(front != rear)
	{
		int cnt = rear - front;
		while(cnt--)
		{
			TreeNode* node = queue[front++];
			printf("[v:%d h:%d d:%d] ",node->val,node->height,node->density);
			if(NULL != node->left)
				queue[rear++] = node->left;
			if(NULL != node->right)
				queue[rear++] = node->right;
		}
		printf("\n");
	}
}

// 访问第k个小的数,按大小值的顺序访问
void _assign_tree(TreeNode* root,int k,int* pos,int* ptr)
{
	if(NULL == root)
		return;

	_assign_tree(root->left,k,pos,ptr);
	if(++*pos == k)
		*ptr = root->val;
	_assign_tree(root->right,k,pos,ptr);
}

int assign_tree(TreeNode* root,int k)
{
	if(k < 1)
		return -1;

	int pos = 0 , val = -1;
	_assign_tree(root,k,&pos,&val);
	return val;
}

int main(int argc,const char* argv[])
{
	TreeNode* root = NULL;
	for(int i=0; i<10; i++)
	{
		add_tree(&root,rand()%100);
	}
	in_order_tree(root);
	modify_tree(&root,77,99);
	in_order_tree(root);
	level_order_tree(root);

	return 0;
}

平衡二叉树(AVL树)

首先是一棵二叉搜索树

二叉搜索树在极端情况下(插入的序列是有序的),二叉搜索树将退化成单链表,时间复杂度会退化为线性O(n)

可以通过随机化建立二叉搜索树来尽量避免这种情况,在多次删除时候,只将左或右子树前提,则会导致树往一遍沉。

会将树的平衡性破坏,提高他的操作时间复杂度。

优点:
  • 能够让二叉搜索树以最佳的状态进行操作 时间复杂度为O(log2n),避免了二叉搜索树的单支状分布
缺点:
  • 平衡二叉树在创建,添加,删除时,为了让二叉树保持平衡,需要进行大量的左旋,右旋,高度的计算,所以平衡二叉树在创建时,添加删除数据时,速度比较慢

    因此,平衡二叉树适合使用在数据量大,但数据量稳定没有大量的添加删除动作,大多数情况下都是在查询

不平衡的二叉树调整为二叉树

前提:检查二叉树是否平衡,从叶子结点向根节点遍历

不平衡二叉树的四种情况

          A           A          A            A
         /           /            \            \
        B           B              B            B
       /             \            /              \
      C               C          C                C
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define TreeHight(node) (NULL==(node)?0:(node)->height)

typedef struct TreeNode
{
	int val;
	struct TreeNode* left;
	struct TreeNode* right;
	size_t height;
}TreeNode;

// 创建结点
TreeNode* create_node(int val)
{
	TreeNode* root = malloc(sizeof(TreeNode));
	root->val = val;
	root->left = NULL;
	root->right = NULL;
	root->height = 1;
	return root;
}

// 调整树的高度
void auto_height_tree(TreeNode* root)
{
	if(NULL == root)
		return;
	
	auto_height_tree(root->left);
	auto_height_tree(root->right);
	int lh = TreeHight(root->left);
	int rh = TreeHight(root->right);
	root->height = lh > rh ? lh+1 : rh+1;
}

void right_rotate_tree(TreeNode** rpp)
{
	TreeNode* A = *rpp;
	TreeNode* B = A->left;
	TreeNode* t = B->right;

	*rpp = B;
	B->right = A;
	A->left = t;
}

void left_rotate_tree(TreeNode** rpp)
{
	TreeNode* A = *rpp;
	TreeNode* B = A->right;
	TreeNode* t = B->left;

	*rpp = B;
	B->left = A;
	A->right = t;
}

void auto_balance_tree(TreeNode** rpp)
{
	if(NULL == *rpp)
		return;

	auto_balance_tree(&(*rpp)->left);
	auto_balance_tree(&(*rpp)->right);

	int lh = TreeHight((*rpp)->left); 
	int rh = TreeHight((*rpp)->right);

	if(lh-rh > 1)
	{
		if(TreeHight((*rpp)->left->left) >= TreeHight((*rpp)->left->right))
			right_rotate_tree(rpp);
		else
		{
			left_rotate_tree(&(*rpp)->left);
			right_rotate_tree(rpp);
		}
		auto_balance_tree(rpp);
	}
	else if(lh-rh < -1)
	{
		if(TreeHight((*rpp)->right->right) >= TreeHight((*rpp)->right->left))
			left_rotate_tree(rpp);
		else
		{
			right_rotate_tree(&(*rpp)->right);
			left_rotate_tree(rpp);
		}
		auto_balance_tree(rpp);
	}
	else
		(*rpp)->height = lh > rh ? lh+1 : rh+1;
}

bool is_balance_tree(TreeNode* root)
{
	if(NULL == root)
		return true;

	bool flag1 = is_balance_tree(root->left);
	bool flag2 = is_balance_tree(root->right);

	return flag1 && flag2 && 1 >= abs(TreeHight(root->left)-TreeHight(root->right));
}


void _in_order_tree(TreeNode* root)
{
	if(NULL == root)
		return;

	_in_order_tree(root->left);
	printf("%d ",root->val);
	_in_order_tree(root->right);
}

void in_order_tree(TreeNode* root)
{
	_in_order_tree(root);
	printf("\n");
}

void level_order_tree(TreeNode* root)
{
	if(NULL == root)
		return;

	TreeNode* queue[100];
	int front = 0 , rear = 0;
	queue[rear++] = root;

	while(front != rear)
	{
		int cnt = rear - front;
		while(cnt--)
		{
			TreeNode* node = queue[front++];
			printf("[v:%d h:%d] ",node->val,node->height);
			if(NULL != node->left)
				queue[rear++] = node->left;
			if(NULL != node->right)
				queue[rear++] = node->right;
		}
		printf("\n");
	}
}

// 添加结点
void _add_tree(TreeNode** rpp,TreeNode* node)
{
	if(NULL == *rpp)
	{
		*rpp = node;
		return;
	}

	if(node->val < (*rpp)->val)
		_add_tree(&(*rpp)->left,node);
	else
		_add_tree(&(*rpp)->right,node);
}

// 添加元素
void add_tree(TreeNode** rpp,int val)
{
	// 创建结点并添加
	_add_tree(rpp,create_node(val));
	// 调整树的高度
	auto_height_tree(*rpp);
	// 让树自动平衡
	auto_balance_tree(rpp);
}

// 初始化二权树
TreeNode* init_tree(int* arr,int size)
{
	TreeNode* root = NULL;
	// 循环把所有的元素创建结点并添加到树中
	for(int i=0; i<size; i++)
	{
		_add_tree(&root,create_node(arr[i]));
	}

	// 调整树的高度
	auto_height_tree(root);
	// 让树自动平衡
	auto_balance_tree(&root);
	return root;
}

int main(int argc,const char* argv[])
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	TreeNode* root = NULL;
	for(int i=0; i<10; i++)
	{
		add_tree(&root,arr[i]);
	}
	printf("%d\n",is_balance_tree(root));
	return 0;
}

红黑树和AVL树的区别(面试)

  1. AVL树在物理上就是平衡的,所以在创建,添加,删除时速度比较慢,但他的查询速度接近平衡二叉树的极限
  2. 红黑树是一种特殊的AVL树,它的物理结构不是严格的平衡,而是接近平衡,从根节点所有的叶子结点的速度大致相同,它的非绝对平衡使用节约很多左旋,右旋的次数,因此它创建,添加,删除时速度比AVL树要快,查询速度接近AVL树
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值