数据结构学习笔记(自用)

第一章 绪论

数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及它们之间的关系和操作等相关问题的学科

1.1 基本概念和术语

数据:

数据是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合

数据元素:

数据元素是组成数据的、有一定意义的基本单位,在计算机中通常作为整体处理,也被称为记录

数据项:

一个数据元素可以由若干个数据项组成,数据项是数据不可分割的最小单位

数据对象:

数据对象是性质相同的数据元素的集合,是数据的子集

数据结构:

不同数据元素之间不是独立的,而是存在特定的关系,我们将这些关系称为结构,简单来说,数据结构就是相互之间存在一种或者多种特定关系的数据元素的集合

1.2逻辑结构和物理结构

1.2.1 逻辑结构

  逻辑结构是指数据对象中的数据元素之间的相互关系

   数据之间的逻辑关系可细分为三类,“一对一”、“一对多”和“多对多”三类

  1. 线性表用于存储具有“一对一”逻辑关系的数据;
  2. 树结构用于存储具有“一对多”关系的数据;
  3. 图结构用于存储具有“多对多”关系的数据;

1.2.2 物理结构

  数据的物理结构,指的是数据在物理存储空间上选择集中存放还是分散存放

如果选择集中存储,就使用顺序存储结构;反之,就使用链式存储

   并且,数据的用途不同,选择的存储结构也不同。将数据进行集中存储有利于后期对数据进行遍历操作,而分散存储更有利于后期增加或删除数据。因此,如果后期需要对数据进行大量的检索(遍历),就选择集中存储;反之,若后期需要对数据做进一步更新(增加或
删除),则选择分散存储。

第二章 线性表

2.1 线性表的类型定义

2.1.1 线性表的定义

什么是线性表
线性表是n 个类型相同数据元素的有限序列, 通常记作(a1 , a2 , a3 , …, an )。

线性结构的基本特征
01 集合中必存在唯一的一个“第一元素”。
02 集合中必存在唯一的一个 “最后元素”。
03 除最后元素之外,均有唯一的后继。
04 除第一元素之外,均有唯一的前驱。


线性结构的抽象数据类型描述

ADT List {

数据对象:D={ ai | ai ∈ElemSet, i=1,2,...,n, n≥0 }
		 {称 n 为线性表的表长;n=0 时的线性表为空表。} 
		
数据关系:R1={ |ai-1 ,ai∈D, i=2,...,n }
    
基本操作
    
}ADT List

线性表的基本操作

InitList(&S) //构造一个空的线性表L

DestroyList(&S) //销毁线性表L

ListEmpty(S)//判断L是否空

ListLength(S) //求L的长度

PirorElem(L,cur_e,&pre_e) //求前驱的值

NextElem(L,cur_e,&next_e) //求后驱的值

GetElem(L,i&e) //取i位置的值

LocateElem(L,e,compare()) //在线性表中查找e

ListTravelse(L,visit()) //遍历线性表

ClearList(&L) //清空线性表

ListInsert(&L,i,e) //在i位置插入e

ListDelete(&L,i,&e) //删除i位置的元素

静态数组和动态数组的说明
静态数组

typedef struct{
    Elemtype data[30];
    int length;
    int listsize;
}SqList;

动态数组

typedef struct{
    Elemtype *elem;
    int length;
    int listsize;
}SqList;
SqList L; //声明线性表变量L
L.elem = (ElemType*)malloc(30*sizeof(ElemType));
L.data 是数组名
L.elem 是数组名

2.1.2 基本操作的应用

基本操作的应用 例2-1

#include<stdio.h>

/*题目概述:
    假设:有两个集合A和B分别用两个线性表LA和LB表示,即:线性表中的数据元素即为集合中的成员。
    现要求一个新的集合A=AUB。
*/

