数据结构(C++)笔记:01.绪论

1.1 数据结构的兴起和发展

一、数据结构起源于程序设计。

·程序设计的新问题:应如何组织待处理的数据以及数据之间的关系(结构)。
·70年代初,数据结构作为一门独立的课程开始进入大学课堂。

请查找数据结构起源的资料:大话数据结构P4
早期人们都把计算机理解为数值计算工具,就是感觉计算机当然是用来计算的,所以计算机解决问题,应该是先从具体问题中抽象出一个适当的数据模型,设计出一个解此数据模型的算法,然后再编写程序,得到一个实际的软件。
可现实中,我们更多的不是解决数值计算的问题,而是需要一些更科学有效的手段(比如表、树和图等数据结构)的帮助,才能更好地处理问题。所以数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及它们之间的关系和操作等相关问题的学科。
1968年,美国的高德纳(Donald E.Knuth)教授在其所写的《计算机程序设计艺术》第一卷《基本算法》中,较系统地阐述了数据的逻辑结构和存储结构及其操作,开创了数据结构的课程体系。同年,数据结构作为一门独立的课程,在计算机科学的学位课程中开始出现。也就是说,那之后计算机相关专业的学生开始接受《数据结构》的“折磨”——其实应该是享受才对。
之后,70年代初,出现了大型程序,软件也开始相对独立,结构程序设计成为程序设计方法学的主要内容,人们越来越重视“数据结构”,认为程序设计的实质是对确定的问题选择一种好的结构,加上设计一种好的算法。可见,数据结构在程序设计当中占据了重要的地位。

二、数据结构随着程序设计的发展而发展。

程序设计经历了三个阶段:无结构阶段、结构化阶段和面向对象阶段,相应地,数据结构的发展也经历了三个阶段:
在这里插入图片描述

三、数据结构的发展并未终结。

1. 数据结构将继续随着程序设计的发展而发展;
2. 面向各专门领域的数据结构得到研究和发展,各种空间数据结构也在探索中。

1.2 数据结构的研究对象

例1-1 学籍管理问题
在这里插入图片描述
例1-2 人——机对弈问题
在这里插入图片描述
例1-3 教学计划编排问题
在这里插入图片描述
结论:
·用计算机求解问题一般包含两个步骤:
⑴分析问题(抽象问题,使其成为一个合适的数学模型)
⑵设计算法 后面还有编程和测试。
·数值问题抽象出的模型通常是数学方程
非数值问题抽象出的模型通常是数据结构
·数据结构是研究非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作的学科。

1.3 数据结构的基本概念

