数据结构学习 --5 树和二叉树

数据结构学习 --1 绪论
数据结构学习 --2 线性表
数据结构学习 --3 栈,队列和数组
数据结构学习 --4 串
数据结构学习 --5 树和二叉树
数据结构学习 --6 图
数据结构学习 --7 查找
数据结构学习 --8 排序

本人学习记录使用 希望对大家帮助 不当之处希望大家帮忙纠正

数据结构学习 --5 树和二叉树



前言

提示:这里可以添加本文要记录的大概内容:

例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


提示:以下是本篇文章正文内容,下面案例可供参考
在这里插入图片描述

5.1 树的基本概念

5.1.1 树的定义

树是 n(n>=0)个结点的有限集合。当n=0时,称为空树。在任意一颗树非空树中应该满足:
1 有且仅有一个特定的称为根的结点
2 当n>1 时,其余结点为m(m > 0)个互不相交的有限集合t1, t2 …tm,其中每个集合本身又是一颗树,并且称为根的子树。
显然,树的定义是递归的,即在树的定义中又用到了其自身,树是一种递归的数据结构。树作为一种逻辑结构,同时也是一种分层结构,具有以下两个特点。
1.树的根结点没有前驱,除根结点外的所有结点有且只有一个前驱。
2.树中所有结点都可以有零个或多个后继。
树合适于表示具有层次结构的数据。树中的某个结点(除根结点外)最多只和上一层的一个结点(即其父结点)有直接关系,根结点没有直接上层结点,因此在n个结点的树有n-1条边。而书中每个结点与其下一层的零个或多个结点(即其子女结点)都有直接关系。

5.1.2 基本术语 在这里插入图片描述

  1. 考虑结点K。根A 到结点K 的唯一路径上的任结点,称为结点K的祖先。如结点 B 是结点K的祖先,而结点K 是结点 B 的子孙。路径上最接近结点K 的结点E称为K的双亲,而K为结点E的孩子。根A 是树中唯一没有双亲的结点。有相同双亲的结点称为兄弟,如结点K和结点L有相同的双亲 E,即K和L为兄弟。

  2. 树中一个结点的孩子个数称为该结点的,树中结点的最大度数称为树的度。如结点 B的度为2,结点D的度为3,的度为3

  3. 度大于0结点称为分支结点的结点(又称非终端结点),度为0(没有子女结点)的结点称为叶结点(又称终端结点)。在分支结点中,每个结点的分支数就是该结点的度。

  4. 结点的深度、高度和层次。
    结点的层次从树根开始定义,根结点为第 1 层,它的子结点为第 2 层,以此类推。双亲在同一层的结点互为堂兄弟,图中结点G与EFHIJ互为堂兄弟
    结点的深度是从根结点开始自顶向下逐层累加的。
    结点的高度是从叶结点开始自底向上逐层累加的。
    树的高度《或深度)是树中结点的最大层数。图中的高度为4

  5. 有序树和无序树
    树中结点的各子从左到右是有次序的,不能换,称该树为有序树,否则称为无序。

  6. 路径和路径长度。树中两个结点之间的路径是由这两个结点之间所经过的结点序列构成的,而路径长度是路径上所经过的边的个数。

  7. 森林。森林是 m(m>=0)颗互不相交的树的集合。森林的概念于树的概念十分相近,因为只要把树的根结点删去就成了森林。反之,只要给 m 棵独立的树加上一个结点,并把这 m 棵树作为该结点的子树,则森林就变成了树。

5.1.3 树的性质

树具有如下最基本的性质:

5.2 二叉树的概念

5.2.1 二叉树的定义及其主要特征