void union(List &La,List Lb){
    La_len=ListLength(La);
    La_len=ListLength(Lb); //求线性表的长度
    for(i=1;i<=Lb_len;i++){
        GetElem(Lb,i,e); //对Lb进行遍历,取Lb中第i个元素赋给e
        if(!LocateElem(La,e,equal())) //判断条件:La中元素与Lb中元素相等,然后作非运算
            ListInsert(La,++La_len,e); //将La中不存在和e相同的元素插入进去
    }
}
/*时间复杂度分析:O(ListLength(La)*ListLength(Lb))
    整体结构为一个循环嵌套一个条件判断,
    循环体中第7行对Lb做遍历,所以复杂度为ListLength(Lb),
    条件判断中对La中每一个元素做检验,所以复杂度为ListLength(La)

基本操作的应用 例2-2

#include<stdio.h>

/*题目概述:
    将两个“数据元素按值递增有序排列”的线性表La和Lb归并到线性表Lc中,且Lc具有同样的性质。
*/

void MergeList(List La,List Lb,List &Lc){
    InitList(Lc); //构造空的线性表Lc
    La_len=ListLength(La);
    Lb_len=ListLength(Lb);
    while((i<=La_len)&&(j<=Lb_len)){ //当i和j均<=各自对应表长,保证La和Lb均不为空
        GetElem(La,i,ai);
        GetElem(Lb,i,bi); //获取元素位置
        if(ai<=bj){ //将ai插入到Lc中
            ListInsert(Lc,++k,ai);
            ++i;
        }else{ //将bi插入到Lc中
            ListInsert(Lc,++k,aj);
            ++j;
        }
        //有一方元素达到表长,此时只剩下一个线性表中有元素没有插入
        while(i<=La_len){ //当La不空时
            GetElem(La,i,ai);
            ListInsert(Lc,++k,ai);
        } //插入La表中剩余元素
        while(i<=Lb_len){ //当Lb不空时
            GetElem(Lb,j,bj);
            ListInsert(Lc,++k,bj);
        } //插入Lb表中剩余元素
    }
}
/*时间复杂度分析:O(ListLength(La)+ListLength(Lb))

2.2线性表的顺序表示和实现

2.2.1 线性表的顺序表示

定义
  以数据元素x 的存储位置和数据元素 y 的存储位置之间的某种关系表示逻辑关系<x,y>。将具有“一对一”关系的数据“线性”地存储到物理空间中,这种存储结构就称为线性存储结构(简称线性表)。

图示
用一组地址连续的存储单元依次存放线性表中的数据元素
在这里插入图片描述
数据元素地址之间的关系

以“存储位置相邻”表示有序对时, ai-1和ai的地址关系如下:
LOC(ai ) = LOC(ai-1 ) + C
// C为一个数据元素所占存储量

所有数据元素的存储位置均取决于第一个数据元素的存储位置。
LOC(ai ) = LOC(a1 ) + (i-1)×C
// LOC(a1 )表示基地址

顺序表的实现
01 数据元素的数据类型Elemtype实现
02 顺序表数据类型的实现
03 顺序表的实现
04 顺序表数据类型的动态数组


实现表格的顺序存储

#include<stdio.h>

/*步骤:
  1.数据元素的数据类型Elemtype实现
  2.顺序表数据类型的实现
  3.顺序表的实现
*/
//第一步 方式1:
typedef data{
  char name[8];
  int age;
  char sex[2];
  float score;
};
typedef struct data Elemtype;
//第一步 方式2:
typedef struct data{
  char name[8];
  int age;
  char sex[2];
  float score;
}Elemtype;
//第一步 方式3:
typedef struct {
  char name[8];
  int age;
  char sex[2];
  float score;
}Elemtype;
//第二步 方式1 静态数组实现:
typedef struct {
  Elemtype data[30];
  int length;
  int listsize;
}SqList; //SqList现在是一个顺序表数据类型
//第三步
SqList L;
//第i个人的信息存储在:
L.data.[i].name;
L.data.[i].age;
L.data.[i].sex;
L.data.[i].score;
//表信息:
L.length; //线性表长度
L.listsize; //线性表规模

2.2.2 基本操作的实现

构造一个空线性表

typedef struct Table{
    int * head;//声明了一个名为head的长度不确定的数组,也叫“动态数组”
    int length;//记录当前顺序表的长度
    int size;//记录顺序表分配的存储容量
}table;
#define Size 5 //对Size进行宏定义,表示顺序表申请空间的大小
table initTable(){
    table t;
    t.head=(int*)malloc(Size*sizeof(int));//构造一个空的顺序表,动态申请存储空间
    if (!t.head) //如果申请失败,作出提示并直接退出程序
    {
        printf("初始化失败");
        exit(0);
    }
    t.length=0;//空表的长度初始化为0
    t.size=Size;//空表的初始存储空间为Size
    return t;
}

