参考王道《数据结构》的大体知识结构
个人代码存放 github:数据结构
个人博客(由于用阿里免费的DNS解析,所以很大概率会无法访问,建议第一次访问后等几分钟):stardust的博客
仅记录个人学习情况
语言使用c/c++、java
主要目的是补充知识体系,顺便解释一下一些未说明清楚的地方,以及使用java语言来实现数据结构,如有侵权的行为会进行删除
禁转
持续更新中…
第一章 绪论
1.1数据结构的基本概念
1.1.1 基本概念
1.什么是程序
程序=数据结构+算法
2.数据
数据是信息的载体,是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程序识别和处理的符号的集合。数据是计算机程序加工的原料。
3.数据元素
数据元素是数据的基本单位,通常作为一个整体进行考虑和处理。一个数据元素可由若干项数据项组成,数据项是组成数据元素的不可分割的最小单位。列如,学生记录就是一个数据元素,它由学号、姓名、性别等数据组成。
4.数据对象
数据对象是具有相同性质的数据元素的集合,是一个数据的子集。例如,整数数据对象是集合 { N = ± 1 , ± 2 , ⋯ } \{N= \pm 1, \pm 2, \cdots\} {N=±1,±2,⋯}
5.数据类型
数据类型是一个值的集合和定义在此集合和定义上的一组操作的总称。
如C语言中的int数据类型是由-32768~32767(16位机)的整数和+、-、*、1、%等运算符组成。简单来说,数据类型定义了数据的种类,以及在这个数据上可以进行操作。
-
原子类型。其值不可再分的数据类型。一种值的集合 + 定义在值集合上的一组操作。(比如:int,float,字符串)
int型:包括值集(1,2,3,4,5,···),并且可以在这些值上进行±*/
/ ——值的集合 原子类型 \ ——操作
-
结构类型。其值可以再分解为若干成分(等量)的数据类型。 一种数据结构 + 定义在这种数据结构上的一组操作。(比如:python中的列表,字典,元组)
/ ——数据结构 结构类型 \ ——操作
-
抽象数据类型。一个数学模型及定义在该数学模型上的一组操作。它通常是对数据的某种抽象,定义了数据发取值范围及其数据结构,以及对数据操作的集合。抽象数据类型需要通过固有数据类型(高级编程语言中已实现的数据类型,如int,float等)来实现。类似编程语言中的类。
数据类型与抽象数据类型类型的区别:
数据结构的形式定义为:一个二元组,Data Structure = (D, S) 其中D是数据元素的有限集,S是D上关系的有限集。
抽象数据类型的形式定义为:一个三元数组,ADT = (D, S, P) 其中P是对D的基本操作集。
- 数据抽象的意义在于数据类型的数学抽象特性。抽象数据类型和数据类型实质上都是概念,只不过抽象数据类型是对数据类型的进一步抽象。不仅限于各种不同的计算机处理器中已经实现的数据类型,还包括为解决更为复杂的问题而由用户自定义的复杂数据类型。不仅限于各种不同的计算机处理器中已经实现的数据类型,还包括为解决更为复杂的问题而由用户自定义的复杂数据类型。如:C++中的类就是抽象数据类型的一种具体实现。
- 通俗的讲:抽象数据类型,泛指除基本数据类型以外的数据类型。
- 什么叫类型?就是一类数据。基本数据类型被人做是最基本地,不可再划分的数据,一般就是整形、浮点型、以及字符型。抽象数据类型是由若干基本数据类型归并之后形成的一种新的数据类型,这种类型由用户定义,功能操作比基本数据类型更多,一般包括结构体和类。其实说白了,抽象数据类型就是把一些有一定关联的基本数据类型打包,然后当做新的数据类型使用。
- 抽象数据类型的用处:比如你要实现对一个人地信息管理,如果你只用基本数据类型那么你需要定义很多数据类型的变量比如名字、性别、出生地、生日之类的,并且操作起来不方便。如果用抽象数据来实现就简单了,直接把这些信息放包装在一个新的数据类型中,然后就可以直接定义这样的一个变量就可以了。
6.数据结构
数据结构是相互之间存在一种或多种特定关系的数据元素的集合。在任何问题中,数据元素都不是孤立存在的,他们之间存在某中关系,这种数据元素之间的关系称为结构(Structure)。数据结构包括三方面的内容:逻辑结构、存储结构和数据的运算。
数据的逻辑机构和存储结构是密不可分的两个方面,一个算法的设计取决于所选定的逻辑结构,而算法的实现依赖于所采用的存储结构。
数据结构与数据类型的区别:数据结构考虑了物理实现,数据类型只考了数据的逻辑结构和运算操作。
-
数据类型关注的是单个数据元素的类型和操作,而数据结构关注的是数据元素的组织和相互关系。数据结构强调的是数据元素之间的关系和数据的组织方式,而不仅仅是数据类型。
-
数据类型是构建数据结构的基础,数据结构用来更高效地管理和组织数据。 引入的新的数据结构时,必须借助编程语言所提供的数据类型来描述数据的存储结构。
-
数据类型通常由编程语言提供并且是固定的,而数据结构则可以由程序员根据需要自定义和实现。
-
数据类型主要强调了每一个数据都属于某种数据类型,显式或隐含地规定了数据的取值范围、存储方式以及允许进行的运算。虽然两者在概念的定义上都提到了运算,但是两者的运算还是有区别的。数据类型的运算主要是算数运算、逻辑运算等。而数据结构运算主要是对数据的增删改查等。
1.1.2数据结构三要素
1.数据的逻辑结构
逻辑结构是指数据之间的逻辑关系,即从逻辑关系上的描述。它与数据的存储无关,是独立于计算机的。数据的逻辑结构分为线性结构和非线性结构,线性表
是典型的线性结构;集合、树和图
是典型的非线性结构。数据的逻辑结构分类如图所示
- 集合:结构中的数据元素之间除“同一个集合”外,无其他的关系。
- 线性结构:结构中的数据元素只存在一对一的关系。
- 树状结构:结构中的元素之间存在一对多的关系。
- 图状结构(网状结构):结构中的元素存在多对多的关系。
2.数据的存储结构
存储结构是指数据结构在计算机中的表示(又称映像),也称物理结构。它包括数据元素的表示和关系的表示。数据的存储结构是用计算机语言实现的逻辑结构,它依赖于计算机语言。数据的存储结构主要有顺序存储、链式存储、索引存储和散列存储。
- 顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。其优点是可以实现随机存取,每个元素占用最少的存储空间:缺点是只能使用相邻的一整块存储单元,因此可能产生较多的外部碎片。(顺序表)
- 链式存储。不要求逻辑上相邻的元素在物理位置上也相邻,借助指示元素存储地址的指针来表示元素之间的逻辑关系。其优点是不会出现碎片现象,能充分利用所有存储单元:缺点是每个元素因存储指针而占用额外的存储空间,且只能实现顺序存取。(链表)
- 索引存储。在存储元素信息的同时,还建立附加的索引表。索引表中的每项称为索引项,索引项的一般形式是(关键字,地址)。其优点是检索速度快:缺点是附加的索引表额外占用存储空间。另外,增加和删除数据时也要修改索引表,因而会花费较多的时间。
- 散列存储。根据元素的关键字直接计算出该元素的存储地址,又称哈希(Hash)存储。其优点是检索、增加和删除结点的操作都很快:缺点是若散列函数不好,则可能出现元素存储单元的冲突,而解决冲突会增加时间和空间开销。
3.数据的运算
施加在数据上的运算包括运算的定义和实现。运算的定义是针对逻辑结构的,指出运算的功能;运算的实现是针对存储结构的,指出运算的具体操作步骤。
1.2.算法
1.2.1算法的基本概念
算法(Algorithm)是对特定问题求解步骤的一种描述,它是指令的有限序列,其中的每条指令表示一个或多个操作。此外,一个算法还具有下列五个重要特性:
-
有穷性。一个算法必须总在执行有穷步之后结束,且每一步都可在有穷时间内完成。
-
确定性。算法中每条指令必须有确切的含义,对于相同的输入只能得出相同的输出。
-
可行性。算法中描述的操作都可以通过已经实现的基本运算执行有限次来实现。
-
输入。一个算法有零个或多个输入,这些输入取自于某个特定的对象的集合。
-
输出。一个算法有一个或多个输出,这些输出是输入有着某种特定关系的量。通常,设计一个“好”的算法应考虑达到以下目标:
-
正确性。算法应能够正确地解决求解问题。
-
可读性。算法应具有良好的可读性,以帮助人们理解。
- 健壮性。算法能对输入的非法数据做出反应或处理,而不会产生莫名其妙的输出。
- 高效率与低存储量需求。效率是指算法执行的时间,存储量需求是指算法执行过程中所需要的最大存储空间,这两者都与问题的规模有关。
-
1.2.2算法效率的度量
通过时间复杂度和空间复杂度来描述的。
1.时间复杂度
一个语句的频度是指该语句在算法中被重复执行的次数。算法中所有语句的频度之和记为
T
(
n
)
T(n)
T(n),它是该算法问题规模n的函数,时间复杂度主要分析
T
(
n
)
T(n)
T(n)的数量级。算法中基本运算(最深层循环中的语句)的频度与
T
(
n
)
T(n)
T(n)同数量级,因此通常将算法中基本运算的执行次数的数量级作为该算法的时间复杂度。于是,算法的时间复杂度记为
T
(
n
)
=
O
(
f
(
n
)
)
T(n)=O(f(n))
T(n)=O(f(n))
式中, O O O的含义是 T ( n ) T(n) T(n)的数量级,其严格的数学定义是:若 T ( n ) T(n) T(n)和 f ( n ) f(n) f(n)是定义在正整数集合上的两个函数,则存在正常数 C C C和, n 0 {n_0} n0使得当 n ≥ n 0 n≥{n_0} n≥n0时,都满足 0 ≤ T ( n ) ≤ C f ( n ) 0≤T(n)≤Cf(n) 0≤T(n)≤Cf(n)。算法的时间复杂度不仅依赖于问题的规模n,也取决于待输入数据的性质(如输入数据元素的初始状态)。例如,在数组A[0…n-1]中,查找给定值k的算法大致如下:
i=n-1;
whi1e(i>=0&&(A[i]!=k))
i--;
return i;
该算法中语句3(基本运算)的频度不仅与问题规模有关,而且与下列因素有关:
- 若A中没有与k相等的元素,则语句3的频度f(n)=n。
- 若A的最后一个元素等于k,则语句3的频度f(n)是常数0。
最坏时间复杂度是指在最坏情况下,算法的时间复杂度。
平均时间复杂度是指所有可能输入实例在等概率出现的情况下,算法的期望运行时间。最好时间复杂度是指在最好情况下,算法的时间复杂度。
一般总是考虑在最坏情况下的时间复杂度,以保证算法的运行时间不会比它更长。在分析一个程序的时间复杂性时,有以下两条规则:
- 加法规则: T ( n ) = T 1 ( n ) + T 2 ( n ) = O ( f ( n ) ) + O ( g ( n ) ) = O ( max ( f ( n ) , g ( n ) ) ) T(n)=T_1(n)+T_2(n)=O(f(n))+O(g(n))=O(\max(f(n),g(n))) T(n)=T1(n)+T2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n)))
- 乘法规则: T ( n ) = T 1 ( n ) × T 2 ( n ) = O ( f ( n ) ) × O ( g ( n ) ) = O ( f ( n ) × g ( n ) ) T(n)=T_1(n)\times T_2(n)=O(f(n))\times O(g(n))=O(f(n)\times g(n)) T(n)=T1(n)×T2(n)=O(f(n))×O(g(n))=O(f(n)×g(n))
例如,设a{}、b{}、c{}三个语句块的时间复杂度分别为 O ( 1 ) 、 O ( n ) 、 O ( n 2 ) O(1)、O(n)、O(n^2) O(1)、O(n)、O(n2),则
a{
b{}
c{}
} //时间复杂度为o(n^2),满足加法规则
a{
b{
c{}
}
} //时间复杂度为o(n^3),满足乘法规则
常见的渐进时间复杂度为
O
(
1
)
<
O
(
log
2
n
)
<
O
(
n
)
<
O
(
n
log
2
n
)
<
O
(
n
2
)
<
O
(
n
3
)
<
O
(
2
n
)
<
O
(
n
!
)
<
O
(
n
n
)
O(1) < O({\log _2}n) < O(n) < O(n{\log _2}n) < O({n^2}) < O({n^3}) < O({2^n}) < O(n!) < O({n^n})
O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
- 可以忽略加法常数
O ( 2 n + 3 ) = O ( 2 n ) O(2n + 3) = O(2n) O(2n+3)=O(2n)
- 与最高次项相乘的常数可忽略
O ( 2 n 2 ) = O ( n 2 ) O(2n^2) = O(n^2) O(2n2)=O(n2)
- 最高次项的指数大的,函数随着 n 的增长,结果也会变得增长得更快
O ( n 3 ) > O ( n 2 ) O(n^3) > O(n^2) O(n3)>O(n2)
- 判断一个算法的(时间)效率时,函数中常数和其他次要项常常可以忽略,而更应该关注主项(最高阶项)的阶数
O ( 2 n 2 ) = O ( n 2 + 3 n + 1 ) O ( n 3 ) > O ( n 2 ) O(2n^2) = O(n^2+3n+1)\\ O(n^3) > O(n^2) O(2n2)=O(n2+3n+1)O(n3)>O(n2)
2.空间复杂度
算法的空间复杂度 S(n)定义为该算法所需的存储空间,它是问题规模
n
n
n的函数,记为
S
(
n
)
=
O
(
g
(
n
)
)
S(n)=O(g(n))
S(n)=O(g(n))
一个程序在执行时除需要存储空间来存放本身所用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和存储一些为实现计算所需信息的辅助空间。若输入数据所占空间只取决于问题本身,和算法无关,则只需分析除输入和程序之外的额外空间。例如,若算法中新建了几个与输入数据规模
n
n
n相同的辅助数组,则空间复杂度为
O
(
n
)
O(n)
O(n)。
算法原地工作是指算法所需的辅助空间为常量,即 O ( 1 ) O(1) O(1)。