1.3.1 数据结构

  1. 数据:在计算机科学中是指所有能输入到计算机中并能被计算机程序识别和处理的符号集合。
    数值数据:例如:整数、实数
    非数值数据:例如:文字、声音、图形和图像。
    也就是说,我们这里说的数据,其实就是符号,而且这些符号必须具备两个前提:
    可以输入到计算机中。能被计算机程序处理。
    对于整型、实型等数值类型,可以进行数值计算。
    对于字符数据类型,就需要进行非数值的处理。而声音、图像、视频等其实是可以通过编码的手段变成字符数据来处理的。
  2. 数据元素:是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。
    构成数据元素的不可分割的最小单位称为数据项。
    数据元素:是组成数据的、有一定意义的基本单位,在计算机中通常作为整体处理。也被称为记录。
    比如,在人类中,什么是数据元素呀?当然是人了。
    畜类呢?哈,牛、马、羊、鸡、猪、狗等动物当然就是禽类的数据元素。
    数据项:一个数据元素可以由若干个数据项组成。
    比如人这样的数据元素,可以有眼、耳、鼻、嘴、手、脚这些数据项,也可以有姓名、年龄、性别、出生地址、联系电话等数据项,具体有哪些数据项,要视你做的系统来决定。
    数据项是数据不可分割的最小单位。在数据结构这门课程中,我们把数据项定义为最小单位,是有助于我们更好地解决问题。所以,记住了,数据项是数据的最小单位。但真正讨论问题时,数据元素才是数据结构中建立数据模型的着眼点。就像我们讨论一部电影时,是讨论这部电影角色这样的“数据元素”,而不是针对这个角色的姓名或者年龄这样的“数据项”去研究分析。
    ※ 数据元素是讨论数据结构时涉及的最小数据单位,其中的数据项一般不予考虑。
  3. 数据对象:是具有相同性质的数据元素的集合,是数据的子集。
    相同性质的含义是:
    什么叫性质相同呢,是指数据元素具有相同数量和类型的数据项,比如,还是刚才的例子,人都有姓名、生日、性别等相同的数据项。
    既然数据对象是数据的子集,在实际应用中,处理的数据元素通常具有相同性质,在不产生混淆的情况下,我们都将数据对象简称为数据。
  4. 数据结构:是指相互之间存在一定关系的数据元素的集合。按照视点的不同,数据结构分为逻辑结构和存储结构。
    结构,简单的理解就是关系,比如分子结构,就是说组成分子的原子之间的排列方式。严格点说,结构是指各个组成部分相互搭配和排列的方式。在现实世界中,不同数据元素之间不是独立的,而是存在特定的关系,我们将这些关系称为结构。那数据结构是什么?
    数据的逻辑结构是指数据元素之间逻辑关系的整体。
    所谓逻辑关系是指:线性结构(线性表,栈,队列,双队列,数组,串)和非线性结构(树、图)。也可以按下面四种方式分:
    ⑴ 集合 数据元素之间的关系是 属于同一集合 。
    ⑵ 线性结构 数据元素之间的关系是 线性关系。
    ⑶ 树结构 数据元素之间的关系是 层次关系。
    ⑷ 图结构 数据元素之间的关系是 任意关系。
    数据的存储结构又称为物理结构,是数据及其逻辑结构在计算机中的表示。
    有两种存储结构:顺序存储结构和链接存储结构。
    顺序存储结构的基本思想是:用一组连续的存储单元依次存储数据元素,数据元素之间的逻辑关系是由元素的存储位置来表示的(逻辑关系和物理关系是一致的)。
    这种存储结构其实很简单,说白了,就是排队占位。大家都按顺序排好,每个人占一小段空间,大家谁也别插谁的队。我们之前学计算机语言时,数组就是这样的顺序存储结构。当你告诉计算机,你要建立一个有9个整型数据的数组时,计算机就在内存中找了片空地,按照一个整型所占位置的大小乘以9,开辟一段连续的空间,于是第一个数组数据就放在第一个位置,第二个数据放在第二个,这样依次摆放。
    链接存储结构的基本思想是:用一组任意的存储单元存储数据元素,数据元素之间的逻辑关系是用指针来表示的。
    如果就是这么简单和有规律,一切就好办了。可实际上,总会有人插队,也会有人要上厕所、有人会放弃排队。所以这个队伍当中会添加新成员,也有可能会去掉老元素,整个结构时刻都处于变化中。显然,面对这样时常要变化的结构,顺序存储是不科学的。那怎么办呢?
    现在如银行、医院等地方,设置了排队系统,也就是每个人去了,先领一个号,等着叫号,叫到时去办理业务或看病。在等待的时候,你爱在哪在哪,可以坐着、站着或者走动,甚至出去逛一圈,只要及时回来就行。你关注的是前一个号有没有被叫到,叫到了,下一个就轮到了。
    链式存储结构:是把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的。数据元素的存储关系并不能反映其逻辑关系,因此需要用一个指针存放数据元素的地址,这样通过地址就可以找到相关联数据元素的位置。
    结合数据库的逻辑体系和物理体系来理解。

1.3.2数据结构的访问接口

·数据结构的基本操作或基本运算是指:增删改查
·数据结构的基本操作的接口的全体称为数据结构的访问接口。
数据结构的基本操作应具有如下特性:
⑴ 抽象性:
⑵ 基本性:
⑶ 完备性:
⑷ 一般性:

1.3.3 抽象数据类型

1.数据类型

