目录
1引言
1.1什么是数据结构?
数据结构包含两方面的内容,其一是构成集合的数据元素,其二是数据元素之间存在的关系。
数据结构也就是带有结构的数据元素的集合,结构指的是数据元素之间的相互关系,即数据的组织形式。
线性结构
表1-1 成绩单
树型结构
图1-1公司员工结构组织图
图结构
图1-2城市交通图
1.2数据结构研究什么?
数据结构是一个二元组:Data_Structure=(D,R)
其中,D是数据元素的有限集,R是D上关系的有限集合。
数据结构具体应包括三个方面:数据的逻辑结构、数据的物理结构、数据的运算集合。
逻辑结构
数据的逻辑结构是指数据元素之间逻辑关系的描述。根据数据元素之间关系的不同特性,通常有下列四种基本的逻辑结构(如图1-3):
存储结构
存储结构(又称物理结构)是逻辑结构在计算机中的存储映象,是逻辑结构在计算机中的实现(或存储表示),它包括数据元素的表示和关系的表示。
数据结构Data_Structure=(D,R),对于D中的每一数据元素都对应有存储空间中的一个单元,D中全部元素对应的存储空间必须明显或隐含地体现关系R。
逻辑结构与存储结构的关系为:逻辑结构是抽象,存储结构是实现,两者综合起来建立了数据元素之间的结构关系。
数据结构在计算机中的映象,包括数据元素映象和关系映象。关系映象在计算机中可用顺序存储结构或非顺序存储结构两种不同方式来表示。
运算集合
讨论数据结构的目的是为了在计算机中实现所需的操作,施加于数据元素之上的一组操作构成了数据的运算集合。以表1.1为例介绍
数据结构的内容可归纳为三个部分:逻辑结构、存储结构、运算集合。按某种逻辑关系组织起来的一批数据,依一定的映象方式把它存放在计算机存储器中,并在这些数据上定义了一个运算的集合,就构成了一个数据结构。
1.3数据结构的基本概念
1.数据
数据是描述客观事物的数值、字符以及所有其它能输入到计算机中,且能被计算机处理的各种符号的集合。简言之,数据就是计算机化的信息(或存储在计算机中的信息)。
2.数据元素与数据项
数据元素与数据项数据元素是组成数据的基本单位,是数据集合的个体,在计算机中通常作为一个整体进行考虑和处理。一数据元素可由一个或多个数据项组成,数据项是有独立含义的最小单位(不可再分割)。如表1.1所示的成绩表
3. 数据对象
数据对象是性质相同的数据元素的集合,是数据的一个子集。不论数据元素集合是无限集(如整数集),是有限集(如字符集),还是由多个数据项组成的复合数据元素(如成绩表),只要性质相同,都是同一个数据对象。
4.数据类型
数据类型是一组性质相同的值集合以及定义在这个值集合上的一组操作的总称。值集合确定了该类型的取值范围,操作集合确定了该类型中允许使用的一组运算。例如高级语言中的数据类型就是已经实现的数据结构。
2算法及算法的描述
2.1算法规范表示形式
C函数形式表示:
[函数返回值类型] 函数名([形式参数表列])
{变量声明部分;
执行语句部分;}
结构化的程序设计思想是将问题分解为若干子问题,故程序通常由一个主函数和若干子函数构成,一个函数完成特定的功能。一个完整的、可执行的C程序的一般结构如下:
[包含文件语句]
[宏定义语句]
[自定义类型语句]
[所有子函数原型说明]
[子函数1定义]......
[子函数n定义
][主函数定义]
算法是对特定问题求解步骤的一种描述,它是指令的有限序列,其中每一条指令表示一个或多个操作。此外,一个算法还具有下列五个重要特性。
•有穷性
•确定性
•可行性
•输入
•输出
2.2算法(Algorithm)
•算法是解决问题的一种方法或一个过程,是一个由若干运算或指令组成的有穷序列
.•算法—问题
求解问题的算法可以看作是输入实例与输出之间的函数
•算法是指解决问题的一种方法或一个过程。
•算法是若干指令的有穷序列,满足性质:
•(1)输入:有外部提供的量作为算法的输入。
•(2)输出:算法产生至少一个量作为输出。
•(3)确定性:组成算法的每条指令是清晰,无歧义的。
•(4)有限性:算法中每条指令的执行次数是有限的,执行每条指令的时间也是有限的。
•早期计算机使用者只关注算法正确性,忽略时间复杂度问题,发现随着所解问题的规模增大,每次执行时间成百上千倍增长。
变量作用域
程序的编译单位是源程序文件,一个源程序文件可以包含一个或若干个子函数,在函数内定义的变量是内部变量,在函数外定义的变量是外部变量,又称全局变量或全程变量。
全局变量:程序中所有函数都可以访问的量。可以为本文件中其他函数所公用,其作用域从定义该变量的位置开始直到文件结束。
局部变量:只能在本函数中访问的量。只在本函数范围内有效。
参数传递方式
参数传递是函数之间进行信息通信的重要渠道,其参数传递的主要方式有传值和传地址两类。
C语言中调用函数时,实参代替形参的过程是一个单向的传值过程,在编译技术中称为值传递方式。
C语言中指针类型参数传递可以看作是传地址方式。
传值方式参数只为函数提供待处理数据;
传地址方式参数既能为函数提供待处理数据,又能返回函数结果。
函数结果的带出方式
•函数返回值
函数返回值即使用retum返回方式带出一个函数结果值。它的优点是简单明了,缺点是只能传递一个数据信息。
•全局变量
全局变量的方式可以在函数间传递多个不同类型的数据信息,但全局变量是一种隐式参数传递,即全局变量为多个函数共用,一个函数中对全局变量的改变会影响其他函数的调用,使用全局变量必须注意这个问题。因此,全局变量不宜过多。
•传地址参数
传地址参数方式也可以传递多个数据信息,常见的形式有数组名作参数、指针作为参数。
数组名作参数,就是将数组的首地址或指针作为实参和形参。这种方式可以将被调函数中形参数组的所有元素值一次性传递回主调函数。这种方式传递的多个值类型相同。
指针作为参数,这种方式是指针作为形参,某类型变量的地址作为实参。可以一次传递多个不同类型的值,需要传递几个值就对应设计几个形参指针即可。
2.3问题求解(Problem Solving)
每一算法可用一个或多个简化了的C语言(即类C)函数进行描述,简化是针对高级语言的语法细节所做的。最常见的,用伪代码描述算法。
需要注意的是,《数据结构》中的算法,因为用类C描述,所以不等同于C语言程序,若要上机运行某一算法,则必须将其完善为C语言程序。
2.4算法示例:排序问题
•求解排序问题
输入:n个元素序列
输出:一个有序的排列
满足
就像抽扑克牌一样。把后面的每一个数插入到前面的有序序列中,使有序序列扩大到全部。
2.5算法设计的要求
•正确性
•可读性
•健壮性
•效率与低存储量需求
算法的分析
算法效率的度量
算法执行的时间是其对应的程序在计算机上运行所消耗的时间。程序在计算机上运行所需时间与下列因素有关。
(1) 算法本身选用的策略;
(2) 书写程序的语言;
(3) 编译产生的机器代码质量;
(4) 机器执行指令的速度;
(5) 问题的规模。
算法效率(Efficiency)的分析,指的是算法求解一个问题所需要的时间和空间
◼时间资源和空间资源
◼计算模型
Turing机、以及RAM(随机存储器)等
◼算法时间资源的估算
算法执行基本运算(或步数)的数目
2.6图灵和图灵机
3算法的时间复杂度
3.1引言
一个算法的执行时间大致上等于其所有语句执行时间的总和,对于语句的执行时间是指该条语句的执行次数和执行一次所需时间的乘积。所以,可用算法中语句的执行次数来度量一个算法的效率。
插入排序算法的时间复杂度
上文我们讲到插入排序算法为InsertSort(A)
令tj表示在for循环的迭代j中,while循环执行的次数,则有
最好情形:
InsertSort(A)算法的运行时间T(n)可以表示为关于问题规模n的线性函数
最坏情形:
nsertSort(A)算法的运行时间T(n)可以表示为关于问题规模n的二次函数
3.2复杂度与时间无关
我们可以人工计时或插入测量时长的代码来计算一个程序实际上跑了多久。
然而,用实际运行时长来比较两种算法是无意义的,因为它们可能用不同的语言编写,用不同的数据实例,或用了不同的电脑来运行。
◼注意:语句执行一次实际所需的具体时间是与机器的软、硬件环境(机器速度、编译程序质量等)密切相关,与算法设计的好坏无关。
3.3算法时间复杂度的分析
找出运算所需时间和输入规模之间的函数关系。
•输入数据规模的度量模型
通常用输入数据中数字的个数,或输入的集合中元素的个数,n,作为输入的大小。
•运算时间的度量用算法中一个主要的基本运算被执行的次数作为时间复杂度。
度量一个算法的效率应抛开具体机器的软硬件环境(书写程序的语言、编译产生的机器代码质量、机器执行指令的速度属于软硬件环境)。
对于一个特定算法只考虑算法本身的效率,算法自身的执行效率是问题规模的函数。
对同一个问题,选用不同的策略就对应不同的算法,不同的算法对应有各自的问题规模函数,根据这些函数就可以比较(解决同一个问题的)不同算法的优劣。
为什么这样做?
大大简化分析的工作。
◼与实际复杂度之间最多差一个常数因子。这是因为:
(1) 任一语言只提供常数个基本运算。
(2) 不同基本运算所需时间相差一个常数倍。
(3) 通常,一个基本运算所需时间不因数据大小而不同。
◼复杂度好坏取决于输入规模n→时,其增长的快慢。
◼差常数倍因子的两个复杂度函数认为是等阶的。
◼高阶与低阶的两个复杂度在n→时,它们之比一定无界。
◼为了理论上正确和方便,要求算法必须允许有n→的输入规模34
3.4时间复杂度T(n)=O(f(n))
假如解一个问题有两种算法,其时间复杂度分别为f(n)=200n,g(n)=3n它们是同阶的,当n趋向于无穷大时,增长率相同。
◼假如解一个问题有两种算法,其时间复杂度分别为f(n)=3,g(n)=3n,和上面有本质的区别,当n趋向于无穷大时,f(n)和g(n)之比会大于任何一个常数而无限增大。
算法的效率主要取决于算法本身,与计算模型(例如计算机)无关,这样可以通过分析算法的运行时间,从而比较出算法之间的快慢
◼度量一个算法运行时间的三种方式:
◼(1)最坏情况下的时间复杂性Tmax(n) = max{ T(I) | size(I)=n }
◼(2)最好情况下的时间复杂性Tmin(n) = min{ T(I) | size(I)=n }
◼(3)平均情况下的时间复杂性Tavg(n) =
其中I是问题的规模为n的实例,p(I)是实例I出现的概率。
n×n阶矩阵相乘算法中的各条语句以及每条语句的语句频度:
所有语句的总执行次数为Tn=2+3 +2n+1, 即语句总的执行次数是问题的规模(矩阵的阶)n的函数f(n)(Tn= f(n))。进一步地简化,可用Tn表达式中n的最高次幂来度量算法执行时间的数量级,算法的时间复杂度记作:
T(n)=O(f(n))
算法的时间复杂度取决于主要项
上式T(n)=O(f(n)) ,它表示随问题规模n的增大,算法的执行时间的增长率和f(n)的增长率相同,称作时间复杂度。上面算法的时间复杂度为T(n)=O(n3)。
算法中所有语句的总执行次数Tn是问题规模n的函数,即Tn= f(n),其中n的最高次幂项与算法中称作原操作的语句的语句频度对应,原操作是算法中实现基本运算的操作,在上面的算法中的原操作是c[i][j]=c[i][j]+a[i][k]*b[k][j]。
一般情况下原操作由最深层循环内的语句实现。
3.5渐进上界符号O
渐进分析的特点在它注重分析大输入数据的问题,只考虑首项,且忽略首项的系数。我们选择首项是因为随着输入的规模增大,低次幂的项给整体带来的影响越小。
例如对于f(n) = 2+ 100n,我们有:
f(1000) = 2*10002+ 100*1000 = 2.1M,
vs
f(100000) = 2*1000002+ 100*100000 = 20010M。
注意到低次幂的项100n 对于整体有着更小的影响。
忽略首项的系数
假设两个算法分别有2and 30的首项。虽然实际时间会因常数不同而不同,但它们的增长率(growth rate) 是一样的。相比于另一个首项为n3的算法,增长率的不同才是更重要的决定因素。因此,我们在学习算法复杂度的时候可以不用在意首项的系数。
3.6三个数量级的时间复杂度
T(n)随n的增大而增大,增长的越慢,其算法的时间复杂度越低。下列三个程序段中分别给出了原操作count++的三个不同数量级的时间复杂度。
(1)count++ ;其时间复杂度为O(1),称之为常量阶时间复杂度。
(2)for (i=1; i<= n; i++) count++; 其时间复杂度为O(n),是线性阶时间复杂度。
(3)for (i=1; i<= n; i++)for (j=1;j<= n; j++) count++; 其时间复杂度为O(n2),平方阶时间复杂度。
此外,算法能呈现的时间复杂度还有:对数阶O(log2n),指数阶O(2n)等。
(又如以下两个算法,它们所呈现的时间复杂度分别是O(log2n)和O(n×m)。
(4)i=1;
while (i<n)
i=2*i;
(5)for (i=0; i<n; i++)
for (j=0; j<m; j++)
a[i][j]=0;
3.7常见的时间复杂度的比较
常见的时间复杂度
O(1) 常数阶、O(n)线性阶、O(n2)平方阶、O(n3)立方阶、O(2n)指数阶、O(log2n)对数阶与O(nlog2n)。时间复杂度(从小到大排列)的比较如表所示。
3.8最坏时间复杂度
算法中基本操作重复执行的次数还随问题的输入数据集的不同而不同。例如下面冒泡排序算法:
在这个算法中,“交换序列中相邻的两个整数”(a[j] ←→ a[j+1] )为原操作。
当a中初始序列为自小到大有序,原操作的执行次数为0;当初始序列为自大到小有序时,原操作的执行次数为n(n-1)/2。
对于这类算法时间复杂度的分析,一种解决的方法是计算它的平均值,即考虑它对所有可能输入数据集的期望值,此时相应的时间复杂度为算法的平均时间复杂度。
算法的平均时间复杂度是难以确定的,通常的做法是讨论算法在最坏情况下的时间复杂度。例如冒泡排序在最坏情况下(初始序列为自大到小有序时)的时间复杂度就为T(n)=O()。
3.9算法的空间复杂度
采用空间复杂度作为算法所需存储空间的量度,记作:S(n)=O(f (n))
其中n为问题的规模。
程序执行时,除了需存储本身所用的指令,常数,变量和输入数据以外,还需要一些对数据进行操作的辅助存储空间。
其中对于输入数据所占的具体存储量只取决于问题本身,与算法无关,这样只需要分析该算法在实现时所需要的辅助空间单元数就可以了。
算法的执行时间和存储空间的耗费是一对矛盾体,即算法执行的高效通常是以增加存储空间为代价的,反之亦然。不过,就一般情况而言,常常以算法执行时间做为算法优劣的主要衡量指标。算法的空间复杂度不作进一步讨论。
编者能力有限,如有错误欢迎留言交流。
编者能力有限,如有错误欢迎留言交流
编者的其他专栏:
关注编者了解更多