数据结构学习第五章树和二叉树

第五章树和二叉树

1树的定义

1.1定义

在这里插入图片描述

1.2基本术语

在这里插入图片描述

1.3二叉树定义

在这里插入图片描述

特点:

①每个结点最多有两个孩子(二叉树中不存在度大于2的结点)。

②子树有左右之分,次序不能颠倒。

③二叉树可以是空集合,跟可以有空的左子树或者空的右子树。

:二叉树不是树的特殊情况,二叉树的子树要区分左子树和右子树,而树无需区分。

例子:

具有三个节点的二叉树有五种不同形态。

在这里插入图片描述

树有两种形态

在这里插入图片描述

1.4二叉树的5种形态

在这里插入图片描述

2.树的应用案例

【案例1】数据压缩问题

将数据文件转换成0、1组成的二进制串,称之为编码。这些编码可以用哈夫曼树来实现。

如:

在这里插入图片描述

【案例2】利用二叉树求解表达式的值

在这里插入图片描述

3.二叉树

3.1二叉树性质

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

性质3:利用边数推理得来。

在这里插入图片描述

性质4表明了完全二叉树节点数n与完全二叉树深度k之间的关系

在这里插入图片描述

性质5:表明了完全二叉树中双亲结点编号与孩子结点编号之间的关系

3.2满二叉树

定义:一棵二叉树拥有最多结点为满二叉树。

在这里插入图片描述

3.3完全二叉树

定义:深度为k的具有n个结点的二叉树,当且仅当每一个结点都与深度为k的满二叉树中编号为1~n的结点对应时,成为完全二叉树。

注:根据定义满二叉树是完全二叉树。

例:

在这里插入图片描述

在这里插入图片描述

4二叉树存储结构

在这里插入图片描述

4.1二叉树的顺序存储

实现描述:按满二叉树的结点层次编号,依次存放二叉树中数据元素。

在这里插入图片描述

【类型定义】

//二叉树顺序存储表示
#define MAXTSIZE 100
Typedef TElemType SqBiTree[MAXSTIZE];
SqBiTree bt;

例(不是满二叉树时):

在这里插入图片描述

顺序存储缺点:

在这里插入图片描述

特点:结点间关系蕴含在存储位置,适于满二叉树和完全二叉树。

4.2二叉树的链式存储

二叉树链式存储的结点,包含了两个后继:一个左孩子和一个右孩子,还有一个前驱:一个双亲。

在这里插入图片描述

1.二叉链表

存储方式需要经常操作后继元素,可以定义一个结点包含:左孩子(lchild),右孩子(rchild)和数据元素本身(data)。

在这里插入图片描述

【定义实现】

typdef struct BiNode{
    TElemType data;
    struct BiNode *lchild,*rchild;//左右孩子指针
}BiNode,*BiTree;

二叉树的链式存储示意图

在这里插入图片描述

由于每个结点都有两个指针域(左孩子跟右孩子),所以当有n个结点时,必定有2n个链域。这里除了根节点,其余结点都会有一个相应的链连接到双亲结点,所以一共有n-1条链 ,那么空指针的个数为 2n-(n-1) 个。

在n个结点的二叉树链表中,有 n+1 个空指针。

在这里插入图片描述

2.三叉链表

存储方式需要经常用到前驱元素,可以定义一个结点包含:左孩子(lchild),右孩子(rchild)和数据元素本身(data)以及父亲结点(parent)。

在这里插入图片描述

【定义】

typedef struct TriTree{
    TElemType data;
    struct TriNode *lchild,*rchild,*parent;
}TriNode,*TriTree;

5.遍历二叉树

  • 遍历:顺着某一条搜索路径“ 访问 ”结点,使得每个结点均被访问一次,而且仅被访问一次。

注:“ 访问 ”定义很广泛,可以是对接点的各种处理(如:输出接点信息,修改接点数据值等),但要求这种访问不破坏原来的数据结构。

  • 遍历可以得到树结点的一个线性排列。
  • 遍历用途—它是树结构插入、删除、修改、查找和排序运算的前提,是二叉树一切运算的基础和核心。

5.1遍历方法

二叉树每个结点可归结为三个组成部分:根节点、左子树、右子树