1.二叉树的定义
二叉树是一种特殊的树形结构,其特点是每个结点至多只有两颗子树(即二叉树中不存在度大于 2的结点),并且二叉树的子树有左右之分,其次不能任意倒。
与数相似,二叉树也以递归的形式定义。二叉树是n(n≥0)个结点的有限集合:
(1)或者为空二叉树,即n=0。
(2)或者由一个根结点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一颗二叉树
在这里插入图片描述
二叉树与度为2 的有序树的区别:
(1)度为2的树至少有3个结点,而二叉树可以为空
(2)度为2的有序数的孩子的左右次序是对于另一个孩子而言的,若某个结点只有一个孩子,则这个孩子就无序区分其左右次序,而二叉树无论其孩子是否为2,均需确定其左右次序,即二叉树的结点次序不是相对于另一个结点而言的,而是确定的。

  1. 几个特殊的二叉树
    (1)满二叉树。一颗高度为h,且含有二的h次方减一个结点的二叉树称为满二叉树,即树中的每层都含有最多的结点满二叉树的结点都集中在二叉树的最下一层,并且除叶节点之外的每个结点度数均为2.
    可以对满二叉树按层序编号:
    约定编号从根结点(根结点编号为1)起,自上而下,自左向右。这样,每个结点对应一个编号,对于编号为i的结点,若有双亲,则其双亲为 i/2 若有左孩子,则左孩子为 2i 若有右孩子,则孩子为 2i+1。
    (2)完全二叉树。高度为h、有n个节点的二叉树,当且仅当其每个结点都与高度为h的满二叉树中编号为1~n的结点一一对应时,称为完全二叉树
    (2.1)若 i < n/2,则结点i为分支结点,否则为叶结点。
    (2.2)叶结点只可能在层次最大的两层上出现。对于最大层次中的叶结点,都依次排列在该层最左边的位置上。
    (2.3)若有度为1的结点,则只可能有一个,且该结点只有左孩子而无右孩子(重要特征)
    (2.4)按层序编号后,一旦出现某结点(编号为i)为叶结点或只有左孩子,则编号大于i 的结点均为叶结点。
    (2.5)若n为奇数,则每个分支结点都有左孩子和右孩子 ,若n为偶数,则编号最大的分支结点(编号为 n/2)只有左孩子,没有右孩子,其余分支结点左、右孩子都有。
    (3)二叉排序树。左子树所有结点的关键字均小于根结点的关键字:右子树上的所有结点的关键字均大于根结点点关键字;左右子树各是一个二叉排序树。
    (4)平衡二叉树 树上任意一个结点的左子树和右子树的深度之差不超过1.

  2. 二叉树的性质
    在这里插入图片描述

5.2.2 二叉树的存储结构

1.顺序存储结构
二树的序存是指用一组地址连续的存储单元次自上而下自左至存完全二叉树上的结点元素,即将完全二叉树上编号为i的结点元存储在一维数组下标为i-1的分中
依据二叉树的性质,完全二叉树和满二叉树采用顺序存储比较合适,树中结点的序号可以唯一地反映结点之间的逻辑关系,这样既能最大可能地节省存储空间,又能利用数组元素的下标值确定结点在二叉树中的位置,以及结点之间的关系。
在这里插入图片描述
2.链式存储结构
由于顺序存储的空间利用率较低,因此二叉树一般都采取链式存储结构,用链表结点来存储二叉树中的每个结点。
在这里插入图片描述
二叉树的链式存储结构描述如下:

typedef struct BiTNode {
	ElemType data; //数据域
	struct BiTNode *lchild,*rchild; //左、右孩子指针
}BiTNode,*BiTree;

使用不同的存储结构时,实现二叉树操作的算法也会不同,因此要根据实际应用场合(二叉树的形态和需要进行的运算)来选择合适的存储结构。
容易验证,在含有n个结点的二叉链表中,含有 n +1个空链域(重要结论,经常出现在选择题中)。

5.3 二叉树的遍历和线索二叉树

5.3.1 二叉树的遍历

二叉树的遍历是指按某条搜索路径访问树中每个结点,使得每个结点均被访问一次,而且仅被访问一次。
由于二叉树是一种非线性结构,每个结点都可能有两棵子树,因而需要寻找一种规律,以便使二叉树上的结点能排列在一个线性队列上,进而便于遍历。
由二叉树的递归定义可知,遍历一棵二叉树便要决定对根结点N、左子树L和右子树R的访问顺序。按照先遍历左子树再遍历右子树的原则,常见的遍历次序有先序 (NLR)、中序(LNR)和后序(LRN)三种遍历算法,其中“序”指的是根结点在何时被访问。

  1. 先序遍历
    操作如下
    若二叉树为空,则什么也不做
    (1)访问根节点;
    (2)先序遍历左子树;
    (3)先序遍历右子树;
    算法如下