数据类型是一组值的集合以及定义于这个值集上的一组操作的总称。数据类型规定了该类型数据的取值范围和对这些数据所能采取的操作。
数据类型是按照值的不同进行划分的。在高级语言中,每个变量、常量和表达式都有各自的取值范围。类型就用来说明变量或表达式的取值范围和所能进行的操作。
当年那些设计计算机语言的人,为什么会考虑到数据类型呢?
比如,大家都需要住房子,也都希望房子越大越好。但显然,没有钱,考虑房子是没啥意义的。于是商品房就出现了各种各样的房型,有别墅的,有错层的,有单间的;有一百多平米的,也有几十平米的,甚至在北京还出现了胶囊公寓一—只有两平米的房间……这样就满足了不同人的需要。
同样,在计算机中,内存也不是无限大的,你要计算一个如1+1=2、3+5=8这样的整型数字的加减乘除运算,显然不需要开辟很大的适合小数甚至字符运算的内存空间。于是计算机的研究者们就考虑,要对数据进行分类,分出来多种数据类型。
在C语言中,按照取值的不同,数据类型可以分为两类:
■原子类型:是不可以再分解的基本类型,包括整型、实型、字符型等。
■结构类型:由若干个类型组合而成,是可以再分解的。例如,整型数组是由若干整型数据组成的。
C++中有哪些基本数据类型,若有如下类型声明:int a,b;如何理解数据类型规定了该类型数据的取值范围和对这些数据所能采取的操作?
答:在C语言中变量声明int a,b,这就意味着,在给变量a和b赋值时不能超出int的取值范围,变量a和b之间的运算只能是int类型所允许的运算。

2.抽象

所谓抽象,就是抽出问题本质的特征而忽略非本质的细节,是对具体事物的一个概括。

3.抽象数据类型

因为不同的计算机有不同的硬件系统,这就要求程序语言最终通过编译器或解释器转换成底层语言,如汇编语言甚至是通过机器语言的数据类型来实现的。可事实上,高级语言的编程者不管最终程序运行在什么计算机上,他的目的就是为了实现两个整型数字的运算,如a+b、a-b、a×b和a/b等,他才不关心整数在计算机内部是如何表示的,也不想知道CPU为了实现1+2进行几次开关操作,这些操作是如何实现的,对高级语言开发者来讲根本不重要。于是我们就会考虑,无论什么计算机、什么计算机语言,大都会面临着如整数运算、实数运算、字符运算等操作,我们可以考虑把它们都抽象出来。我们对已有的数据类型进行抽象,就有了抽象数据类型。
抽象数据类型(以下简称ADT)是一个数据结构以及定义在该结构上的一组操作的总称。
ADT可理解为对数据类型的进一步抽象,数据类型和ADT的区别仅在于:数据类型指的是高级程序设计语言支持的基本数据类型,而ADT指的是自定义的数据类型。
在这里插入图片描述
抽象数据类型不仅仅指那些已经定义并实现的数据类型,还可以是计算机编程者在设计软件程序时自己定义的数据类型,比如我们编写关于计算机绘图或者地图类的软件系统,经常都会用到坐标。也就是说,总是有成对出现的x和y,在3D系统中还有z出现,既然这三个整型数字是始终在一起出现,我们就定义一个叫point的抽象数据类型,它有X、y、z三个整型变量,这样我们很方便地操作一个point数据变量就能知道这一点的坐标了。
根据抽象数据类型的定义,它还包括定义在该模型上的一组操作。就像“超级玛丽”这个经典的任天堂游戏,里面的游戏主角是马里奥(Mario)。我们给他定义了几种基本操作,走(前进、后退、上、下)、跳、打子弹等。一个抽象数据类型定义了:一个数据对象、数据对象中各数据元素之间的关系及对数据元素的操作。至于,一个抽象数据类型到底需要哪些操作,这就只能由设计者根据实际需要来定。像马里奥,可能开始只有两种操作,走和跳,后来发现应该要增加一种打子弹的操作,再后来发现有些玩家希望它可以走得快一点,就有了按住打子弹键后前进就会“跑”的操作。这都是根据实际情况来设计的。
事实上,抽象数据类型体现了程序设计中问题分解、抽象和信息隐藏的特性。抽象数据类型把实际生活中的问题分解为多个规模小且容易处理的问题,然后建立一个计算机能处理的数据模型,并把每个功能模块的实现细节作为一个独立的单元,从而使具体实现过程隐藏起来。
一个ADT的定义不涉及它的实现细节,在形式上可繁可简,本书对ADT的定义包括抽象数据类型名、数据元素之间逻辑关系的定义、每种基本操作的接口(操作的名称和该操作的前置条件、输入、功能、输出、后置条件的定义),形式如下:

ADT 抽象数据类型名
Data
	  数据元素之间逻辑关系的定义
Operation
   操作1
		前置条件:执行此操作前数据所必须的状态
     输入:执行此操作所需要的输入
		功能:该操作将完成的功能
		输出:执行该操作后产生的输出
     后置条件:执行该操作后数据的状态
	  操作2
		……
	  ……
   操作n
         ……
endADT 

1.4 算法及算法分析

1.4.1 算法

1.什么是算法

通俗地讲,算法是解决问题的方法;
严格地说,算法是对特定问题求解步骤的一种描述,是指令的有限序列。
算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
算法必须满足下列五个重要特性:
⑴ 输入:
⑵ 输出:
输入和输出特性比较容易理解,算法具有零个或多个输入。尽管对于绝大多数算法来说,输入参数都是必要的,但对于个别情况,如打印“hello world!”这样的代码,不需要任何输入参数,因此算法的输入可以是零个。算法至少有一个或多个输出,算法是一定需要输出的,不需要输出,你用这个算法干吗?输出的形式可以是打印输出,也可以是返回一个或多个值等。
⑶ 有穷性:
有穷性:指算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成。现实中经常会写出死循环的代码,这就是不满足有穷性。当然这里有穷的概念并不是纯数学意义的,而是在实际应用当中合理的、可以。接受的“有边界”。你说你写一个算法,计算机需要算上个二十年,一定会结束,它在:
数学意义上是有穷了,可是媳妇都熬成婆了,算法的意义也不就大了。
加密算法的破解。
⑷ 确定性:
确定性:算法的每一步骤都具有确定的含义,不会出现二义性。算法在一定条件下,只有一条执行路径,相同的输入只能有唯一的输出结果。算法的每个步骤被精确定义而无歧义。
⑸ 可行性:
可行性:算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成。可行性意味着算法可以转换为程序上机运行,并得到正确的结果。尽管在目前计算机界也存在那种没有实现的极为复杂的算法,不是说理论上不能实现,而是因为过于复杂,我们当前的编程方法、工具和大脑限制了这个工作,不过这都是理论研究领域的问题,不属于我们现在要考虑的范围。
问题:用手机测量人体身高的可行性?

2.什么是“好”算法

一个“好”算法首先要满足算法的五大特性,此外还要具备下列特性:
⑴ 正确性:
正确性:算法的正确性是指算法至少应该具有输入、输出和加工处理无歧义性、能正确反映问题的需求、能够得到问题的正确答案。
但是算法的“正确”通常在用法上有很大的差别,大体分为以下四个层次。
1.算法程序没有语法错误。
2.算法程序对于合法的输入数据能够产生满足要求的输出结果。
3.算法程序对于非法的输入数据能够得出满足规格说明的结果。
4.算法程序对于精心选择的,甚至刁难的测试数据都有满足要求的输出结果。对于这四层含义,层次1要求最低,但是仅仅没有语法错误实在谈不上是好算法。这就如同仅仅解决温饱,不能算是生活幸福一样。而层次4是最困难的,我们几乎不可能逐一验证所有的输入都得到正确的结果。
因此算法的正确性在大部分情况下都不可能用程序来证明,而是用数学方法证明的。证明一个复杂算法在所有层次上都是正确的,代价非常昂贵。所以一般情况下,我们把层次3作为一个算法是否正确的标准。
⑵ 鲁棒性:
一个好的算法还应该能对输入数据不合法的情况做合适的处理。比如输入的时间或者距离不应该是负数等。
健壮性:当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果。
和物理环境有关,例如带宽,服务器的性能等,
⑶ 简单性:
⑷ 抽象分级:
可读性:算法设计的另一目的是为了便于阅读、理解和交流。
可读性高有助于人们理解算法,晦涩难懂的算法往往隐含错误,不易被发现,并且难于调试和修改。
我们写代码的目的,一方面是为了让计算机执行,但还有一个重要的目的是为了便于他人阅读,让人理解和交流,自己将来也可能阅读,如果可读性不好,时间长了自己都不知道写了些什么。可读性是算法(也包括实现它的代码)好坏很重要的标志。
注释的重要性。
⑸ 高效性:
好的算法还应该具备时间效率高和存储量低的特点。
时间效率指的是算法的执行时间,对于同一个问题,如果有多个算法能够解决,执行时间短的算法效率高,执行时间长的效率低。存储量需求指的是算法在执行过程中需要的最大存储空间,主要指算法程序运行时所占用的内存或外部硬盘存储空间。设计算法应该尽量满足时间效率高和存储量低的需求。在生活中,人们都希望花最少的钱,用最短的时间,办最大的事,算法也是一样的思想,最好用最少的存储空间,花最少的时间,办成同样的事就是好的算法。求100个人的高考成绩平均分,与求全省的所有考生的成绩平均分在占用时间和内存存储上是有非常大的差异的,我们自然是追求可以高效率和低存储量的算法来解决问题。