在这里插入图片描述

设:L—左子树,D—根结点,R—右子树。

则遍历整个二叉树方案:DLR、LDR、LRD、DRL、RDL、RLD。

若规定先左后右,则只有三种情况:DLR—根左右(先序遍历)、LDR—左根右(中序遍历)、LRD—左右根(后序遍历)。

注:根据根的遍历优先,分为先序遍历、中序遍历、后续遍历。

【先序遍历例子】

在这里插入图片描述

【中序遍历】

在这里插入图片描述

【后序遍历】

在这里插入图片描述

【例题】

在这里插入图片描述

先序:A B D G C E H F

中序:D G B A E H C F

后序:G D B H E F C A

【例题2】用二叉树表示算术表达式

在这里插入图片描述

先序—前缀表示(波兰式): - + a × b - c d / e f

中序—中缀表示:a + b × c - d - e / f

后序—后缀表示(逆波兰式):a b c d - × + e f / -

5.2遍历确定二叉树

  • 若二叉树中各结点的值均不相同,则二叉树结点的先序序列、中序序列和后序序列是唯一的
  • 由二叉树的“ 先序序列+中序序列 ” ,或者由二叉树的“ 后续序列+中序序列 “可以唯一确定一颗二叉树。

【例】已知二叉树的先序和中序序列,构造出相应的二叉树

​ 先序:A B C D E F G H I J

​ 中序:C D B F E A I H G J

【解析】

由于先序是根左右序列,中序是左根右序列。所以由上束可以推出,A为根节点,C D B F E为A的左子树全部元素, I H G J 为A的右子树全部元素。

在这里插入图片描述

接着推出 A左子树 CDBFE,根据先序与中序序列定义,可以推出B位根节点,CD为B的左子树全部元素,FE为B的右子树全部元素。

在这里插入图片描述

依照这个方法分别对每个元素团细分为根节点、左子树右子树,分解到每个结点只有一个元素时,结果如下

在这里插入图片描述

5.3遍历算法实现(递归)

1.先序遍历

【算法解析】

在这里插入图片描述

【算法实现】

//先序
Status PreOrderTraverse(BiTree T){
    if(T==NULL) returnOK;//为空二叉树
    else{
        visit(T);//访问根节点,例如输出根节点:printf("%d\t",t->data);
        PreOrderTraverse(T->lchild); //递归遍历左子树
        PreOrderTraverse(T->rchild); //递归遍历右子树
    }
}

【算法例子】

void pre(BiTree T){
    if(T=NULL)
        return OK;
    else{
        printf("%d\t",T->data);  //打印
        pre(T->lchild); //递归遍历左子树
        pre(T->rchild); //递归遍历右子树
    }
}

2.中序遍历

相比于先序遍历,中序遍历仅需调换访问根的次序到左子树右子树中间。

【算法实现】

//中序
Status InOrderTraverse(BiTree T){
    if(T==NULL) return OK;//已是空树
    else{
        InOrderTraverse(T->lchild);//递归遍历左子树
        visit(T);//访问根结点
        InOrderTraverse(T->rchild);//递归遍历右子树
    }
}

3.后序遍历

相比于先序遍历,中序遍历仅需调换访问根的次序到左子树右子树最后。

【算法实现】

//后序
Status PostOrderTraverse(BiTree T){
     if(T==NULL) return OK;//已是空树
    else{
        InOrderTraverse(T->lchild);//递归遍历左子树
        InOrderTraverse(T->rchild);//递归遍历右子树
        visit(T);//访问根结点
    }
}

4.遍历分析

在这里插入图片描述

每个结点只访问一次。时间复杂度:O(n)。

在递归过程中,未访问结点需要临时存放到栈,所以栈占用最大辅助空间为空间复杂度。空间复杂度:O(n)。

5.4遍历算法(非递归)

1.中序遍历非递归算法

【算法思想】

①建立一个栈

②根结点进栈,遍历左子树。

③根结点出栈,输出根节点,遍历右子树。

【算法实现】

Status InOrderTraverse(BiTree T){
    BiTree p;
    InitStack(S);
    p=T;
    while(p || !StackEmpty(S)){
        if(p){
            Push(S,p);
            p=p->lchild;
        }else{
            Pop(S,q);
            printf("%c" ,q->data);
            p=q->rchild;
        }
    }//while
    return OK;
}