void PreOrder (BiTree T){
	if(T!=NULL){
		visit(T);   //访问根结点
		PreOrder(T->lchild); //递归遍历左子树
		PreOrder(T->rchild);  //递归遍历右子树
	}
}
  1. 中序遍历
    若二叉树为空,则什么也不做;否则,
    (1)中序遍历左子树;
    (2)访问根节点;
    (3)中序遍历右子树;
void InOrder(BiTree T){
	if(T!=NULL){
		InOrder(T->lchild); //递归遍历左子树
		visit(T);   //访问根结点
		InOrder(T->rchild);  //递归遍历右子树
	}
}
  1. 后续遍历
    若二叉树为空,则什么也不做;否则,
    (1)后续遍历左子树;
    (2)后续遍历右子树;
    (3) 访问根节点;
void PostOrder(BiTree T){
	if(T!=NULL){
		PostOrder(T->lchild); //递归遍历左子树
		PostOrder(T->rchild);  //递归遍历右子树
		visit(T);   //访问根结点
	}
}

三种遍历算法中,递归遍历左、右子树的顺序都是固定的,只是访问根结点的顺序不同。不管采用哪种遍历算法,每个结点都访问一次且仅访问一次,故时间复杂度都是 O()。在递归遍历中,递归工作栈的栈深恰好为树的深度,所以在最坏情况下,二叉树是有n个结点且深度为 的单支树,遍历算法的空间复杂度为 O(n)。

  1. 递归算法和非递归算法的转换
    在上节介绍的三种遍历算法中,暂时抹去和递归无关的 visit()语句,则3个遍历算法完全相同,因此,从递归执行过程的角度看先序、中序和后序遍历也是完全相同的。
    在这里插入图片描述
    中序遍历的非递归算法
void InOrder2(BiTree T){
	InitStack(S);BiTree p = T;//初始化栈s;p是遍历指针
	while(p || !IsEmpty(S)){ //栈空不循环
		if(p){        //一路向左
			Push(s,p);// 当前结点入栈
			p= p->lchild;// 左孩子不空,一直向左走
		}
		else{//出站,并转向出栈的右子树
			Pop(S,p); //栈顶元素出栈,访问出栈结点
			visit(p);
			p =p->rchild; //向右子树走,p赋值为当前结点的右孩子
		
		}
	}
}

先序遍历

void ProOrder2(BiTree T){
	InitStack(S);BiTree p = T;//初始化栈s;p是遍历指针
	while(p || !IsEmpty(S)){ //栈空不循环
		if(p){        //一路向左
			visit(p);Push(s,p);// 访问当前结点入栈
			p= p->lchild;// 左孩子不空,一直向左走
		}
		else{//出站,并转向出栈的右子树
			Pop(S,p); //栈顶元素出栈,访问出栈结点
			p =p->rchild; //向右子树走,p赋值为当前结点的右孩子
		}
	}
}

后续遍历

void PostOrder2(BiTree T){
	InitStack(S);
	BiTree *p = T;
	BiTree *r = NULL;
	while(p || !IsEmpty(S)){ //栈空不循环
		if(p){        //走到最左边
			Push(s,p);// 访问当前结点入栈
			p= p->lchild;// 左孩子不空,一直向左走
		}
		else{//向右
			GetTop(S,p); //读栈顶结点(非出栈)
			if(p->lchild && p->lchild !=r )//若右子树存在,且未被访问过
				p=p->rchild;//转向右
			else{
				Pop(S,p); //将结点弹出
				visit(p->data); //访问该结点
				r=p; //记录最近访问过的结点
				p=NULL;//结点访问完后,重置p指针
			}
		}
	}
}
  1. 层次遍历
    要进行层次遍历,需要借助一个队列。首先将二叉树根结点入队,然后出队,访问出队结点,若它有左子树,则将左子树根结点入队;若它有右子树,则将右子树根结点入队。完成入队后出队,访问出队结点 如此反复 直到队列为空
    算法如下