顺序表插入数据元素

//插入函数,其中,elem为插入的元素,add为插入到顺序表的位置
table addTable(table t,int elem,int add)
{
    //判断插入本身是否存在问题(如果插入元素位置比整张表的长度+1还大(如果相等,是尾随的情况),或者插入的位置本身不存在,程序作为提示并自动退出)
    if (add>t.length+1||add<1) {
        printf("插入位置有问题\n");
        return t;
    }
    //做插入操作时,首先需要看顺序表是否有多余的存储空间提供给插入的元素,如果没有,需要申请
    if (t.length==t.size) {
        t.head=(int *)realloc(t.head, (t.size+1)*sizeof(int));
        if (!t.head) {
            printf("存储分配失败\n");
            return t;
        }
        t.size+=1;
    } 
    //插入操作,需要将从插入位置开始的后续元素,逐个后移
    for (int i=t.length-1; i>=add-1; i--) {
        t.head[i+1]=t.head[i];
    }
    //后移完成后,直接将所需插入元素,添加到顺序表的相应位置
    t.head[add-1]=elem;
    //由于添加了元素,所以长度+1
    t.length++;
    return t;
}

顺序表删除元素

table delTable(table t,int add){
    if (add>t.length || add<1) {
        printf("被删除元素的位置有误\n");
        return t;
    }
    //删除操作
    for (int i=add; i<t.length; i++) {
        t.head[i-1]=t.head[i];
    }
    t.length--;
    return t;
}

顺序表查找元素

//查找函数,其中,elem表示要查找的数据元素的值
int selectTable(table t,int elem){
    for (int i=0; i<t.length; i++) {
        if (t.head[i]==elem) {
            return i+1;
        }
    }
    return -1;//如果查找失败,返回-1
}

顺序表更改元素

//更改函数,其中,elem为要更改的元素,newElem为新的数据元素
table amendTable(table t,int elem,int newElem){
    int add=selectTable(t, elem);
    t.head[add-1]=newElem;//由于返回的是元素在顺序表中的位置,所以-1就是该元素在数组中的下标
    return t;
}

顺序表的基本操作的完整代码

#include <stdio.h>
#include <stdlib.h>
#define Size 5
typedef struct Table{
    int * head;
    int length;
    int size;
}table;
table initTable(){
    table t;
    t.head=(int*)malloc(Size*sizeof(int));
    if (!t.head)
    {
        printf("初始化失败\n");
        exit(0);
    }
    t.length=0;
    t.size=Size;
    return t;
}
table addTable(table t,int elem,int add)
{
    if (add>t.length+1||add<1) {
        printf("插入位置有问题\n");
        return t;
    }
    if (t.length>=t.size) {
        t.head=(int *)realloc(t.head, (t.size+1)*sizeof(int));
        if (!t.head) {
            printf("存储分配失败\n");
        }
        t.size+=1;
    }
    for (int i=t.length-1; i>=add-1; i--) {
        t.head[i+1]=t.head[i];
    }
    t.head[add-1]=elem;
    t.length++;
    return t;
}
table delTable(table t,int add){
    if (add>t.length || add<1) {
        printf("被删除元素的位置有误\n");
        return t;
    }
    for (int i=add; i<t.length; i++) {
        t.head[i-1]=t.head[i];
    }
    t.length--;
    return t;
}
int selectTable(table t,int elem){
    for (int i=0; i<t.length; i++) {
        if (t.head[i]==elem) {
            return i+1;
        }
    }
    return -1;
}
table amendTable(table t,int elem,int newElem){
    int add=selectTable(t, elem);
    t.head[add-1]=newElem;
    return t;
}
void displayTable(table t){
    for (int i=0;i<t.length;i++) {
        printf("%d ",t.head[i]);
    }
    printf("\n");
}
int main(){
    table t1=initTable();
    for (int i=1; i<=Size; i++) {
        t1.head[i-1]=i;
        t1.length++;
    }
    printf("原顺序表:\n");
    displayTable(t1);
  
    printf("删除元素1:\n");
    t1=delTable(t1, 1);
    displayTable(t1);
  
    printf("在第2的位置插入元素5:\n");
    t1=addTable(t1, 5, 2);
    displayTable(t1);
  
    printf("查找元素3的位置:\n");
    int add=selectTable(t1, 3);
    printf("%d\n",add);
  
    printf("将元素3改为6:\n");
    t1=amendTable(t1, 3, 6);
    displayTable(t1);
    return 0;
}

