数据(data) 是信息的载体,是描述客观事物的数、字符,以及所有能输入到计算机中并被计算机程序识别和处理的符号的集合.
数据大致可分为 2 类: 数值性数据(包括整数、浮点数、复数、双精度数等) 和 非数值数据(主要包括字符和字符串,以及文字、图形、图像、语音等数据).
数据的基本单位是数据元素,一个数据元素可由若干个数据项组成,它是一个数据整体中相对独立的单位.
在数据元素中数据项可分为两种:初等项(不能分割的最小单位) 和 组合项(可以分割)
在数据处理中所涉及的数据元素之间都不会是孤立的,在它们之间都存在着这样或那样的关系,这种数据之间的关系称为结构.
数据结构由某一数据元素的集合和该集合中数据元素之间的关系组成,记为: D a t a _ S t r u c t u r e = { D , R } Data\_Structure=\{D,R\} Data_Structure={D,R} 其中, D D D 是某一数据元素的集合, R R R 是该集合中所有数据元素之间的关系的有限集合
数据结构包括以下三个方面的内容
- 数据元素之间的逻辑关系,也称为逻辑结构.
- 数据元素及其关系在计算机内存中的表示(又称为映像),称为数据的物理结构或数据的存储结构.
- 数据的运算和实现,即对数据元素可以施加的操作以及这些操作在相应的存储结构上的实现
依据数据元素之间关系的不同,数据结构分为两大类:线性结构和非线性结构.(这是按照逻辑结构的一种分类)
线性结构也称为线性表,在这种结构中所有数据元素都按照某种次序排列在一个序列中.
对于线性结构类中每一数据元素,除第一个元素外,其他每一个元素都有一个且仅有一个直接前驱,第一个数据元素没有前驱. 除最后一个元素外,其他每一个元素都有且仅有一个直接后继,最后一个元素没有后继.
根据对线性结构中数据元素存取方法的不同,又可分为直接存取结构、顺序存取结构和字典结构.
在非线性结构中各个数据元素不再保持在一个线性序列中,每个数据元素可能与零个或多个其他元素发生联系.
根据关系不同,可分为层次结构和群结构:
层级结构(hierarchical structure) 是按层次划分的数据元素的集合,指定层次上元素可以有零个或多个处于下一层次上的直接所属下层元素. 树形结构就是典型的层次结构.
群结构(group structrue) 中所有元素直接并无顺序关系. 集合就是一种群结构,在集合中没有重复的元素. 此外,还有图结构和网络结构.
逻辑结构的第二种分类方式——四种基本逻辑结构
- 集合结构: 结构中的数据元素之间除了同属于一个集合的关系外,无任何其他关系.
- 线性结构: 结构中的数据元素之间存在着一对一的线性关系.
- 树形结构: 结构中的数据元素之间存在着一对多的层次关系.
- 图状结构或网状结构: 结构中的数据元素之间存在着多对多的任意关系.
存储结构的四个种类
- 顺序存储结构: 用一组连续的存储单元依次存储数据元素,数据元素之间的逻辑关系由元素的存储位置来表示. 通常借助一维数组来描述.
- 链接存储结构: 用一组任意的存储单元存储数据元素,数据元素之间的逻辑关系用指针来表示. 通常借助指针来实现.
- 索引存储结构: 在存储结点信息的同时,还建立附加的索引表.
- 散列存储结构: 根据结点的关键字直接计算出该结点的存储地址.
在使用高级程序设计语言编写程序时,必须对程序中出现的每个变量、常量或表达式,明确说明它们所属的数据类型.
一些最基本数据结构可以用数据类型来实现,如数组、字符串等;
而另一些常用的数据结构,如栈、队列、树、图等,不能直接用数据类型来表示.
高级语言中的数据类型明显地或隐含地规定了在程序执行期间变量和表达的所有可能的取值范围,以及在这些数值范围上所允许进行的操作.
定义: 数据类型是一组性质相同的值的集合以及定义于这个值集合上的一组操作的总称.
数据类型 = 值的集合 + 值集合上的一组操作 数据类型 = 值的集合 + 值集合上的一组操作 数据类型=值的集合+值集合上的一组操作
定义: 是指一个数学模型以及定义在此数学模型上的一组操作.
- 由用户定义,从问题抽象出数据类型(逻辑结构).
- 还包括定义在数据模型上的一组抽象运算(相关操作).
- 不考虑计算机内的具体存储结构与运算的具体实现算法.
抽象数据类型的形式定义
抽象数据类型可用 ( D , S , P ) (D,S,P) (D,S,P) 三元组表示,其中:
D D D 是数据对象;
S S S 是 D D D 上的关系集;
P P P 是对 D D D 的基本操作集.
一个抽象数据类型的定义格式
定义伪代码如下:
ADT 抽象数据类型{
数据对象:<数据对象的定义>
数据关系:<数据关系的定义>
基本操作:<基本操作的定义>
}ADT 抽象数据类型名
基本操作定义格式说明:
参数表:
赋值参数:只为操作提供输入值
引用参数:以 & 打头,除可提供输入值外,还将返回操作结果
初始条件:
描述操作执行之前数据结构和参数应满足的条件,若不满足,则操作失败,并返回相应出错信息. 若初始条件为空,则省略之.
操作结果:
说明操作正常完成之后,数据结构的变化状况和应返回的结果.
抽象数据类型(ADT)定义举例01:Circle的定义
ADT Circle{
数据对象:D = {r,x,y|r,x,y均为实数}
数据关系:R = {<r,x,y>|r是半径,<x,y>是圆心坐标}
基本操作:
Circle(&C,r,x,y)
操作结果:构造一个圆
double Area(C)
初始条件:圆已存在
操作结果:计算面积
double Circumference(C)
初始条件:圆已存在
操作结果:计算周长
}ADT Circle
抽象数据类型(ADT)定义举例02:复数的定义
ADT Complex{
D = {r1,r2|r1,r2都是实数}
S = {<r1,r2>|r1是实部,r2是虚部}
assign(&C,v1,v2)
初始条件:空的复数C已存在
操作结果:构造复数C,r1,r2分别被赋以参数v1,v2的值.
destory(&C)
初始条件:复数C已存在
操作结果:复数C已被销毁
}ADT Complex
算法: 一个又穷的指令集,这些指令为解决某一特定任务规定了一个运算序列.
算法要满足的特性:
1). 有输入: 一个算法必须有 0 个或多个输入.
2). 有输出: 一个算法应有一个或多个输出,输出的量是算法计算的结果.
3). 确定性: 算法的每一步都应确切地、无歧义地进行.
4). 有穷性: 一个算法无论在什么情况下都应在执行有穷步后结束.
5). 能行性: 算法中每一条运算都必须是足够基本的.
自然语言: 英语、中文;
流程图: 传统流程图、NS流程图;
伪代码: 类语言;
程序代码: C++语言程序.
算法: 是解决问题的一种方法或一个过程,考虑如何将输入转换成输出,一个问题可以有多种算法.
程序: 是用某种程序设计语言对算法的具体实现.
程序 = 数据结构 + 算法 ⋅ 数据结构通过算法实现操作 ⋅ 算法根据数据结构设计程序 程序=数据结构+算法 \\ \cdot 数据结构通过算法实现操作 \\ \cdot 算法根据数据结构设计程序 程序=数据结构+算法⋅数据结构通过算法实现操作⋅算法根据数据结构设计程序
判断一个算法的优劣,主要有以下几个标准:
1). 正确性(Correctness): 要求算法能够正确地执行预定的功能和性能需求.
2). 可使用性: 要求算法能够很方便的使用.
3). 可读性(Readability): 算法应当是可读的.
4). 效率性(Efficiency): 算法的效率主要指算法执行时计算机资源的消耗.
5). 健壮性(Robustness): 要求在算法中加入对输入参数、打开文件、读文件记录、子程序调用状态进行自动检错、报错并通过与用户对话来纠错的功能.
6). 简单性: 算法的简单性是指一个算法所采用的数据结构和方法的简单程度.
算法效率的度量分为事前估计和后期测试.
后期测试主要通过在算法中某些部位插装时间函数(如: time())来测试算法完成某一规定功能所需的时间.
算法复杂性的度量属于事前估计,可分为空间复杂度度量和时间复杂度度量.
空间复杂度(space complexity) 是指当问题的规模以某种单位从 1 增加到 n n n 时,解决这个问题的算法在执行时所占用的存储空间也以某种单位由 1 增加到 S ( n ) S(n) S(n),则成此算法的空间复杂度为 S ( n ) S(n) S(n);
时间复杂度(time complexity) 是指当问题的规模以某种单位从 1 增加到 n n n 时,解决这个问题的算法在执行时所耗费的时间也以某种单位由 1 增加到 T ( n ) T(n) T(n),则称此算法的时间复杂度为 T ( n ) T(n) T(n).
时间复杂度和空间复杂度有时是矛盾的
下面给出 3 个程序:
程序01:
计算: a + b + b × c + a + b − c a + b + 4.0 a+b+b\times c+\frac{a+b-c}{a+b}+4.0 a+b+b×c+a+ba+b−c+4.0 具体如下:
// 计算 a+b+b*c+(a+b+c)/(a+b)+4.0
float abc(float a, float b, float c){
return a+b+b*c+(a+b+c)/(a+b)+4.0;
}
程序02和程序03:
累加数组 a a a 前 n n n 个元素的值,具体如下:
// 程序02:累加数组 a 前 n 个元素的值的迭代算法
float sum(float a[], const int n){
float s = 0.0;
for(int i = 0;i < n;i++) s += a[i];
return s;
}
// 程序03:累加数组 a 前 n 个元素的值的递归算法
float rsum(float a[], const int n){
if(n <= 0) return 0;
else return rsum(a, n-1) + a[n-1];
}
这些程序所需的存储空间包括两个部分:
1). 固定部分: 这部分空间的大小与输入输出个数多少、数值大小无关.
2). 可变部分: 这部分空间主要包括其与问题规模有关的变量所占空间、递归工作栈所用空间,以及在算法运行过程中通过 n e w new new 和 d e l e t e delete delete 命令动态使用的空间.
分析空间复杂度示例
例 01.
// 将一维数组 a 中的 n 个数逆序存放到原数组中
// 算法01
for(i = 0; i < n/2; i++){
t = a[i];
a[i] = a[n-i-1];
a[n-i-1] = t;
}
// 算法02
for(i = 0; i < n; i++){
b[i] = a[n-i-1];
for(i = 0; i < n; i++)
a[i] = b[i];
}
例 01 的两个算法时间复杂度分别为: S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1) 和 S ( n ) = O ( n ) S(n)=O(n) S(n)=O(n).
算法的运行时间涉及加、减、乘、除、转移、存、取等基本运算. 要想准确地计算总运算时间是不可行的,因此,度量算法的运行时间,主要从程序结构着手,统计算法的程序步数. 简单地说,程序步是指在语法上或语义上有意义的一段指令序列,而且这段指令序列的执行时间与实例特性无关.
一个算法的运行时间是指一个算法在计算机上运行所耗费的时间大致可以等于计算机执行一种简单的操作(如赋值、比较、移动等)所需要的时间与算法中进行的简单操作次数乘积
算法运行的时间 = 单次简单操作时间 × 简单操作次数 算法运行的时间=单次简单操作时间\times 简单操作次数 算法运行的时间=单次简单操作时间×简单操作次数也即算法中每条语句的执行时间之和:
算法运行的时间 = ∑ 每条语句的执行次数 × 该语句执行一次的时间 算法运行的时间=\sum 每条语句的执行次数\times 该语句执行一次的时间 算法运行的时间=∑每条语句的执行次数×该语句执行一次的时间
为了方便比较不同算法的时间效率,我们仅比较它们的数量级.
例如:两个不同的算法,时间效率分别为:
T 1 ( n ) = 10 n 2 T_{1}(n)=10n^2 T1(n)=10n2 与 T 2 ( n ) = 5 n 3 T_{2}(n)=5n^3 T2(n)=5n3,我们认为前者更优.
算法的渐进时间复杂度
若有某个辅助函数 f ( n ) f(n) f(n),使得当 n n n 趋近于无穷大时, T ( n ) / f ( n ) T(n)/f(n) T(n)/f(n) 的极限值为不等于零的常数,则称 f ( n ) f(n) f(n) 是 T ( n ) T(n) T(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)) 为算法的渐进时间复杂度( O O O 是数量级的符号),简称为时间复杂度.
一般情况下,不必计算所有操作的执行次数,而只考虑算法中的基本操作执行的次数,它是问题规模 n n n 的某个函数,用 T ( n ) T(n) T(n) 表示.
基本语句:
- 算法中重复执行次数和算法的执行时间成正比的语句.
- 对算法运行时间的贡献最大.
- 执行次数最多
问题规模 n n n ( n n n 越大,算法执行时间越长):
- 排序: n n n 为记录数.
- 矩阵: n n n 为矩阵的阶数.
- 多项式: n n n 为多项式的项数.
- 集合: n n n 为元素个数.
- 树: n n n 为树的结点个数.
- 图: n n n 为图的顶点数或边数.
算法中基本语句重复执行的次数是问题规模 n n n 的某个函数 f ( n ) f(n) f(n),算法的时间量度记作: T ( n ) = O ( f ( n ) ) T(n)=O(f(n)) T(n)=O(f(n)).
它表示随着 n n n 的增大,算法执行的时间的增长率和 f ( n ) f(n) f(n) 的增长率相同,称渐进时间复杂度.
分析算法时间复杂度的基本方法
分析时间复杂度示例
例 01.
x = 0;
y = 0;
for(int k = 0; k < n; k++) // n+1
x++; // n
for(int i = 0; i < n; i++) // n+1
for(int j = 0; j< n; j++) // n*(n+1)
y++; // n^2
例 01 的算法时间复杂度为: T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2).
例 02.
void exam(float x[][], int m, int n){
float sum[];
for(int i = 0; i < m; i++){
sum[i] = 0.0;
for(int j = 0; j < n; j++){
sum[j] += x[i][j]; // 基本语句执行 m*n 次
}
}
for(i = 0; i < m; i++){
cout << i << ":" << sum[i] << endl;
}
}
例 02 的算法时间复杂度为 F ( n ) = O ( m n ) F(n)=O(mn) F(n)=O(mn).
例 03.
for(i = 1; i <= n; i++){
for(j = 1; j <= n; j++){
c[i][j] = 0;
for(k = 1; k <= n; k++)
c[i][j] = c[i][j] + a[i][k]*b[k][j]; // 基本语句执行 n^3 次
}
}
例 03 的算法时间复杂度为 F ( n ) = O ( n 3 ) F(n)=O(n^3) F(n)=O(n3).
例 04.
for(i = 1; i <= n; i++)
for(j = 1; j <= i; j++)
for(k = 1; k <= j; k++)
x = x + 1; // 基本语句执行 n(n+1)(n+1)/6 次
例 04 的算法时间复杂度为 F ( n ) = O ( n 3 ) F(n)=O(n^3) F(n)=O(n3).
例 05.
i = 1;
while(i <= n)
i = i*2; // 设基本语句执行 t 次
因为要满足 i ≤ n i \leq n i≤n,所以 2 t < = n 2^t <= n 2t<=n,所以 t < l o g 2 n t < log_{2}n t<log2n.
则由 2 f ( n ) ≤ n 2^{f(n)} \leq n 2f(n)≤n,即 f ( n ) ≤ l o g 2 n f(n) \leq log_{2}n f(n)≤log2n,取最大值 f ( n ) = l o g 2 n f(n)=log_{2}n f(n)=log2n.
例 05 的算法时间复杂度为 F ( n ) = O ( l o g 2 n ) F(n)=O(log_{2}n) F(n)=O(log2n).
请注意,在有的情况下,算法中的基本操作重复执行的次数还随问题的输入数据集不同而不同.
例 06. 顺序查找
// 在数组 a[i] 中查找值等于e的元素,返回其所在位置
for(i = 0; i < n; i++)
if(a[i] == e) return i + 1;
return 0;
对于例 06,最好情况为 1 1 1 次,最坏情况为 n n n 次,平均时间复杂度为 O ( n ) O(n) O(n)
对于复杂的算法,可以将它分成几个容易估算的部分,然后利用 O O O 的加法法则和乘法法则,计算算法的时间复杂度:
加法规则:
T ( n ) = T 1 ( n ) + T 2 ( n ) = O ( f 1 ( n ) ) + O ( f 2 ( n ) ) = O ( m a x ( f 1 ( n ) , f 2 ( n ) ) ) T(n)=T_{1}(n)+T_{2}(n)=O(f_{1}(n))+O(f_{2}(n))=O(max(f_{1}(n),f_{2}(n))) T(n)=T1(n)+T2(n)=O(f1(n))+O(f2(n))=O(max(f1(n),f2(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))
数据结构是指 数据元素的组织形式
数据在计算机存储器内表示时,物理地址与逻辑地址不相同的,称之为 链式存储形式
树形结构是数据结构之间存在一种 一对多关系
设语句 x++ 的时间是单位时间,则以下语句的时间复杂度为: O ( n 2 ) O(n^2) O(n2)
for(i = 1;i <= n;i++)
for(j = 1;j <= n;j++)
x++;
算法分析的目的是 分析算法的效率以求改进
算法分析的两个主要方面是 空间复杂度和时间复杂度
计算机算法指的是 解决问题的有限运算序列
计算机算法具备输入,输出和 可行性,确定性和有穷性 五个特性
数据在计算机内有链式和顺序两种存储方式,在存储空间使用的灵活性上,链式存储比顺序存储要 高
数据结构只是研究数据的逻辑结构和物理结构,这种观点是 错误的
计算机内部数据处理的基本单位是 数据元素
在存储数据时,通常不仅要存储各数据元素,还要存储 数据元素之间的关系
可以用 抽象数据类型 定义一个完整的数据结构
下列数据结构中,不属于线性结构的是 树
以下关于数据结构的说法中,正确的是 数据的逻辑结构独立于其存储结构
某算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),表明算法的 执行时间与 n 2 n^2 n2 成正比
设 n 是描述问题的规模的非负整数,下面程序段的时间复杂度为 O ( l o g 2 n ) O(log_{2}n) O(log2n)
x = 2;
while(x < n/2)
x = x*2
下面程序段的时间复杂度为 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n)
count = 0;
for(k = 1;k <= n;k *= 2)
for(j = 1;j <= n;j++)
count++;
求整数 n 的阶乘的算法如下,其时间复杂度为 O ( n ) O(n) O(n)
int fact(int n){
if(n <= 1)
return 1;
else
return n*fact(n-1)
}
一个算法所需的时间由下述递推方程表示:
T ( n ) = { 1 , n = 1 2 T ( n 2 ) + n , n > 1 T(n)=\begin{cases} 1, & n=1 \\ 2T(\frac{n}{2})+n, & n>1 \\ \end{cases} T(n)={1,2T(2n)+n,n=1n>1
式中, n n n 是问题的规模,为简单起见,可以假设 n n n 是 2 的次幂.
试求该算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2)