void levelOrder(BiTree T){
	InitQueue(Q); //初始化辅助队列
	BiTree p;
	EnQueue(Q,T);  //将根结点入队
	while(!IsEmpty(Q)){ //队列空不循环
		EnQueue(Q,p);  //队头结点出队
		visit(p);  		//访问出队结点
		if(p->lchild !=NULL){   
			EnQueue(Q,p->lchild); //左子树不空,则左子树根结点入队
		}
		if(p->rchild!=NULL){   
			EnQueue(Q,p->rchild); //右子树不空,则右子树根结点入队
		}
	}
}
  1. 由遍历序列构造二叉树

由二叉树的先序序列和中序序列可以唯一地确定一棵二叉树。
在先序遍历序列中,第一个结点一定是二叉树的根结点:而在中序遍历中,根结点必然将中序序列分割成两个子序列,前一个子序列是根结点的左子树的中序序列,后一个子列是根结点的右子树的中序序列。根据这两个子序列,在先序序列中找到对应的左子序列和右子序列。在先序序列中,左子序列的第一个结点是左子树的根点,右子列的第一个结点是右子的根结点如此递归地进行下去,便能唯一地确定这棵二叉树。

同理,由二叉树的后序序列和中序序列也可以唯一地确定一棵二叉树。
因为后序序列的最后一个结点就如同先序序列的第一个结点,可以将中序序列分制成两个子序列,然后采用类似的方法递归地进行划分,进而得到一棵二叉树。
由二叉树的层序序列和中序序列也可以唯一地确定一棵二叉树,实现方法留给读者思考。需要注意的是,若只知道二叉树的先序序列和后序序列,则无法唯一确定一棵二叉树。
例如,求先序序列(ABCDEFGHI)和中序序列(BCAEDGHFI)所确定的二叉树首先,由先序序列可知A
为二叉树的根结点。中序序列中A之前的BC为左子树的中序序列EDGHFI为右子树的中序序列。然后,由先序序列可知B是左子树的根结点,D是右子树的根结点。

5.3.2 线索二叉树

  1. 线索二叉树的基本概念
    线索二叉树的存储结构描述如下:
typedef struct ThreadNodel{
	ElemType data; //数据元素
	struct ThreadNode *lchild,*rchild; //左、右孩子指针
	int Itag,rtag; //左、右线索标志
}ThreadNode*ThreadTree;

以这种结点结构构成的二叉链表作为二叉树的存储结构,称为线索链表,其中指向结点前驱和后继的指针称为线索。加上线索的二叉树称为线索二叉树。

  1. 中序线索二叉树的改造

二叉树的线索化是将二叉链表中的指针改为指向前驱或后驱的线索而前驱或者后驱的信息只有在遍历时才能得到,因此线索化的实质就是遍历一次二叉树。

void InThread(ThreadTree &p,ThreadTree &pre){
	if(p !=NULL){
		InThread(P->Lchild,pre);//递归,线索化左子树
		if(p->lchild == NULL){ //左子树为空建立前驱线索
			p->lchild = pre;
			p->ltag = l;
		}
		if(pre != NULL && pre->rchild == NULL){ 
			pre ->rchild = p; //建立前驱结点的后继线索
			pre ->rtag = l;
		}
		pre =p;//标记当前结点成为刚刚访问过的结点
		InThread(p->rchild,pre);//递归,线索化右子树
	}
}

通过建立中序线索二叉树的主要过程算法如下:

void CreateInThread(ThreadTree T){
	ThreadTree pre =NULL;
	if(T !=NULL){ //非空二叉树,线索化
		InThread(pre,T);//线索化 二叉树
		pre->rchild = NULL;  //处理遍历最后一个结点
		pre->rtag = 1;
	}
}
  1. 中序线索二叉树的遍历
    中序线索二叉树的结点中隐含了线索二叉树的前驱和后继信息。在对其进行遍历时,只要先找到序列中的第一个结点,然后依次找结点的后继,直至其后继为空。在中序线索二叉树中找结点后继的规律是:若其右标志为“1”,则右链为线索,指示其后继,否则遍历右子树中第一个访问的结点(右子树中最左下的结点)为其后继。不含头结点的线索二叉树的追历算法如下。

(1)求中序线索二叉树中中序序列下的第一个结点:

ThreadNode *Firstnode(ThreadNode*p){
	fwhile(p->ltag==0) p=p->lchild;//最左下结点(不一定是叶结点)
	return p;
}