2.2.2 顺序表的优缺点

优点
01 节省存储空间
02 对线性表中第i个结点的操作易于实现
03 容易查找一个结点的前驱和后继

缺点
01 插入和删除操作需要移动数据
02 建立空表时,较难确定所需的存储空间

2.3 线性表的链式表示和实现

2.3.1 单链表

单链表的结构示意图
在这里插入图片描述
链表的定义
一个线性表由若干个结点组成,每个结点至少含有两个域:
数据域(信息域)和 指针域(链域),
由这样的结点存储的线性表称作链表。
结构特点
01 逻辑次序和物理次序不一定相同。
02 元素之间的逻辑关系用指针表示。
03 需要额外空间存储元素之间的关系。
头指针的概念
在链式存储结构中以第一个结点的存储地址作为线性表的基地址,通常称它为“头指针”。
线性表的最后一个数据元素没有后继,因此最后一个结点中的“指针域"是一个特殊的值"NULL",通常称它为"空指针"
头结点
01 头结点的概念
在单链表上设置一个结点,它本身不存放数据,它的指针域指向第一个元素的地址。
02 头结点的作用
使对第一个元素的操作与对其它元素的操作保持一致。
在这里插入图片描述
单链表的基本操作的实现分析

  1. 创建链表
//声明节点结构
typedef struct Link{
    int  elem;//存储整形元素
    struct Link *next;//指向直接后继元素的指针
}link;
//创建链表的函数
link * initLink(){
    link * p=(link*)malloc(sizeof(link));//创建一个头结点
    link * temp=p;//声明一个指针指向头结点,用于遍历链表
    //生成链表
    for (int i=1; i<5; i++) {
     //创建节点并初始化
        link *a=(link*)malloc(sizeof(link));
        a->elem=i;
        a->next=NULL;
        //建立新节点与直接前驱节点的逻辑关系
        temp->next=a;
        temp=temp->next;
    }
    return p;
}
  1. 链表插入元素
    同顺序表一样,向链表中增添元素,根据添加位置不同,可分为以下 3 种情况:
    插入到链表的头部(头节点之后),作为首元节点;
    插入到链表中间的某个位置;
    插入到链表的最末端,作为链表中最后一个数据元素;

虽然新元素的插入位置不固定,但是链表插入元素的思想是固定的,只需做以下两步操作,即可将新元素插入到指定的位置:
将新结点的 next 指针指向插入位置后的结点;
将插入位置前结点的 next 指针指向插入结点;

注意:链表插入元素的操作必须是先步骤 1,再步骤 2;反之,若先执行步骤 2,除非再添加一个指针,作为插入位置后续链表的头指针,否则会导致插入位置后的这部分链表丢失,无法再实现步骤 1。

//p为原链表,elem表示新数据元素,add表示新元素要插入的位置
link * insertElem(link * p, int elem, int add) {
    link * temp = p;//创建临时结点temp
    //首先找到要插入位置的上一个结点
    for (int i = 1; i < add; i++) {
        temp = temp->next;
        if (temp == NULL) {
            printf("插入位置无效\n");
            return p;
        }
    }
    //创建插入结点c
    link * c = (link*)malloc(sizeof(link));
    c->elem = elem;
    //向链表中插入结点
    c->next = temp->next;
    temp->next = c;
    return p;
}
  1. 链表删除元素
    从链表中删除指定数据元素时,实则就是将存有该数据元素的节点从链表中摘除,但作为一名合格的程序员,要对存储空间负责,对不再利用的存储空间要及时释放。因此,从链表中删除数据元素需要进行以下 2 步操作:
    1 将结点从链表中摘下来;
    2 手动释放掉结点,回收被结点占用的存储空间;

其中,从链表上摘除某节点的实现非常简单,只需找到该节点的直接前驱节点 temp,执行一行程序:temp->next=temp->next->next;