5.5二叉树的层次遍历

层次遍历:从根节点开始,从上到下每一个深度顺序访问,每个深度按照从左到右访问每个结点。每个结点只访问一次。

在这里插入图片描述

【算法思想】

使用一个队列:

①将根结点进队。

②队不空时循环:从队列中出列一个结点*p,访问它;

—若它有左孩子结点,将左孩子结点进队;

—若它有右孩子结点,将右孩子结点进队。

【算法实现】

//队列定义
typedef struct{
    BTNode data[MaxSize];  //存放队中元素
    int front,rear;  //队头和队尾
}SqQueue; //顺序循环队列类型

//二叉树层次遍历算法
void LevelOrder(BTNode *b){
    BTNode *p;
    SqQueue *qu;  //定义一个队列
    InitQueue(qu);//初始化队列
    enQueue(qu,b); //根节点入队
    while(!QueueEmpty(qu)){//若队列不为空
        deQueue(qu,p); //出队结点p
        printf("%c",p->data); //访问p结点
        if(p->lchild !=NULL)  //先判断左结点是否为空
            enQueue(qu,p->lchild);
        if(p->rchild !=NULL) //再判断有结点是否为空
            enQueue(qu,p->rchild); 
    }
}

5.6二叉树遍历的应用

1.二叉树建立

【例】在这里插入图片描述

这里用“ # "充当空结点字符。则对上图先序遍历得到字符串:ABC##DE#G##F###

【算法实现】

Status CreateBiTree(BiTree &T){
    scanf(&ch); //键盘输出字符
    if(ch == "#") T==NULL;
    else{
        T=new BiTNode;
        if(!T){//获取空间失败
            exit(OVERFLOW);
        }
        T->data = ch;  //生成当前根结点
        CreateBiTree(T->lchild);//构造左子树
        CreateBiTree(T->rchild);//构造右子树
    }
    return OK;
}

2.复制二叉树

【算法描述】

①如果是空树,递归结束;

②否则,申请新结点,复制根结点

—先递归复制左子树

—再递归复制右子树

int Copy(BiTree T, BiTree &NewT){
    if(T==NULL){
        NewT = NULL;
        return 0; //结束
    }else{
        NewT=new BiTNode;
        NewT->data = T->data;//复制值
        Copy(T->lchild,NewT->lchild);//复制左子树
        Copy(T->rchild,NewT->rchild);//复制右子树
    }
}

3.计算二叉树深度

【算法描述】

①如果是空树,则深度为0;

②否则,递归计算左子树深度记录为m,递归计算右子树的深度记为n,取n与m较大者+1。

【算法实现】

int Depth(BiTree T){
    if(T=NULL)
        return 0;//如果树为空返回0
    else{
        m=Depth(T->lchild);
        n=Depth(T->rchild);
        if(m>n) return(m+1);
        else return(n+1);
    }
}

4.计算二叉树结点总个数

【算法描述】

①如果是空树,结点个数为0。

②否则,结点个数=左子树的结点个数+右子树的节点个数+1。

【算法实现】

int NodeCount(BiTree T){
    if(T ==NULL)
        return 0;
    else{
        return NodeCount(T->lchild)+NodeCount(T->rchild)+1;
    }
}

5.计算二叉树叶子结点总数

【算法描述】

①如果是空树,则叶子结点为0。

②否则,为左子树叶子结点个数+右子树叶子结点个数。

int LeadCount(BiTree T){
    if(T==NULL)
        return 0;
    if(T->lchild == NULL & T->rchild == NULL)
        return 1;
    else
        return LeafCount(T->lchild)+LeafCount(T->rchild);
}

6线索二叉树

  • 为了方便直接查找某个结点在“ 某种遍历序列 ”中的前驱和后继结点。将空结点利用起来成为线索二叉树,这种利用起来空结点的指针成为“ 线索 ”。加上线索的二叉树成为线索二叉树(Threaded Binary Tree)。

空结点利用:

①如果某个结点左孩子为空,则将空的左孩子指针改为指向其前驱。

②如果某个结点右孩子为空,则将空的右孩子指针改为指向其后继。

