1 绪论
1.1 数据结构的基本概念
1.1.0 开篇_数据结构在学什么
用程序代码把现实世界的问题信息化 用计算机高效地处理这些信息从而创造价值
1.1.1 数据结构的基本概念
数据
数据是信息的载体 ,是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程序识别 (二进制0和1)和处理的符号的集合。数据是计算机程序加工的原料。
数据元素、数据项
数据元素 是数据的基本单位,通常作为一个整体进行考虑和处理。一个数据元素可由若干数据项 组成,数据项是构成数据元素的不可分割的最小单位。 要根据实际的业务需求来确定什么是数据元素、什么是数据项。
数据结构、数据对象
结构——各个元素之间的关系。 数据结构 是相互之间存在一种或多种特定关系 的数据元素的集合。数据对象 是具有相同性质 的数据元素的集合,是数据的一个子集。
数据类型、抽象数据类型
数据类型 是一个值的集合和定义在此集合上的一组操作的总称。
原子类型。其值不可再分的数据类型(bool、int、double等)。 结构类型。其值可以再分解为若干成分(分量)的数据类型(struct)。 抽象数据类型 (Abstract Data Type, ADT)是抽象数据组织和与之相关的操作。
ADT用数学化的语言定义数据的逻辑结构、定义运算。与具体的实现无关。 确定了存储结构,才能实现数据结构。
1.1.2 数据结构的三要素
逻辑结构
数据元素之间的逻辑关系是什么。 集合、线性结构、树形结构、图形结构(网状结构)。
集合 :各个元素同属一个集合,并无其他关系。线性结构 :数据元素之间是一对一的关系。除了第一个元素,所有元素都有唯一前驱;除了最后一个元素,所有元素都有唯一后继。树形结构 :数据元素之间是一对多的关系。图结构 :数据元素之间是多对多的关系。
物理结构(存储结构)
如何用计算机表示数据元素的逻辑关系。 顺序存储、链式存储、索引存储、散列存储(后三者统称非顺序存储)。
顺序存储 :把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。链式存储 :逻辑上相邻的元素在物理位置上可以不相邻,借助指示元素存储地址的指针来表示元素之间的逻辑关系。索引存储 :在存储元素信息的同时,还建立附加的索引表。索引表中的每项称为索引项,索引项的一般形式是(关键字,地址)。散列存储 :根据元素的关键字直接计算出该元素的存储地址,又称哈希(Hash)存储 。 若采用顺序存储 ,则各个元素在物理上必须是连续的 ;若采用非顺序存储 ,则各个数据元素在物理上可以是离散的 。 数据的存储结构 会影响存储空间分配的方便程度 。 数据的存储结构 会影响对数据运算的速度 。
数据的运算
施加在数据上的运算包括运算的定义和实现。
运算的定义 是针对逻辑结构 的,指出运算的功能。运算的实现 是针对存储结构 的,指出运算的具体操作步骤。
在探讨一种数据结构时
①定义逻辑结构(数据元素之间的关系) ②定义数据的运算(针对现实需求,应该对这种逻辑结构进行什么样的运算) ③确定某种存储结构,实现数据结构,并实现一些对数据结构的基本运算。
1.2 算法和算法评价
1.2.1 算法的基本概念
什么是算法
程序 = 数据结构 + 算法
数据结构:如何把现实世界的问题信息化,将信息存进计算机。同时还要实现对数据结构的基本操作。 算法:如何处理这些信息,以解决实际问题。
算法的特性
有穷性 。一个算法必须总在执行有穷步之后结束,且每一步都可在有穷时间内完成。
确定性 。算法中每条指令必须有确切的含义,对于相同的输入 只能得到相同的输出 。可行性 。算法中描述的操作都可以通过已经实现的基本运算执行有限次 来实现。输入 。一个算法有零个或多个输入 ,这些输入取自于某个特定的对象的集合。输出 。一个算法有一个或多个输出 ,这些输出是与输入有着某种特定关系的量。
“好”算法的特质
正确性 ,算法应能够正确的解决问题。可读性 。算法应具有良好的可读性,以帮助人们理解。健壮性 。输入非法数据时,算法能适当地做出反应或进行处理,而不会产生莫名其妙的输出结果。高效率 与低存储量需求
高效率:执行速度快。时间复杂度低。 低存储量需求:不费内存。空间复杂度低。
1.2.2 算法效率的度量
时间复杂度
常考 让算法先运行,事后统计运行时间?不行 。因为
算法运行时间和机器性能有关,如:超级计算机vs.单片机 算法运行时间和编程语言有关,越高级的语言执行效率越低 算法运行时间和编译程序产生的机器指令质量有关 有些算法是不能事后再统计的,如:导弹控制算法 算法时间复杂度
事前预估 算法时间开销T(n)与 问题规模n 的关系(T表示“time”) 例:
void loveYou ( int n) {
int i= 1 ;
while ( i<= n) {
i++ ;
printf ( "I Love You %d\n" , i) ;
}
printf ( "I Love You More Than %d\n" , n) ;
}
加法规则
T(n) = T1(n) + T2(n) = O(f(n)) + O(g(n)) = O(max(f(n),g(n))) 多项相加,只保留最高阶的项,且系数变为1 乘法规则
T(n) = T1(n) × T2(n) = O(f(n)) × O(g(n)) = O(f(n)×g(n)) 多项相乘,都保留 O(1) < O(log2 n) < O(n) < O(nlog2 n) < O(n2 ) < O(n3 ) < O(2n ) < O(n!) < O(nn ) ,常对幂指阶 结论1:顺序执行的代码只会影响常数项,可以忽略 结论2:只需挑循环中的一个基本操作分析它的执行次数与n的关系即可 结论3:如果有多层嵌套循环,只需关注最深层循环循环了几次 例:
void loveYou2 ( int flag[ ] , int n) {
printf ( "I Am Iron Man\n" ) ;
for ( int i= 0 ; i< n; i++ ) {
if ( flag[ i] == n) {
printf ( "I Love You %d\n" , n) ;
break ;
}
}
}
int flag[ n] = { 1. . . n} ;
loveYou2 ( flag, n) ;
上述算法,最好时间复杂度 T(n)=O(1),最坏时间复杂度 T(n)=O(n),平均时间复杂度 T(n)=O(n) 最好时间复杂度 是指在最好情况下,算法的时间复杂度最坏时间复杂度 是指在最坏情况下,算法的时间复杂度平均时间复杂度 是指所有可能输入实例在等概率出现的情况下,算法的期望运行时间一般总是考虑在最坏情况下的时间复杂度,以保证算法的运行时间不会比它更长 算法的性能问题只有在n很大时才会暴露出来
空间复杂度
程序运行时的内存需求:程序代码(大小固定,与问题规模无关);数据(变量i,参数n,……) 算法空间复杂度 为S(n) 算法原地工作 ——算法所需内存空间为常量(S(n)=O(1)) 只需关注存储空间大小与问题规模相关的变量 例:
void test ( int n) {
int flag[ n] [ n] ;
int other[ n] ;
int i;
}
void loveYou3 ( int n) {
int a, b, c;
if ( n> 1 ) {
loveYou ( n- 1 ) ;
}
printf ( "I Love You %d\n" , n) ;
}
加法规则
S(n) = S1(n) + S2(n) = O(f(n)) + O(g(n)) = O(max(f(n),g(n))) 多项相加,只保留最高阶的项,且系数变为1 乘法规则
S(n) = S1(n) × S2(n) = O(f(n)) × O(g(n)) = O(f(n)×g(n)) 多项相乘,都保留 O(1) < O(log2 n) < O(n) < O(nlog2 n) < O(n2 ) < O(n3 ) < O(2n ) < O(n!) < O(nn ) ,常对幂指阶