3. 算法的描述方法

常用的描述算法的方法有自然语言、流程图、程序设计语言和伪代码等。

1.4.2 算法分析

1.度量算法效率的方法

一种方法是事后统计的方法,先将算法实现,然后输入适当的数据运行,测算其时间和空间开销。其缺点:⑴ 编写程序实现算法将花费较多的时间和精力;⑵ 所得实验结果依赖于计算机的软硬件等环境因素,有时容易掩盖算法本身的优劣。
度量算法效率必须依据算法事先编制好程序,这通常需要花费大量的时间和精力。如果编制出来发现它根本是很糟糕的算法,不是竹篮打水一场空吗?
时间的比较依赖计算机硬件和软件等环境因素,有时会掩盖算法本身的优劣。要知道,现在的一台四核处理器的计算机,跟当年286、386、486等老爷爷辈的机器相比,在处理算法的运算速度上,是不能相提并论的;而所用的操作系统、编译器、运行框架等软件的不同,也可以影响它们的结果;就算是同一台机器,CPU使用率和内存占用情况不一样,也会造成细微的差异。
算法的测试数据设计困难,并且程序的运行时间往往还与测试数据的规模有很大关系,效率高的算法在小的测试数据面前往往得不到体现。比如10个数字的排序,不管用什么算法,差异几乎是零。而如果有一百万个随机数字排序,那不同算法的差异就非常大了。那么我们为了比较算法,到底用多少数据来测试,这是很难判断的问题。
另一种是事前分析估算的方法——渐进复杂度,它是对算法所消耗资源的一种估算方法。
经过分析,我们发现,一个用高级程序语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:
1.算法采用的策略、方法。
2.编译产生的代码质量。
3.问题的输入规模。
4.机器执行指令的速度。
第1条当然是算法好坏的根本,第2条要由软件来支持,第4条要看硬件性能。
也就是说,抛开这些与计算机硬件、软件有关的因素,一个程序的运行时间,依赖于算法的好坏和问题的输入规模。所谓问题输入规模是指输入量的多少。

2.算法的时间复杂度

问题规模是指输入量的多少。运行算法所需要的时间T是问题规模n的函数。
基本语句是执行次数与整个算法的执行次数成正比的语句。
这种衡量效率的方法得出的不是时间量,而是一种增长趋势的度量。即当只考察问题规模充分大时,算法中基本语句的执行次数在渐近意义下的阶,称作算法的渐进时间复杂度,简称时间复杂度,通常用大 O O O(读作“大欧”)记号表示。
定义1-1 若存在两个正的常数 c c c n 0 n_0 n0,对于任意 n ≥ n 0 n≥n_0 nn0,都有 T ( n ) ≤ c × f ( n ) T(n)≤c×f(n) T(n)c×f(n),则称 T ( n ) = O ( f ( n ) ) T(n)=O(f(n)) T(n)=O(f(n))(或称算法在 O ( f ( n ) ) O(f(n)) O(f(n))中)。
具体看两个求和算法的例子:
第一种算法:

int i,sum=0,n=100/*执行1次*/
for(i=1;i<=n;i++/*执行了n+1次*7
{
	sum=sum+i;/*执行n次*/
}
printf("d",sum);/*执行1次*/

第二种算法:

int sum=0,n=100/*执行一次*/
sum=1+n)*n/2/*执行一次*/
printf("%d",sum);/*执行一次*/

显然,第一种算法,执行了1+(n+1)+n+1次=2n+3次;而第二种算法,是1+1+1=3次。事实上两个算法的第一条和最后一条语句是一样的,所以我们关注的代码其实是中间的那部分,我们把循环看作一个整体,忽略头尾循环判断的开销,那么这两个算法其实就是n次与1次的差距。算法好坏显而易见。
再看一个例子:

int i,j,x=0,sum=0,n=100/*执行一次*/
for(i=1;i<=n;i++{
	for(j=1;j<=n;j++{
		x++/*执行n×n次*/
		sum=sum+x;
	}
}
printf("%d",sum);/*执行一次*/

这个例子中,i从1到100,每次都要让j循环100次,而当中的x++和sum=
sum+x;其实就是1+2+3+…+10000,也就是1002次,所以这个算法当中,循环部分的代码整体需要执行 n 2 n^2 n2(忽略循环体头尾的开销)次。显然这个算法的执行次数对于同样的输入规模n=100,要多于前面两种算法,这个算法的执行时间随着n的增加也将远远多于前面两个。
此时你会看到,测定运行时间最可靠的方法就是计算对运行时间有消耗的基本操作的执行次数。运行时间与这个计数成正比。
我们不关心编写程序所用的程序设计语言是什么,也不关心这些程序将跑在什么样的计算机中,我们只关心它所实现的算法。这样,不计那些循环索引的递增和循环终止条件、变量声明、打印结果等操作,最终,在分析程序的运行时间时,最重要的是把程序看成是独立于程序设计语言的算法或一系列步骤。
可以从问题描述中得到启示,同样问题的输入规模是n,求和算法的第一种,求1+2+…+n需要一段代码运行n次。那么这个问题的输入规模使得操作数量是f(n)=n,显然运行100次的同一段代码规模是运算10次的10倍。而第二种,无论n为多少,运行次数都为1,即f(n)=1;第三种,运算100次是运算10次的100倍。因为它是f(n)= n 2 n^2 n2

3.最好、最坏和平均情况

4.算法的空间复杂度

算法的空间复杂度是指在算法的执行过程中,需要的辅助空间数量。辅助空间是除算法本身和输入输出数据所占据的空间外,算法临时开辟的存储空间。通常记作:S(n)=O(f(n)),其中,n为问题规模,分析方法与算法的时间复杂度类似。
我们在写代码时,完全可以用空间来换取时间,比如说,要判断某某年是不是闰年,你可能会花一点心思写了一个算法,而且由于是一个算法,也就意味着,每次给一个年份,都是要通过计算得到是否是闰年的结果。还有另一个办法就是,事先建立一个有2050个元素的数组(年数略比现实多一点),然后把所有的年份按下标的数字对应,如果是闰年,此数组项的值就是1,如果不是值为0。这样,所谓的判断某一年是否是闰年,就变成了查找这个数组的某一项的值是多少的问题。此时,我们的运算是最小化了,但是硬盘上或者内存中需要存储这2050个0和1。
这是通过一笔空间上的开销来换取计算时间的小技巧。到底哪一个好,其实要看你用在什么地方。

5. 算法分析举例

定理1-1 若 A ( n ) = a m n m + a m − 1 n m − 1 + … + a 1 n + a 0 A(n)=a_mn^m +a_{m-1}n^{m-1} +…+a_1n+a_0 A(n)=amnm+am1nm1++a1n+a0是一个m次多项式,则 A ( n ) = O ( n m ) A(n)=O(n^m) A(n)=O(nm)
定理可以参考大话数据结构27页.2.8节的函数渐进增长。

参考书目:

大话数据结构
数据结构——从概念到C++实现(第2版)
数据结构(C++版)教师用书

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

oldmao_2000

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

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

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

打赏作者

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

抵扣说明:

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

余额充值