【例】

在这里插入图片描述

为了区分lchild和rchild指针是指向孩子指针,还是前驱或后继指针,对二叉链表中增设两个标志域,ltag和rtag,且约定:

—ltag=0:lchild指向该节点左孩子;ltag=1:lchild指向该节点前驱。

—rtag=0:rchild指向该节点右孩子;rtag=1:rchild指向该节点后继。

节点结构如图:

在这里插入图片描述

【定义实现】

typedef struct BiThrNode{
    int data;
    int ltag,rtag;
    struct BiThrNode *lchild,*rchild;
}BiThrNode,*BiThrTree;

如先序线索二叉树:

在这里插入图片描述

【练习】

在这里插入图片描述

由于这里H的前驱与G的后继为空,这里增设了一个头结点:ltag=0,lchild指向根节点;rtag=1,rchild指向遍历序列最后一个结点。并且遍历中第一个结点的lchild和最后一个节点的rchild都指向头结点。

在这里插入图片描述

7.树和森林的存储结构

在这里插入图片描述

7.1双亲表示法

【定义】结构数组,存放树的结点,每个结点两个域:

—数据域:存放结点本身信息。

—双亲域:指示本结点的双亲结点在数组中的位置。

【例子】

一棵树在这里插入图片描述
数据存储:在这里插入图片描述

【特点】找双亲容易,找孩子难。

【实现】

//结点结构
typedef struct PTNode{
    TElemType data;  //数据域
    int parent;      //双亲域
}PTNode;

//树结构
#defind MAX_TREE_SIZE 100 
typedef struct{
    PTNode nodes[MAX_TREE_SIZE];
    int r,n;//记录根结点位置和结点个数
}PTree;

7.2孩子链表

把每个结点的孩子结点排列起来,看成一个线性表,用单链表存储。则n个结点有n个孩子链表。n个头指针又组成一个线性表,用顺序表存储。

【例】

在这里插入图片描述

【实现】

//孩子节点
typedef struct CTNode{
    int child;
    struct CTNode *next;
}*ChildPtr;

//双亲节点
typedef struct{
    TElemType data;
    ChildPtr firstchild;  //孩子链表头指针
}CTBox;

//树结构
typedef struct{
    CTBox nodes[MAX_TREE_SIZE];
    int n,r; //结点树与结点位置
}CTree;

【特点】找孩子容易,找双亲难。可以在双亲结点中再增加一个成员,指向这个结点的双亲。

【示意图】带双亲的孩子链表

在这里插入图片描述

7.3孩子兄弟(二叉链表表示法)

【定义】用二叉链表作树的存储结构,链表中每个结点的两个指针域分别指向第一个孩子结点和下一个兄弟结点

typedef struct CSNode{
    ElemType data;
    struct CSNode *firstchild,*nextsibling;
}CSNode,*CSTree;

【例子】

在这里插入图片描述

8.树和二叉树的转换

  • 将树转化成为二叉树进行处理,利用二叉树的算法来实现对树的操作。
  • 由于树和二叉树都可以用二叉链表(孩子兄弟表示法)作为存储结构,则以二叉链表作为媒介可以导出树和二叉树之间的对应关系。

在这里插入图片描述

8.1树转二叉树

①加线:在兄弟之间加一连线。

②抹线:对每个结点,除了其左孩子外,去除其余孩子之间的关系。

③旋转:以树根结点为轴心,整树顺时针转45°。

【例】

在这里插入图片描述

8.2二叉树转树

①加线:若p结点是某个双亲结点的左孩子,则将p结点的右子树的右子孙都与p的双亲结点链接起来。

②抹线:抹掉原二叉树中双亲域右孩子之间的连线。

③调整:将结点按层次排列,形成树结构。

【例】

在这里插入图片描述

在这里插入图片描述

8.3森林转二叉树

①将各棵树分别转换成二叉树

②将每棵树的根结点用线相连

③以第一棵树根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型结构。

在这里插入图片描述

8.4二叉树转森林

①抹线:将二叉树中根结点与其右孩子连线,以及沿右分支搜索到的所有右孩子间连线全部抹掉,是之变成孤立的二叉树。

②还原:将孤立的二叉树还原成树。