//p为原链表,add为要删除元素的值
link * delElem(link * p, int add) {
    link * temp = p;
    //遍历到被删除结点的上一个结点
    for (int i = 1; i < add; i++) {
        temp = temp->next;
        if (temp->next == NULL) {
            printf("没有该结点\n");
            return p;
        }
    }
    link * del = temp->next;//单独设置一个指针指向被删除结点,以防丢失
    temp->next = temp->next->next;//删除某个结点的方法就是更改前一个结点的指针域
    free(del);//手动释放该结点,防止内存泄漏
    return p;
}
  1. 链表查找元素
    在链表中查找指定数据元素,最常用的方法是:从表头依次遍历表中节点,用被查找元素与各节点数据域中存储的数据元素进行比对,直至比对成功或遍历至链表最末端的 NULL(比对失败的标志)。
//p为原链表,elem表示被查找元素、
int selectElem(link * p,int elem){
//新建一个指针t,初始化为头指针 p
    link * t=p;
    int i=1;
    //由于头节点的存在,因此while中的判断为t->next
    while (t->next) {
        t=t->next;
        if (t->elem==elem) {
            return i;
        }
        i++;
    }
    //程序执行至此处,表示查找失败
    return -1;
}

注意,遍历有头节点的链表时,需避免头节点对测试数据的影响,因此在遍历链表时,建立使用上面代码中的遍历方法,直接越过头节点对链表进行有效遍历。

  1. 链表更新元素
    更新链表中的元素,只需通过遍历找到存储此元素的节点,对节点中的数据域做更改操作即可。
//更新函数,其中,add 表示更改结点在链表中的位置,newElem 为新的数据域的值
link *amendElem(link * p,int add,int newElem){
    link * temp=p;
    temp=temp->next;//在遍历之前,temp指向首元结点
    //遍历到待更新结点
    for (int i=1; i<add; i++) {
        temp=temp->next;
    }
    temp->elem=newElem;
    return p;
}

总结:

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

typedef struct Link {
    int  elem;
    struct Link *next;
}link;
link * initLink();
//链表插入的函数,p是链表,elem是插入的结点的数据域,add是插入的位置
link * insertElem(link * p, int elem, int add);
//删除结点的函数,p代表操作链表,add代表删除节点的位置
link * delElem(link * p, int add);
//查找结点的函数,elem为目标结点的数据域的值
int selectElem(link * p, int elem);
//更新结点的函数,newElem为新的数据域的值
link *amendElem(link * p, int add, int newElem);
void display(link *p);

int main() {
    //初始化链表(1,2,3,4)
    printf("初始化链表为:\n");
    link *p = initLink();
    display(p);

    printf("在第4的位置插入元素5:\n");
    p = insertElem(p, 5, 4);
    display(p);

    printf("删除元素3:\n");
    p = delElem(p, 3);
    display(p);

    printf("查找元素2的位置为:\n");
    int address = selectElem(p, 2);
    if (address == -1) {
        printf("没有该元素");
    }
    else {
        printf("元素2的位置为:%d\n", address);
    }
    printf("更改第3的位置上的数据为7:\n");
    p = amendElem(p, 3, 7);
    display(p);

    return 0;
}

link * initLink() {
    link * p = (link*)malloc(sizeof(link));//创建一个头结点
    link * temp = p;//声明一个指针指向头结点,用于遍历链表
    //生成链表
    for (int i = 1; i < 5; i++) {
        link *a = (link*)malloc(sizeof(link));
        a->elem = i;
        a->next = NULL;
        temp->next = a;
        temp = temp->next;
    }
    return p;
}
link * insertElem(link * p, int elem, int add) {
    link * temp = p;//创建临时结点temp
    //首先找到要插入位置的上一个结点
    for (int i = 1; i < add; i++) {
        temp = temp->next;
        if (temp == NULL) {
            printf("插入位置无效\n");
            return p;
        }
    }
    //创建插入结点c
    link * c = (link*)malloc(sizeof(link));
    c->elem = elem;
    //向链表中插入结点
    c->next = temp->next;
    temp->next = c;
    return  p;
}