(2)求中序线索二叉树中结点 p 在中序序列下的后继:

ThreadNode *Nextnode(ThreadNode *p){
	!if(p->rtag==0) return Firstnode(p->rchild);
	else return p->rchild;//rtage=1  直接返回后继线索
}

3)利用上面两个算法,可以写出不含头结点的中序线索二叉树的中序遍历的算法

void Inorder(ThreadNode *T){
	for(ThreadNode *puFirstnode(T);p!=NULL;p=Nextnode(p))
	visit(p);
}
  1. 先序线索二叉树和后续线索二叉树
    在后序线索二叉树中找结点的后继较为复杂,可分三种况:D结点是的,则其后继为空;@若结点是其双亲的右孩子,或是其双亲的左孩子且其双亲没有右子树,则其后继即为双亲:3若结点x是其双亲的左孩子,且其双亲有右子树,则其后为双亲的右子树上按后序遍历列出的第一个结点。找结点B 的后继无法通过链域找到,可见在后序线索二叉树上找后继时需知道结点双亲,即需采用带标志域的三叉链表作为存储结构。

5.4 树、深林

5.4.1 树的存储结构

树的存储方式有多种,既可以采取顺序存储结构,又可采用链式存储结构,但无论采取何种存储方式,都要求能唯一地反应树中各结点之间的逻辑关系

  1. 双亲表示法

在这里插入图片描述

#define MAX TREE SIZE 100 //树中最多结点数
typedef struct { //树的结点定义
	ElemType data; //数据元素
	int parent; //双亲位置域
}PTNode;
typedef struct IPTNode{ //树的类型定义
 nodes[MAX TREE SIZE]; //双亲表示
 int n; //结点数
}iPTree; 

该存储结构利用了每个结点(根结点除外),可以很快地得到每个结点的双亲结点,但求结点的孩子时则需要遍历整个结构。

  1. 孩子表示法
    孩子表示法是将每个节点的孩子结点都用单链表链接起来形成一个线性结构,此时n个结点就有n个孩子链表
  2. 孩子兄弟表示法
    孩子兄弟表示法又称二叉树表示法,即以二叉链表作为树的存储结构。孩子兄弟表示法使每个结点包括三部分内容:结点值、指向结点第一个孩子结点的指针,以及指向结点下一个兄弟结点的指针

5.4.2 树、森林与二叉树的转换

由于二叉树和树都可以用二叉链表作为存储结构,因此以二叉链表作为媒介可以导出树与二叉树的一个对应关系,即给定一棵树,可以找到唯一的一棵二叉树与之对应。从物理结构上看,它们的二叉链表是相同的,只是解释不同而已。

森林转换成二叉树的画法:
(1)将森林中的每树转换成相应的二叉树:
(2)每棵树的根也可视为兄弟关系,在每棵树的根之间加一根连线:
(3)以第一棵树的根为轴心顺时针旋转 45°。二叉树转换为森林的规则:若二叉树非空,则二叉树的根及其左子树为第一棵树的二叉树形式,故将根的右链断开。二叉树根的右子树又可视为一个由除第一棵树外的森林转换后的二叉树应用同样的方法,直到最后只剩一棵没有右子树的二叉树为止,最后再将每棵二叉树依次转换成树,就得到了森林。二又树转换为树或森林是唯一的。

5.4.3 树和深林的遍历
树的遍历是指用某种方式访问树中的每个结点,且仅访问一次。主要有两种方式
(1)先根遍历。若树非空,先访问根结点,再依次遍历根结点的每棵子树,遍历子树时仍遵循先根后子树的规则。其遍历序列与这棵树相应二叉树的先序序列相同。
(2)后根遍历。若树非空,先依次遍历根结点的每棵子树,再访问根结点,遍历子树时仍遵循先子树后根的规则。其遍历序列与这棵树相应二叉树的中序序列相同。

树也有层次遍历,与二叉树的层次遍历思想基本相同,即按层序依次访问各结点按照森林和树相互递归的定义,可得到森林的两种遍历方法
(1)先序遍历森林。若森林为非空,则按如下规则进行遍历:
。访问森林中第一棵树的根结点。
。先序遍历第一棵树中根结点的子树森林。
。先序遍历除去第一棵树之后剩余的树构成的森林。
(2)中序遍历森林。森林为非空时,按如下规则进行通历:
。中序遍历森林中第一棵树的根结点的子树森林。
。访问第一棵树的根结点。
。中序遍历除去第一棵树之后剩余的树构成的森林。
在这里插入图片描述

