目录
一.数据相关
1.数据元素与数据项
2.数据对象和数据结构
3.数据结构的三要素
(1)逻辑结构
(2)数据运算
(3)物理结构(存储结构)
4.数据类型和抽象数据类型
二.算法
1.算法的特性
2.好的算法的特质
三.算法效率的度量
1.时间复杂度
2.空间复杂度
一.数据相关
1.数据元素与数据项
数据是信息的载体,是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程序识别和处理(二进制0和1)的符号的集合。数据是计算机程序加工的原料。
数据元素是数据的基本单位,通常作为一个整体进行考虑和处理。一个数据元素可由若干数据项组成,数据项是构成数据元素的不可分割的最小单位。
例如下图:
一个微博账号作为一个数据元素,包含了昵称,性别等多个数据项,数据项由更多细分的属性组成,则这样的数据项为组合项。
2.数据对象和数据结构
数据对象是具有相同性质的数据元素的集合,是数据的一个子集。
数据结构是相互之间存在一种或多种特定关系的数据元素的集合。
数据元素之间一定要有某种关系,才能称为数据结构。而数据元素之间只要具有相同的性质就能称为数据对象了。
同一个数据对象里的数据元素,可以组成不同的数据结构。例如数据元素之间的关系可以用线性数据结构表示,也可以用网状数据结构表示。
同样的数据元素,可组成不同的数据结构。
不同的数据元素,可组成相同的数据结构。
可以看到,每个数据元素包含的数据项是千差万别的,但是数据元素之间的结构可能会存在共性。
数据结构研究的就是数据元素之间的关系,和对这些数据元素的操作,而不关心具体的数据项内容。
3.数据结构的三要素
(1)逻辑结构
•集合结构
各个元素同属一个集合,别无其他关系。
•线性结构(一对一)
数据元素之间是一对一的关系。除了第一个元素,所有元素都有唯一前驱;除了最后一个元素,所有元素都有唯一后继。
•树形结构(一对多)
数据元素之间是一对多的关系。
•图状结构(网状结构)(多对多)
数据元素之间是多对多的关系。
(2)数据运算
针对于某种逻辑结构,结合实际需求,定义基本运算。例如,对于线性结构而言,基本运算有:
①查找第i个数据元素。
②在第i个位置插入新的数据元素。
③删除第i个位置的数据元素。
(3)物理结构(存储结构)
数据的存储结构(物理结构)探讨的是如何用计算机表示数据元素的逻辑关系。常见的有以下四种存储结构:顺序存储,链式存储,索引存储,散列存储。
•顺序存储
顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
•链式存储
链式存储逻辑上相邻的元素在物理位置上可以不相邻,借助指示元素存储地址的指针来表示元素之间的逻辑关系。
•索引存储
索引存储。在存储元素信息的同时,还建立附加的紧引表。索引表中的每项称为家引项,索引项的一般形式是(关键字,地址)
•散列存储
散列存储。根据元素的类键字直接计算出该元素的存储地址,又称哈希(Hash)存储。
总结:
1.若采用顺序存储,则各个数据元素在物理上必须是连续的;若采用非顺序存储,则各个数据元素在物理上可以是离散的。
2.数据的存储结构会影响存储空间分配的方便程度。
3.数据的存储结构会影响对数据运算的速度。
例如,采用顺序存储,并且想在b,d之间插入c,那么需要将d及以后的元素都往后移,再把c插入,这样才能保证元素的逻辑关系是正确的。所以需要挪动很多元素
而采用链式存储,哪里有空都可以放入c,只要将b的后项指针指向c,c的后项指针指向d即可。
运算的定义是针对逻辑结构的,指出运算的功能(在第i个位置插入,在第i个位置删除);运算的实现是针对存储结构的,指出运算的具体操作步骤。
4.数据类型和抽象数据类型
数据类型是一个值的集合和定义在此集合上的一组操作的总称。
(1)原子类型。其值不可再分的数据类型。
(2)结构类型。其值可以再分解为若干成分(分量)的数据类型。
定义一个具体的结构类型,表示一个坐标信息。x、y各占 4B,并用补码表示。
<x,y> 是有序对,不可互换。
可进行的操作:加,减,计算到原点的距离。
抽象数据类型(Abstract DataType,ADT)是抽象数据组织及与之相关的操作。若定义一个ADT,就是在“定义”一种数据结构(数据元素之间呈现怎样的逻辑关系,以及能够实现怎样的数据运算)。确定了ADT的存储结构,才能“实现”这种数据结构。数据结构的使用者只需要知道抽象数据类型即可。实现者才需要知道这一结构在计算机内部如何表示以及各种运算在计算机内部如何实现。
二.算法
算法(Algorithm)是对特定问题求解步骤的一种描述,它是指令的有限序列,其中的每条指令
表示一个或多个操作。
1.算法的特性
•有穷性:一个算法必须总在执行有穷步之后结束,且每一步都可在有穷时间内完成。如果你写了一个"死循环"的代码,那他就不是一个算法。
•确定性:算法中每条指令必须有确切的含义,对于相同的输入只能得出相同的输出。
•可行性:算法中描述的操作都可以通过已经实现的基本运算执行有限次来实现。
•输入:一个算法有零个或多个输入,这些输入取自于某个特定的对象的集合。
•输出:一个算法有一个或多个输出,这些输出是与输入有着某种特定关系的量。
算法和程序的区别:
可以较通俗的理解为程序=数据结构+算法
具体地:
算法具有有穷性。一个算法必须总在执行有穷步之后结束,且每一步都可在有穷时间内完成。而程序可以是无穷的。例如,我们可以说微信是一个程序,但是不能说微信是一个算法。
2.好的算法的特质
(1)正确性:算法应能够正确地解决求解问题。
(2)可读性:算法应具有良好的可读性,以帮助人们理解。
(3)健壮性:输入非法数据时,算法能适当地做出反应或进行处理,而不会产生莫名其妙的输出结果。
(4)高效率与低存储量需求:高效率即花的时间少,时间复杂度低。低存储需求是不费内存。空间复杂度低。
三.算法效率的度量
如何评估算法的时间开销,可以先让算法运行,事后再统计运行时间,这会出现什么问题?
•程序代码的执行速度和机器性能有关,如:超级计算机和单片机的机器性能天差地别。
•和编程语言有关,越高级的语言执行效率越低。
•和编译程序产生的机器指令质量有关。
•有些算法是不能事后再统计的,如:导弹控制算法。
所以在评价算法优劣时,需要排除与算法本身无关的外界因素。如机器性能,编程语言等,并且需要能够实现估计算法优劣。
1.时间复杂度
事前预估算法时间开销T(n)与问题规模n的关系(T表示“time”)
例如,下面的代码:
其时间开销与问题规模n的关系如下:
当问题规模n足够大时,只考虑阶数高的部分即可。大O表示“同阶数量级。即:当n→∞时,二者之比为常数。
加法规则:
多项相加,只保留最高阶的项,且系数变为1。
对于阶数的高低,需要记住(常对幂指阶):
乘法规则:
多项相乘,都保留。
举个例子:
,那么他的时间复杂度为n^3。
我们上面分析时间复杂度是一行一行进行分析的,若有好几千行代码,要怎么办?这就需要用到以下结论:
结论1:顺序执行的代码只会影响常数项,可以忽略
结论2:只需挑循环中的一个基本操作分析它的执行次数与n的关系即可。结论3:如果有多层嵌套循环,只需关注最深层循环循环了几次。
练习1:
这里i的变化依次为i=2,4,8,16····,设最深层循环的语句频度(总共循环的次数)为x,那么i=2^x,由循环条件可知,循环结束时刚好满足2^x>n
,所以当时,刚好满足2^x>n
练习2:
在乱序的数中找到元素n
最好情况:元素n在第一个位置 -----最好时间复杂度 T(n)=O(1)
最坏情况:元素n在最后一个位置 -----最坏时间复杂度 T(n)=O(n)
平均情况:假设元素n在任意一个位置的概率相同为n/1 ----平均时间复杂度 T(n)=O(n)
最坏时间复杂度:最坏情况下算法的时间复杂度。
平均时间复杂度:所有输入示例等概率出现的情况下,算法的期望运行时间
最好时间复杂度:最好情况下算法的时间复杂度
一般只会度量最坏情况与平均情况,最好情况作为参考意义不大。
2.空间复杂度
程序运行时需要两个部分的内存空间,一是程序代码,他的大小固定,与问题规模无关。二是数据部分,用来存放程序中的局部变量以及参数等,这一部分才会影响空间复杂度。
例如下面的代码:
无论问题规模怎么变,算法运行所需的内存空间都是固定的常量,算法空间复杂度为:
S(n)= O(1)
注:s表示"Space"
也就是算法原地工作----算法所需内存空间为常量。
假设一个int 变量占 4B,则所需内存空间=4(参数n)+4n(数组)+4(参数i)=4n+8
只需关注存储空间大小与问题规模(n)相关的变量:S(n)=O(n)
同理:
下面这段代码的空间复杂度为S(n)=O(n^2)
下面这段代码的空间复杂度为S(n)=O(n^2)+O(n)+O(1)=O(n^2)
以上例子都是由变量引起的内存空间的开销,其实还有一种情况也会带来内存开销---函数递归调用:
如下图所示,每一层的调用,都要为该层的局部变量分配相应的内存。
当逐步返回时,系统会根据内存中保存的该层的信息,恢复该函数相应的执行环境,执行函数。
若每一级的调用需要k个字节,而问题规模n又与递归调用的层数(深度)相等。所以当递归调用深度为n时,所需要的内存空间大小为kn个字节,只关注阶数,那么该函数的空间复杂度为S(n)=O(n)
所以对于这一类的递归调用,可以总结,空间复杂度=递归调用的深度
再看下面这段代码:
每一层调用都会声明一个数组,这一数组的存储空间大小与n有关,各级递归调用所需的内存空间大小:
只关注阶数的话,这段代码的空间复杂度即S(n)=O(n^2)