数据结构第01章.docx
《数据结构第01章.docx》由会员分享,可在线阅读,更多相关《数据结构第01章.docx(16页珍藏版)》请在冰豆网上搜索。
数据结构第01章
第一章绪论
1.1数据结构的有关概念
一、基本概念
1.数据(data)人们利用符号对现实世界的事物及其活动所做的描述。
在计算机领域里是指能够输入到计算机中并能被计算机程序处理的符号集合。
可以说,数据就是计算机加工处理的“原料”。
数据的含义十分广泛,例如,在数值计算程序中的整数或者实数;在文字处理程序中的一些符号串等都是数据。
随着技术的进步,数据所描述的内容越来越丰富。
像多媒体技术中涉及到的视频和音频信号,经采集和编码后都能形成被计算机所接受的数据。
在很多场合,人们对数据和信息的引用没有加以区分。
事实上,数据只是信息的符号表示形式,而信息才是数据的含义。
2.数据元素(dataelement)数据的基本单位,也称数据结点。
在计算机程序中通常被作为一个整体进行处理。
有时候,一个数据元素可以由若干个数据项组成(数据项是数据的最小单位)。
例如,数据文件中每一个数据记录就是一个数据元素,而每个数据记录又是由若干个描述客体某一方面特征的数据项组成的。
3.数据对象(dataobject)具有相同特性的数据元素的集合,是数据的一个子集。
例如,自然数的数据对象是集合{1,2,3,…},而由英文字母字符组成的数据对象则为集合{‘A’,‘B’,‘C’,…,‘Z’}。
4.数据结构(datastructure)数据以及数据之间的联系,也称为逻辑结构。
若把存在于数据元素之间的某种联系(即逻辑关系)称为结构,那么,数据结构就是具有结构的数据对象。
为了更确切地描述数据结构,可以用一个形式化定义:
数据结构是一个二元组
Data_Structure=(D,R)
其中,D是数据元素的有限集合,R是D上关系的集合。
5.存储结构(storgestructure)数据结构在计算机中的存储表示(或称映象),又称物理结构。
它包括数据元素的表示和关系的表示。
数据元素之间的关系在计算机中有顺序映象与非顺序映象两种的表示方法,相应的两种存储结构称为顺序存储结构与非顺序存储结构,顺序映象的特点是利用元素在存储器中的相对位置来表示数据元素之间的逻辑关系。
而非顺序映象则是借助元素存储的指针来表示数据元素之间的逻辑关系。
数据的逻辑结构与存储结构是密不可分的两个方面,任何一个算法的设计取决于选定的逻辑结构,而算法的实现则依赖于采用的存储结构。
因此数据的存储结构要能够正确反映数据元素之间的逻辑结构。
6.静态结构与动态结构根据数据的结构(逻辑结构和存储结构)特性在该数据的生存期间的变动情况,可以将数据结构分成静态结构与动态结构。
所谓静态结构就是指数据结构存在期间不发生变动,如数组虽然是一种线性结构,但也是一种静态结构,它不随着操作的进行而改变数组的大小。
而动态结构是指在一定范围内结构的大小要发生变动,如堆栈、队列以及树型结构等都属于动态结构。
7.数据类型(DataType)一个值的集合和定义在这个值集上的一组操作的总称,它是和数据结构密切相关的一个概念。
在用高级程序语言编写的程序中,每个变量、常量或表达式都有一个它所属的确定的数据类型。
类型明显或隐含地规定了在程序执行期间变量或表达式所有可能取值的范围,以及在这些值上允许进行的操作。
例如,C语言中的整型变量,值集为某个区间上的整数(区间大小依赖于不同的机器),定义在其上的操作为:
加、减、乘、除和取模等算术运算。
高级程序语言中的数据类型可分为非结构的原子类型和结构类型两大类。
原子类型的值是不可分解的。
如:
C语言中的基本类型(整型、实型、字符型和枚举类型)、指针类型和空类型。
结构类型的值是由若干成分按某种结构组成的,因此是可以分解的。
例如数组的值由若干分量组成,每个分量可以是整数,也可以是数组等。
在某种意义上,数据结构可以看成是“一组具有相同结构的值”,则结构类型可以看成由一种数据结构和定义在其上的一组操作组成。
“数据类型”的引入使一切用户不必了解的细节都封装在类型中,实现了信息的隐蔽。
例如,用户在使用“整数”类型时,既不需要了解“整数”在计算机内部是如何表示的,也不需要知道其操作是如何实现的。
8.虚拟存储结构数据结构在虚拟处理器中的表示。
由于大部分的书籍对数据结构的操作的讨论都是在高级程序语言的层次上进行的,因此不能直接以内存地址来描述存储结构,但我们可以借用高级程序语言中提供的“数据类型”来描述它,例如在C语言中,可以用“一维数组”类型来描述顺序存储结构,用“指针”来描述链式存储结构。
假如我们把C语言看成是一个执行C指令和C数据类型的虚拟处理器,那末本书中讨论的存储结构是数据结构在C虚拟处理器中的表示。
9.数据结构的图形表示在数据结构(D,R)中,数据对象D中每个数据元素都用图形中的一个顶点(又称结点)表示,关系R中每个序偶都用一条带箭头的连线(又称作有向边或弧)表示,其中序偶的第一元素(称为第二元素前驱结点)为有向边的起始顶点,第二元素(称为第一元素后继结点)为有向边的终止顶点。
二、实例
下面通过图1.1.1所示的一个人事简表构造出一些典型的数据结构,并用图形形象地表示出来。
从而加深对上述概念特别是数据结构这一重要概念的理解。
工号
姓名
性别
职务
教研室
工作时间
发表论文(编号)
01
李明
男
系主任
软件
81.1
A,B
02
马捷
男
教研室主任
软件
85.1
B,C,E,F
03
张华
女
教师
软件
90.8
C,D
04
王大明
男
教师
应用
87.8
A,G
05
王建国
男
教师
应用
75.9
E,I
06
何一民
男
教师
应用
92.2
F,J
07
林红
男
教师
软件
83.8
D,L
08
吴刚
男
教研室主任
应用
86.7
G,H
09
赵小燕
女
教师
应用
95.8
H,I,J,K
10
刘东
男
教师
软件
89.2
L,K
图1.1.1计算机系人事表
图1.1.1中共有10个记录(即数据元素),每一个记录都由工号、姓名、性别、职务和教研室等数据项组成,如果用工号代表整个记录,那么,这有10个记录构成的数据对象是集合D={01,02,03,04,05,06,07,08,09},在集合D上,我们可以定义不同的关系集合R,得到不同的数据结构(D,R):
1.线性结构示例取R={<05,01>,<01,07>,<07,02>,<02,08>,<08,04>,<04,10>,<10,03>,<03,06>,<06,09>}则数据结构L=(D,R)对应的图形如图1.1.2所示。
不难看出,R是按每个教师的工龄从小到大排列的关系。
在该结构中数据元素之间是1对1联系的,即线性关系,把具有这种特点的数据结构叫做线性数据结构。
图1.1.2线性结构示意图
2.树型结构示例取R={<01,02>,<01,08>,<02,03>,<02,10>,<02,07>,<08,04>,<08,05>,<08,06>,<08,09>}则数据结构L=(D,R)对应的图形如图1.1.3所示(因为该图的有向边都是向下,通常省略箭头)。
不难看出,R是人员之间上下级的关系。
该图形象一棵倒着画的树,在这棵树中,最上面的一个没有前驱的结点称为根结点,最下面一层只有前驱没有后继的结点叫做树叶结点,树叶以外的结点叫做树枝结点。
在一棵树中,每个结点有且只有一个前驱结点(除树根结点外),但可以有任意多个后继结点。
这种数据结构的特点是数据元素之间的1对N联系,即层次关系,把具有这种特点的数据结构叫做树型结构,简称树。
图1.1.3线性结构示意图图1.1.4图型结构示意图
3.图型结构示例取R={<01,02>,<01,04>,<02,05>,<02,06>,<02,01>,<02,03>,<03,02>,<03,07>,<04,01>,<04,08>,<05,02>,<05,09>,<06,02>,<06,09>,<07,03>,<07,10>,<08,04>,<08,09>,<09,08>,<09,05>,<09,06>,<09,10>,<10,09>,<10,07>},可以看出R是D上的对称关系,即当在R中时,可以找到也在R中。
把和这两个对称序偶用一个无序对(y,x)或(x,y)来代替,在图形表示中,把x结点和y结点之间两条相反的有向边用一条无向边来代替。
这样R关系可改写为R={(01,02),(01,04),(02,05),(02,06),(02,03),(03,07),(04,08),(05,09),(06,09),(07,10),(08,09),(09,10)}则数据结构L=(D,R)对应的图形如图1.1.4所示。
实际上,R是人员之间的合作(共同发表论文)关系。
从图1.1.4中可以看出,结点之间的联系是M对N联系(M≥0,N≥0),即网状关系,也就是说,每个结点可以有任意多个前驱结点和任意多个后继结点。
把具有这种特点的数据结构叫做图型结构,简称图。
1.2算法及算法分析
1.2.1算法概述
一、算法及基本性质
算法是对待定问题求解步骤的一种描述,它是指令的有限序列,其中每一条指令表示一个或多个操作。
作为一个完整的算法还具有下列五个重要特性:
(1)输入一个算法有零个或多个的输入,这些输入取自于某个特定的对象的集合;
(2)输出一个算法至少有一个输出,这些输出是同输入有着某些特定关系的量。
没有输出的算法是没意义的;
(3)有穷性一个算法必须在执行有穷步之后结束,且每一步都可在有穷时间内完成;
(4)确定性算法中每一条指令必须有确切的含义,无二义性。
并且对于相同的输入只能得出相同的输出。
(5)可行性算法中的操作都是可以通过已经实现的基本运算执行有限次来实现的。
二、算法设计要求
通常设计一个“好”的算法应考虑达到以下目标:
(1)正确性(Correctness)算法应当满足具体问题的需求。
通常一个大型问题的需求,要以特定的规格说明方式给出,它至少应当包括对于输入、输出和加工处理等的明确的无歧义性的描述。
设计或选择的算法应当能正确地反映这种需求。
(2)可读性(Readability)算法主要是为了人的阅读与交流,其次才是机器执行。
可读性好有助于人对算法的理解;晦涩难懂的程序易于隐藏较多错误难以调试和修改。
(3)健壮性(Robustness)当输入数据非法时,算法也能适当地作出反应或进行处理,而不会产生莫明其妙的输出结果。
(4)高效率效率指的是算法执行时间。
对于同一个问题如果有多个算法可以解决,执行时间短的算法效率高。
(5)低存储量存储量需求指算法执行过程中所需要的最大存储空间。
对于同一个问题在相同的数据规模下,如果有多个算法可以解决,存储量少的算法较好。
高效率与低存储量两者是一对矛盾,在算法设计中,应综合考虑。
1.2.2算法描述
有了解决问题的算法思想,如何选择一种合适的语言来描述算法的各个步骤?
通常有以下四种描述工具:
(1)自然语言
(2)流程图
(3)形式化语言
(4)具体的程序设计语言
它们各有优劣,用自然语言来描述的算法,不用掌握别的规则,易于理解,但不直观,对于复杂的算法就难以表达,可能会出现二义性;用采用流程图的形式来描述算法比采用自然语言表达直观了一些,但依然没有解决复杂算法的表达,而且移植性也不好;目前最流行的是采用形式化语言(如类Pascal语言,类C语言,SPARKS语言等)来描述算法,它能正确方便地表达算法思想,不受具体语言语法细节的限制,但用这种语言编写的“程序”(实际上是算法)不能直接在计算机上执行;采用具体的程序设计语言来描述算法,编写的程序能直接执行,但受到具体语言语法细节的限制。
综合考虑以上各种描述工具的特点,本书采用自然语言和C语言相结合进行描述,就是先用自然语言对算法的基本思想或基本步骤做概括性描述,然后用C语言加以具体实现。
基于以下几点考虑:
(1)自然语言对于复杂的步骤表达较难,但对于概括性描述是可以信任的。
(2)本课程是一门操作实用性很强的专业基础课,采用程序设计语言作为描述工具,可以直接对算法的进行调试,通过对各种数据的运行结果进行分析比较,加深对算法的理解,有利于本课程的学习和在实践中对所学知识的应用。
(3)C语言具有丰富的数据类型,能够方便地表示各种数据结构,C语言不管在计算机专业教育方面还是在普及教育方面都越来越流行。
很多程序设计语言的课程都选用C语言。
(4)形式化语言,具有各种程序设计语言共同特点,它的主要特点是省去了一般程序设计语言的细节。
采用C语言作为描述工具,虽然要受到具体语言语法的限制,但我们可以在一些比较繁琐或关键的地方加上注释,同样可以达到形式化语言的效果。
(5)本课程的基础课为程序设计语言,对于学过程序设计语言的人,再去掌握一种不能直接运行的形式化语言,是很多人不愿意接受的。
1.2.3算法分析
同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。
算法分析的目的在于选择合适算法和改进算法。
对一个算法的评价一般从下面四个方面来考虑。
一、正确性
正确性是设计和评价一个算法的前提。
所谓一个正确的算法是指当输人一组合理的数据时,能够在有限的运行时间内得出正确的结果;对于不合理的数据输入,能够给出警告信息。
通过对数据输入的所有可能情况的分析以及上机调试,可以验证算法是否正确;然而,要从理论上证明一个算法的正确性却不是一件容易的事情,而且也不属于本课程主要研究的范围。
故此不作讨论。
二、时间消耗
1.影响时间消耗的因素考虑影响程序在计算机中运行时间的因素有很多,全部加以考虑是没有道理的,因为其中有许多因素是与计算机系统的硬件、软件以及要解决的问题有关的,这些因素容易掩盖算法本身的优劣,因此,通常利用语句的“频度”和算法的渐进时间复杂度这两个与软、硬件无关的量来讨论算法的执行时间消耗。
2.频度及时间复杂度所谓一条语句的频度就是指该语句被执行的次数。
任何算法最终都是被分解成简单操作(如赋值、转向、比较、输入和输出等)来具体执行的,整个算法的频度是指算法中所有这些简单操作对应的语句频度之和。
知道了解决同一个问题的两个不同算法的语句执行次数,就可以比较出它们的时间复杂程度。
这种把语句执行次数的多少作为算法时间度量的分析方法称为频度统计法。
要精确计算出算法的频度有时是相当困难的,实际上也是没必要的,只要大致估计出相应的数量级(Order)即可。
一般情况下,算法频度是问题规模n的函数,用T(n)表示,若有某个辅助函数ƒ(n),使得
=常数≠0
则称函数ƒ(n)与T(n)同阶;或者说,它们只相差一个常数倍,为同一个数量级,记作
T(n)=O(ƒ(n))
称O(ƒ(n))为算法的渐近时间复杂度(AsymptoticTimeComplexity),简称时间复杂度。
实际上时间复杂度就是频度的数量级的表示。
它表示随问题规模n的增大,算法执行时间的增长率和ƒ(n)的增长率相同。
例如求1+2+3+…+n的算法。
sum(intn)
{inti,sum;
sum=0;
for(i=1i<=n;i++)
sum=sum+i;
printf(“%d”,sum);
}
可以改写为由简单语句组成的算法:
sum=0;1次
i=1;1次
loop:
if(i<=n)n+1次
{sum=sum+i;n次
i++;n次
gotoloopn次
}
printf(“%d”,sum);1次
求得频度T(n)=4n+4,取ƒ(n)=n,则
=
=4≠0,因此其数量级表示形式为T(n)=O(n),即该算法的时间复杂度为O(n)。
3.求时间复杂度方法求一个算法的时间复杂度比求算法的频度方便的多,这时只需要分析影响一个算法运行时间的主要部分即可,不必对每一步都进行详细的分析;同时,对主要部分的分析也可简化,一般只要分析清楚循环体内操作的执行次数即可。
例如求两个n×n矩阵相乘的算法
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]+=a[i][k]*b[k][j];
}
算法的主要部分在最底层的循环中,“乘法”运算是简单操作,共执行n3次,因此其时间复杂度为O(n3)。
4.时间复杂度的各种情况考虑一个算法的时间复杂度除了与问题的规模n有关外,还与程序所处理的具体数据集的状态有关。
所以,当分析一个算法的时间复杂度时,要考虑到最好、平均和最坏等各种情况。
例如,从一维数组a[n]中查找其值等于给定值x的算法为
intsearch(a,n,x)
inta[],n,x
{i=0;
while(i=x)
i++;
if(i>n)return0;
elsereturni;
}
此算法的时间复杂度主要取决于循环操作中的条件的判别次数,而判别次数次数不是固定的,对于给定值x,它与数据集在数组a[n]中的具值分布情况有关。
最好情况为:
当数组的第一个元素a[0]的值为给定值x时,只需进行一次比较,循环就结束,其时间复杂度为O
(1);最坏情况为:
当组的第后一个元素a[n-1]的值为给定值x时,需进行n次比较,循环才会结束,其时间复杂度为O(n)。
6.几种时间复杂度比较图1.2.1给出了各种有代表性的时间复杂度,在不同n时对应算法的运行时间。
因算法的实际运行时间随机器而异,所以此表中的时间主要用作相互比较。
T(n)
n
O(log2n)
O(n)
O(nlog2n)
O(n2)
O(n3)
O(n5)
O(2n)
O(n!
)
20
4.3s
20s
86.4s
400s
8ms
3.2ms
1.05s
771世纪
40
5.3s
40s
213s
1600s
64ms
1.7ms
12.7天
2.591032世纪
60
5.9s
60s
354s
3600s
216ms
13min
366世纪
2.641066世纪
图1.2.1时间复杂度与算法运行时间的关系
假设算法的时间复杂度用O(ƒ(n))表示,从图1.2.1中可以得到两点结论:
(1)当ƒ(n)为对数函数、幂函数或它们的乘积时,算法的运行时间是可以接受的,称这些算法为有效算法;当ƒ(n)为指数函数或阶乘函数时,算法的运行时间是不可接受的,称这些算法是无效的算法。
(2)随着n值的增大,各种时间复杂度所对应算法的运行时间的增加速度大不相同,当n足够大后,各种时间复杂度存在着下列关系:
O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<…<O(2n)<O(n!
)
三、存储空间需求
一个算法在计算机存储器上所占用的存储空间应该包括:
存储算法本身所占用的空间,算法的输入/输出数据所占用的空间以及算法在运行过程中临时占用的空间三个方面。
输入/输出数据所占用的存储空间是由解决的问题所决定的,不随算法的不同而改变。
存储算法本身所占用的存储空间与算法书写的长短成正比,要压缩这方面的存储空间,就必须编写出较精炼的算法。
只有算法在运行过程中临时占用的存储空间因算法而异。
分析一个算法所占用的存储空间要综合考虑各方面。
如对于递归算法来说,一般都比较简短,算法本身所占用的存储空间较少,但算法运行时,需要设置一个附加堆栈,从而占用较多的临时工作单元;若写成相应的非递归算法,算法本身可能较长,占用的存储空间也就较多,但算法运行时需要的临时存储单元相对较少。
有的算法只需要占用少量的临时工作单元,而且不随问题规模的大小而改变,我们称这种算法是“就地”进行的,是节省存储的算法;有的算法需要占用的临时工作单元的数目随着问题规模的增大而增大,当问题的规模较大时,算法格占用较多的存储单元。
算法的存储空间需求量比较容易计算,包括局部变量(即算法范围内定义的变量)所占用的存储空间和系统为实现递归(如果算法是递归的话)所使用的堆栈空间两个部分。
算法的空间需求一般用空间复杂度(数量级的形式)给出,记作
S(n)=O(ƒ(n))
四、其他方面
一个算法质量优劣标准的还应包括算法的简洁性、可读性等。
最简单和最直接的算法往往不一定是最有效的(即最节省时间和空间)。
但是,算法的简洁性可以使算法的正确性证明变得比较容易,同时也便于编写、修改、调试和阅读。
因此,编写出尽可能简洁的算法还是应该强调的。
当然,对于那些需要经常使用的算法来说,有效性比简洁性重要。
上面简单讨论了如何从各个方面来分析一个算法质量的优劣。
需要说明的是,这三个方面往往是相互矛盾的,也就是说,有时为了追求较少的运行时间,可能会占用较多的存储空间;当追求占用较少的存储空间时,又可能带来运算时间增加的问题。
因此,只有综合考虑到算法的使用频率、算法的结构化和算法的易读性以及所使用系统的硬件、软件环境等因素,才可能设计出高质量的算法。
习题
1.1何谓数据的逻辑结构?
何谓数据的物理结构?
两者有何联系?
1.2一个数据结构用二元组(D,R)表示时,D,R分别代表什么?
1.3回答下列问题:
(1)数据结构(即数据的逻辑结构)包括那三种类型?
其中那些类型是非线性结构?
(2)在线性结构中,哪些结点没有前驱结点,哪些结点没有后继结点,哪些结点既有前驱结点且有后继结点?
(3)在树型结构中,有三种结点,写出它们的名称及前驱结点和后继结点的情况。
(4)在图型结构中,每个结点的前驱结点数和后继结点数的情况如何?
1.4有下列三种用二元组表示的数据结构,试画出它们分别对应的图形表示,并指出它们分别属于何种结构。
(1)A=(K,R),其中:
K={a,b,c,d,e,f,g,h}
R={,,,,,,}
(2)B=(K,R),其中:
K={a,b,c,d,e,f,g,h}
R={,,,,,,}
(3)C=(K,R),其中:
K={1,2,3,4,5,6}
R={<1,2>,<2,3>,<2,4>,<3,4>,<3,5>,<3,6>,<4,5>,<4,6>}
(5)
1.5一个算法的五个特性是什么?
1.6能影响算法的时间消耗的因素有那些,在进行算法分析时,应考虑那些实质性的因素?
通常用有哪些量可以衡量一个算法的优劣?
写出它们的定义。
1.7当n足够大后,写出下列各种时间复杂度的关系:
O(n2),O(n),O(nlog2n),O(n3),O(2n),O(n!
),O(log2n)
1.8指出下列各算法的功能,求出其时间复杂度及注释语句的频度
(1)intprime(intn)
{inti=2;
while(n%i!
=0&&ii++;/*求该语句的频度*/
if(i>sqrt(n))return
(1);
elsereturn(0);
}
(2)floatsum1(intn)
{intp=1,sum=0,i;
for(i=1;i<=n;i++)
{p=p*i;/*求该语句的频度*/
sum=sum+p;
}
return(sum);
}
(3)floatsum2(intn)
{intp,sum=0,i,j;
for(i=1;i<=n;i++)
{p=1
for(j=1;j<=n;j