在这里插入图片描述

9.树的遍历

  • 树的遍历:

—先根:若树不为空,则先访问根结点,然后依次先根遍历各棵子树。

—后根:若树不为空,则先依次后根遍历各棵子树,然后访问根结点。

—按层次遍历:若树不为空,则自上而下自左自右访问树中每个结点。

【例】

在这里插入图片描述

  • 森林的遍历:

将森林看成三个部分构成:第一棵树的根结点;第一棵树子树森林;其他树构成的森林。

—先序:先访问森林第一棵树的结点,先序遍历森林中第一棵树的子树森林,先序遍历其他树构成森林。

—中序:中序遍历森林中第一棵树的子树森林,再访问森林第一棵树的结点,中序遍历其他树构成森林。

【例子】

在这里插入图片描述

10.哈夫曼树

  • 有一个判别树来判别成绩的等级。

在这里插入图片描述

当每次输入量很大时,若学生成绩数据共10000个,比较总次数为10000×(1×5%+2×15%+3×40%+4×10%)=31500次。

若将判别树进行修改:

在这里插入图片描述

此时比较总次数为22000次。

综上,不同判别树效率是不一样的。而效率最高的判别树称为" 哈夫曼树 “,也称” 最优二叉树 "。

  • 路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径。
  • 结点的路径长度:两结点间的路径上的分支树。

【例】

在这里插入图片描述

  • 树的路径长度:从树根到每一个结点的路径长度之和,记作:TL

在这里插入图片描述

二叉树中若结点数目相同,完全二叉树是路径长度最短的二叉树。

  • 权(weight):将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。
  • 结点的带权路径长度:从根结点到该结点之间的路径长度与该结点权的乘积。
  • 树的带权路径长度:树中所有叶子结点的带权路径长度之和。在这里插入图片描述

【例】有4个结点a,b,c,d,权值分别为7,5,2,4,构造以此4个结点为叶子结点的二叉树。

在这里插入图片描述

则带权路径长度:WPL=7×2+5×2+2×2+4×2=36。

在这里插入图片描述

则带权路径长度:WPL=4×2+7×3+5×3+2×1=46。

10.1哈夫曼树

最优树:即带权路径长度(WPL)最短的树。

注:“ 带权路径长度最短 ” 是在 “ 度相同 ”的树中比较而得出的,因此优最优二叉树、最优三叉树等。

哈夫曼树:最优二叉树,即带权路径长度(WPL)最短的二叉树。

【例】

在这里插入图片描述

满二叉树不一定是哈夫曼树。具有相同带权结点的哈夫曼树不唯一。

10.2构造哈夫曼树

【特点】哈夫曼树中权值越大的叶子离根越近。

贪心算法:构造哈夫曼树首先选择权值小的叶子结点。

【思路】

①根据给定的权值,将每个结点单独称为一个森林。

②选用最小的两个结点组成新树,删除这两棵树,同时二叉树加入森林中。

③重复②,直至森林中只有一棵树为止,这棵树称为哈夫曼树。

【例】有4个结点a ,b ,c , d,权值分别为7 ,5 , 2 ,4,构造哈夫曼树

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

哈夫曼树的结点只有度为0或2,没有度为1的结点。

包含n个叶子结点的哈夫曼树中共有2n-1个结,点。

【例】有5个结点a ,b ,c ,d ,e,权值分别为7,5,5,2,4,构造哈夫曼树

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10.3哈夫曼树构造算法实现

1.结点类型定义

typedef struct{
    int weight;
    int parent,lch,rch;
}HTNode,*HuffmanTree;

2.构造算法

【示意图】

在这里插入图片描述

【实现】