5.5 树与二叉树的应用

5.5.1 哈夫曼和哈夫曼编码

  1. 哈夫曼树的定义
    在许多应用中,树中结点常常被赋予一个表示某种意义的数值,称为该结点的权。从树的根到任意结点的路径长度(经过的边数)与该结点上权值的乘积,称为该结点的带权路径长度。树中所有叶结点的带权路径长度之和称为该树的带权路径长度
    在这里插入图片描述
    wi;是第i个叶结点所带的权值,li是该叶结点到根结点的路径长度。

  2. 哈夫曼树的构造
    给定n个值分别为 w1,w2,…wn的结点,哈的算法描述如下:
    (1)将这n 个结点分别作为 n 仅含一个结点的二,成森林F。
    (2)造一个新结点,从F中选取两根结点权值小的作为新结点的左、右子,并且将新结点的权值置为左、右子树上根结点的权值之和。
    (3)从F中除刚才选出的两棵,同时将新得到的加入F中
    (4)重复步骤(2)(3),直至中只剩下一颗树为止。
    从上述构造过程中可以看出哈夫曼树具有如下特点:
    (1)每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大。
    (2)构造过程中共新建了 n-1个结点(双分支结点),因此哈的结点总数为2n-1
    (3)每次都构造选择2颗树为新结点,因此哈夫曼树不存在度为1的结点
    在这里插入图片描述

  3. 哈夫曼编码
    在数据通信中,若对每个字符用相等长度的二进制位表示,称这种编码方式为固定长度编码若允许对不同字符用不等长的二进制位表示,则这种编码方式称为可变长度编码。可变长度编码比固定长度编码要好得多,其特点是对频率高的字符赋以短编码,而对频率较低的字符则赋以较长一些的编码,从而可以使字符的平均编码长度减短,起到压缩数据的效果。哈夫曼编码是一种被广泛应用而且非常有效的数据压缩编码。
    若没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码。举例:设计字符 AB和C对应的编码0.101和100是前编码对前编码的解码很简单,因为没有一个编码是其他编码的前缀。所以识别出第一个编码,将它翻译为原码,再对余下的编码文件重复同样的解码操作。例如,码串 00101100可被唯一地翻译为00101和 100。另举反例:如果再将字符D 的编码设计为 00,此时0是00 的前缀,那么这样的码的前两位就无法唯一翻译。由哈夫曼树得到哈夫曼编码是很自然的过程。首先,将每个出现的字符当作一个独立的结点其权值为它出现的频度(或次数),构造出对应的哈夫曼树。显然,所有字符结点都出现在叶结点中。我们可将字符的编码解释为从根至该字符的路径上边标记的序列,其中边标记为0表示“转向左孩子”,标记为1表示“转向右孩子”
    在这里插入图片描述

这棵哈夫曼树的 WPL为
WPL= 1x45 +3x(13 + 12 + 16) + 4x(5 + 9)=224
此处的 WPL 可视为最终编码得到二进制编码的长度,共224 位。若采用 3 位固定长度编码,则得到的二进制编码长度为 300 位,因此哈夫曼编码共压缩了 25%的数据。利用哈夫曼树可以设计出总长度最短的二进制前缀编码。

5.5.2 并查集

并查集是一种简单的集合表示,它支持以下3 种作

#1.将集合 s 中每个元都初始化为只有一个单元素的子合。
Initial(S):
#2.把集合S中的子集合 Root2并入子集合 Root1。
#要求 Root1和 Root2 互不相交,否则不执行合并。
Union(S,Root1,Root2):
#3 查集合 S 中单元 所在的合,并返回该子集合的根结点。
#通常用树(森林)的双亲表示作为并查的存储结构,每个子集合以一棵树表示。
#所有表示子集合的树,构成表示全集合的森林,存放在双亲表示数组内。
#通常用数组元素的下标代表元素名,用根结点的下标代表子集合名,根结点的双亲结点为负数。
Find(S,x):
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小*-^-*九

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值