数据结构的基本概念
基本概念和术语
数据
数据是信息的载体,是能被计算机程序识别和处理的符号的集合。
数据元素(一个对象)
数据元素是数据的基本单位,通常作为一个整体进行考虑和处理。如一个学生就是一个数据元素。
数据项(对象属性)
一个数据元素可由若干数据项组成,数据项是构成数据元素的不可分割的最小单位。如一个学生对象由学号、姓名、性别等数据项组成。
数据对象(对象集合)
数据对象是具有相同性质的数据元素组成的集合,是数据的一个子集。
数据类型(集合及定义在集合上的操作)
数据类型是一个值的集合和定义在此集合上的一组操作的总称。
- 原子类型:其不可再分的数据类型。
- 结构类型:其值可以再分解为若干成分的数据类型。
- 抽象数据类型(Abstract Data Type,ADT):抽象数据组织及其相关的操作。是计算机科学中具有类似行为的特定类别的数据结构的数学模型。
数据结构(数据关系)
数据结构(Data Structure)是相互之间存在一种或多种特定关系的数据元素的集合。数据元素相互之间的关系称之为结构。
数据结构包含:逻辑结构、存储结构、 数据的运算。在数据结构的定义中,使用者往往忽略数据的运算。
如逻辑结构和存储结构相同的数据结构可能不是同一数据结构,因为定义在该结构上的数据的运算可能不同。数据结构三要素
逻辑结构
逻辑结构是指数据元素之间的逻辑结构。
逻辑结构与数据的存储无关,是独立于计算机的。逻辑结构分为线性结构和非线性结构。 - 线性结构:结构中的元素之间只存在一对一的关系。
- 非线性结构:
- 集合: 结构中的数据元素只存在同属一个集合的关系。
- 树形结构:结构中的数据元素之间存在一对多的关系。
- 图状结构或网状结构:结构中的数据元素之间存在多对多的关系。
存储结构
存储结构是指数据结构在计算机中的表示(又称映射),也称物理结构。
数据的存储结构是用计算机语言实现的逻辑结构。所以数据的存储结构本质上也是一种逻辑结构,数据的存储结构不能独立于其逻辑结构。
- 顺序存储:逻辑上相邻的元素存储在物理位置上也相邻的结构。
- 优点:可以实现随机存取,每个元素占用最少的存储空间。
- 缺点:只能使用相邻的一整块存储单元,可能产生较多的外部碎片,不方便扩容。
- 非顺序存储(离散存储):
- 链式存储:逻辑上相邻的元素在物理位置上不要求相邻的结构。借助指针(游标)来表示元素之间的逻辑关系。
- 优点:不会出现碎片现象,能充分利用所有存储单元。
- 缺点:每个元素的存储指针会占用额外的存储空间,只能实现顺序存取。
- 索引存储:在存储元素信息时,建立附件的索引表,索引表中的每项称为索引项。
- 优点:检索速度快。
- 缺点:附件的索引表会占用额外的存储空间。增加和删除数据时需要修改索引表,会花费较多的时间。
- 散列存储(哈希[Hash]存储):根据元素的关键字通过相应的Hash算法计算出该元素的存储地址。
- 优点:检索、增加和删除的操作都很快。
- 缺点:可能出现元素存储单元冲突,解决冲突会增加时间和空间的开销。
数据的运算
施加在数据上的运算包括运算的定义和实现。- 运算的定义是针对逻辑结构的,指出运算的功能。
- 运算的实现是针对存储结构的,指出运算的具体操作步骤。
算法和算法指标
$$程序 = 数据结构 + 算法$$算法的基本概念
算法(Algorithm)是对特定问题求解步骤的一种描述,它是指令的有限序列,其中每条指令表示一个或多个操作。
通俗地讲:一个算法就是一种解题方法。
算法是指令的有限序列,所以一个合格的算法应该是在有限的时间能能够运算完成并给出处理结果的,而不是无限的运行下去。算法的重要特性
- 有穷性:算法必须总在执行有穷步后结束,且每一步都可在有穷时间内完成。即每条指令的执行次数必须是有限的。
- 确定性:算法中每条指令必须有确切的含义,无二义性。
- 可行性:每条指令都应在有限的时间内完成。
- 输入性:具有零个或多个输入。
- 输出性:至少产生一个输出。
在王道数据结构中算法确定性包含对于相同的输入只能得出相同的输出。但近年来随着蚁群算法和遗传算法等在解空间树寻找最优解等针对NP类问题算法的发展,我对这种说法持保留态度。但是针对P类问题(可以在多项式时间内求解的判定问题),应该符合这一特性。算法指标
一个“好”的算法,应该满足以下特性:- 正确性:算法应该可以正确的求解问题。
- 可读性:算法具有良好的可读性,易于理解、编码、调试。
- 健壮性(鲁棒性):针对非法数据,算法能够进行处理,算法不会异常结束,返回非法数据等。
- 高效率和低存储:算法执行的时间少,效率高;算法执行过程占用的存储空间少。
算法效率的度量
算法效率的度量是通过时间复杂度和空间复杂度来度量的。时间复杂度
时间复杂度:算法中基本操作重复执行的次数是问题规模$n$的某个函数$f(n)$,记作$T(n)$。
渐近时间复杂度:当问题的规模$n$趋向无穷大时,我们把时间复杂度$T(n)$的数量级(阶)成为算法的渐近时间复杂度。
例如:
$$T(n) = 2n^3 + 3n^2 + 2n + 1$$
当时间复杂度$T(n)$在$n$趋向于无穷大时,显然有:
$$\lim\limits_{n \to \infty} \frac{T(n)}{n^3} = 2$$
这说明当$n$充分大时,$T(n)$和$n^3$同阶,可记为$T(n) = O(n^3)$。我们称$T(n) = O(n^3)$是$T(n)$的渐近时间复杂度。
当评价算法的时间性能时,采用的主要标准时算法时间复杂度的数量级,即算法的渐进时间复杂度。当我们在题目中遇到求解时间复杂度时,如无特别说明,一般是指求解其渐进时间复杂度,即求解问题规模的阶数。
- 最坏时间复杂度:最坏情况下的时间复杂度。
- 平均时间复杂度:所有可能输入实例等概率出现的情况下,算法的时间复杂度。
- 最好时间复杂度:最好情况下的时间复杂度。
一般总是考虑在最坏情况下的时间复杂度。
常数阶时间复杂度:当程序段的执行时间是与问题规模$n$无关的常数时,算法的时间复杂度为常数阶,记做$T(n) = O(1)$。
时间复杂度运算规则
对于复杂的算法,我们将其分为容易估算的几个部分,然后通过运算规则求解原问题的复杂度。例如:算法中两个部分的时间复杂度分别为$T_1(n) = O(f(n))$和$T_2(n) = O(g(n))$,则有以下两条规则:
加法规则
$$T(n) = T_1(n) + T_2(n) = O(f(n) + g(n)) = O(max(f(n), g(n))$$
乘法规则
$$T(n) = T_1(n) \times T_2(n) = O(f(n) \times g(n)) = O(f(n) \times g(n))$$
常见渐近时间复杂度
$$O(1) < O(\log_2n) < O(n) < O(n\log_2n) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)$$
空间复杂度
空间复杂度:该算法所耗费的存储空间,它是问题规模$n$的函数,记为$S(n) = O(g(n))$。
原地工作:算法原地工作指算法所需的辅助空间为常量,即$O(1)$。
空间复杂度的性质和操作同时间复杂度具有相似性,可参考时间复杂度的性质和操作进行学习。