void CreatHuffmanTree(HuffmanTree HT,int n){//构造哈夫曼树
    if(n<=1) return;
    m=2*n-1;  //一共2n-1个结点
    HT=new HTNode[m+1]; //由于0单元未用,所以需要+1。HT[m]表示根结点
    for(i=1;i<=m;++i){//初始化表
        HT[i].lch=0;
        HT[i].rch=0;
        HT[i].parrnt=0;
    }
    for(i=1;i<=n;++i)
        scanf("%c",&HT[i].weight);//输出前n个元素的权值
    //初始化结束,开始建立哈夫曼树
    for(i=n+1;i<=m;i++){
        Select(HT,i-1,s1,s2);//在HT中选择两个双亲域为0,且权值最小的结点,并返回他们在HT中的序号s1和s2。
        HT[s1].parent=i;//将两个最小结点结合,双亲结点指向当前i结点。即删除s1,s2组成新树
        HT[s2].parent=i;
        HT[i].lch=s1;//双亲结点的左孩子右孩子设置
        HT[i].rch=s2;
        HT[i].weight=HT[s1].weight+HT[s2].weight;//i的权值为左右孩子权值之和
    }
}

10.4哈夫曼编码

  • 在远程通讯中,要将待传字符转换成由二进制的字符串:

例如:设置等长编码A—00,B—01,C—10,D—11。

则:ABACCDA—00 01 00 10 10 11 00

倘若将编码设计长度不等的二进制编码,即让待传送字符串中出现次数较多的字符采用尽可能短的编码,则转换的二进制字符串便可能减少。

例如:设置不等长编码A—0,B—00,C—1,D—01。

则:ABACCDA—0 00 0 1 1 01 0 。这里会发生重码,导致从编码转换为字符时发生错误。

:所以在设计长度不等的编码时,必须使任一字符编码都不是另一个字符的编码的前缀。

  • 使用哈夫曼编码可以避免这个问题:

(1)统计字符集中每个字符在电文中出现的平均概率。

(2)利用哈夫曼树的特点:权越大的叶子离根越近,将每个字符的概率值作为权值,构成哈夫曼树。则概率越大的结点,路径越短,编码越短。

(3)把哈夫曼树的每个分支标上0或1:结点左分支标0,右分支标1。把从根到每个叶子的路径上的标号连接起来,作为该叶子代表的字符编码。

【例】要传输的字符集D={C , A , S , T , ; }其对应的出现频率w={2 , 4 , 2 , 3 , 3}

构造哈夫曼树并标左右分支后得

在这里插入图片描述

则T—00,; —01,A—10,C—110,S—111。这些为哈夫曼编码。

例如电文为CAS;CAT;SAT;AT,其编码为110 10 111 01 110 10 00 01 111 10 00 01 10 00

反之编码为1101000,得出CAT
s1].weight+HT[s2].weight;//i的权值为左右孩子权值之和
}
}


### 10.4哈夫曼编码

* 在远程通讯中,要将待传字符转换成由二进制的字符串:

例如:设置等长编码A—00,B—01,C—10,D—11。

则:ABACCDA—00 01 00 10 10 11 00

倘若将编码设计长度不等的二进制编码,即让待传送字符串中出现次数较多的字符采用尽可能短的编码,则转换的二进制字符串便可能减少。

例如:设置不等长编码A—0,B—00,C—1,D—01。

则:ABACCDA—0 00 0 1 1 01 0 。这里会发生重码,导致从编码转换为字符时发生错误。

**注**:所以在设计长度不等的编码时,必须使任一字符编码都不是另一个字符的编码的前缀。

* 使用哈夫曼编码可以避免这个问题:

(1)统计字符集中每个字符在电文中出现的平均概率。

(2)利用哈夫曼树的特点:权越大的叶子离根越近,将每个字符的概率值作为权值,构成哈夫曼树。则概率越大的结点,路径越短,编码越短。

(3)把哈夫曼树的每个分支标上0或1:结点左分支标0,右分支标1。把从根到每个叶子的路径上的标号连接起来,作为该叶子代表的字符编码。

【例】要传输的字符集D={C , A , S , T , ; }其对应的出现频率w={2 , 4 , 2 , 3 , 3}

构造哈夫曼树并标左右分支后得

<img src="F:\桌面文件\编程学习\img\image-20230721174230263.png" alt="image-20230721174230263" style="zoom:67%;" />

则T—00,; —01,A—10,C—110,S—111。这些为哈夫曼编码。

例如电文为CAS;CAT;SAT;AT,其编码为110 10 111 01 110 10 00 01 111 10 00 01 10 00

反之编码为1101000,得出CAT

学习视频:数据结构——王卓
参考文献:数据机构C语言版第2班——严蔚敏

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值