link * delElem(link * p, int add) {
    link * temp = p;
    //遍历到被删除结点的上一个结点
    for (int i = 1; i < add; i++) {
        temp = temp->next;
        if (temp->next == NULL) {
            printf("没有该结点\n");
            return p;
        }
    }
    link * del = temp->next;//单独设置一个指针指向被删除结点,以防丢失
    temp->next = temp->next->next;//删除某个结点的方法就是更改前一个结点的指针域
    free(del);//手动释放该结点,防止内存泄漏
    return p;
}
int selectElem(link * p, int elem) {
    link * t = p;
    int i = 1;
    while (t->next) {
        t = t->next;
        if (t->elem == elem) {
            return i;
        }
        i++;
    }
    return -1;
}
link *amendElem(link * p, int add, int newElem) {
    link * temp = p;
    temp = temp->next;//tamp指向首元结点
    //temp指向被删除结点
    for (int i = 1; i < add; i++) {
        temp = temp->next;
    }
    temp->elem = newElem;
    return p;
}
void display(link *p) {
    link* temp = p;//将temp指针重新指向头结点
    //只要temp指针指向的结点的next不是Null,就执行输出语句。
    while (temp->next) {
        temp = temp->next;
        printf("%d ", temp->elem);
    }
    printf("\n");
}

2.4静态链表及其创建(C语言实现)

静态链表,也是线性存储结构的一种,它兼顾了顺序表和链表的优点于一身,可以看做是顺序表和链表的升级版。

使用静态链表存储数据,数据全部存储在数组中(和顺序表一样),但存储位置是随机的,数据之间"一对一"的逻辑关系通过一个整形变量(称为"游标",和指针功能类似)维持(和链表类似)。
静态链表中的节点
静态链表存储数据元素也需要自定义数据类型,至少需要包含以下 2 部分信息:

数据域:用于存储数据元素的值;
游标:其实就是数组下标,表示直接后继元素所在数组中的位置;

因此,静态链表中节点的构成用 C 语言实现为

typedef struct {
    int data;//数据域
    int cur;//游标
}component;

代码:

#include <stdio.h>
#define maxSize 6
typedef struct {
    int data;
    int cur;
}component;
//将结构体数组中所有分量链接到备用链表中
void reserveArr(component *array);
//初始化静态链表
int initArr(component *array);
//输出函数
void displayArr(component * array,int body);
//从备用链表上摘下空闲节点的函数
int mallocArr(component * array);
int main() {
    component array[maxSize];
    int body=initArr(array);
    printf("静态链表为:\n");
    displayArr(array, body);
    return 0;
}
//创建备用链表
void reserveArr(component *array){
    for (int i=0; i<maxSize; i++) {
        array[i].cur=i+1;//将每个数组分量链接到一起
        array[i].data=-1;
    }
    array[maxSize-1].cur=0;//链表最后一个结点的游标值为0
}
//提取分配空间
int mallocArr(component * array){
    //若备用链表非空,则返回分配的结点下标,否则返回 0(当分配最后一个结点时,该结点的游标值为 0)
    int i=array[0].cur;
    if (array[0].cur) {
        array[0].cur=array[i].cur;
    }
    return i;
}
//初始化静态链表
int initArr(component *array){
    reserveArr(array);
    int body=mallocArr(array);
    //声明一个变量,把它当指针使,指向链表的最后的一个结点,因为链表为空,所以和头结点重合
    int tempBody=body;
    for (int i=1; i<4; i++) {
        int j=mallocArr(array);//从备用链表中拿出空闲的分量
        array[tempBody].cur=j;//将申请的空闲分量链接在链表的最后一个结点后面
        array[j].data=i;//给新申请的分量的数据域初始化
        tempBody=j;//将指向链表最后一个结点的指针后移
    }
    array[tempBody].cur=0;//新的链表最后一个结点的指针设置为0
    return body;
}
void displayArr(component * array,int body){
    int tempBody=body;//tempBody准备做遍历使用
    while (array[tempBody].cur) {
        printf("%d,%d ",array[tempBody].data,array[tempBody].cur);
        tempBody=array[tempBody].cur;
    }
    printf("%d,%d\n",array[tempBody].data,array[tempBody].cur);
}

2.5

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

心如止水Long

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

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

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

打赏作者

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

抵扣说明:

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

余额充值