第一章 绪论
1.1数据结构讨论的范畴
Niklaus Wirth:
Algorithm + Data Structures = Programs
程序设计:为计算机处理问题编制一组指令集
算法:处理问题的策略
数据结构:问题的数学模型
例如:数值计算的程序设计问题
结构静力分析计算
——线性代数方程组
全球天气预报
——环流模式方程
非数值计算的程序设计问题
例一:求一组(n个)整数中的最大值
算法:基本操作是“比较两个数的大小”
模型:计算机整数
例二:计算机对弈
算法:对弈的规则和策略
模型:棋盘和棋子的表示
例三:足协的数据库管理
算法:需要管理的项目、如何管理、用户界面
模型:各种各样的表格和数据库
概括地说,数据结构描述现实世界实体的数学模型(非数值计算)及其上的操作在计算机中的表示和实现。
1.2基本概念
一、数据与数据结构
数据:所有能被输入到计算机中,且被计算机处理的符号的集合,计算机操作的对象的总称,是计算机处理的信息的某种特定的符号表示形式。
数据元素:数据中的一个“个体”,数据结构中讨论的基本单位但不是最小单位。
数据项:数据结构中讨论的最小单位,数据元素是数据项的集合。
例如:运动员(数据元素)
姓名 | 俱乐部名称 | 出生日期 | 参加日期 | 职务 | 业绩 |
数据结构:带结构的数据元素的集合
例如,一个含12位数的十进制数可以用三个4位的十进制数表示
3214,6587,9345——a1(3214),a2(6587),a3(9345)
在a1、a2和a3之间存在“次序”关系
<a1,a2>、<a2,a3>
3214,6587,9345 ≠ 6587,3214,9345
a1 a2 a3 a2 a1 a3
又例,2行3列的二维数组{a1,a2,a3,a4,a5,a6}
a1 | a2 | a3 |
a4 | a5 | a6 |
行的次序关系:
Row={<a1,a2>,<a2,a3>,<a4,a5><a5,a6>}
列的次序关系:
Col={<a1,a4>,<a2,a5>,<a3,a6>}
a1 a3 a5 ≠ a1 a2 a3
a2 a4 a6 a4 a5 a6
数据的逻辑结构可归为一下四类:
线性结构
树形结构
图状结构
集合结构
数据结构的形式定义为:
数据结构是一个二元组
Data_Structures = (D,S)
其中:D是数据元素的有限集,S是D上关系的有限集。
数据的存储结构
——逻辑结构在存储器中的映像
数据元素的映像方法:
用二进制位(bit)的位串表示数据元素
关系的映象方法:(表示<x,y>的方法)
顺序映象:以存储位置的相邻表示后继关系
y的存储位置和x的存储位置之间差一个常量C
而C是一个隐含值,整个存储结构中只含数据元素本身的信息
链式映象:以附加信息(指针)表示后继关系
需要用一个和x在一起的附加信息指示y的存储位置
在不同的编程环境中,存储结构可有不同的描述方法,当用高级程序设计语言进行编程是,通常可用高级编程语言中提供的数据类型描述之。
例如:以三个带有次序关系的整数表示一个长整数时,可利用C语言中提供的整数数组类型,定义长整数为:
typedfint Long_int [3]
二、数据类型
在用高级程序语言编写的程序中,必须对程序中出现的每个变量,常量或表达式,明确说明它们所属的数据类型。
数据类型是一个值的集合和定义在此集合上的一组操作的总称。
三、抽象数据类型(Abstract Data Type 简称ADT)
是指一个数学模型以及定义在此数学模型上的一组操作。
ADT有两个重要特征:
数据抽象
用ADT描述程序处理的实体时,强调的是其本质的特征、其所能完成的功能以及它和外部用户的接口(即外界使用它的方法)。
数据封装
将实体的外部特性和其内部实现细节分离,并且对外部用户隐藏其内部实现细节
例如 抽象数据类型复数的定义:
ADT Complex{
数据对象:
D = {e1,e2 | e1,e2 ∈RealSet}
数据关系:
R1 = {<e1,e2>|e1是复数的实数部分,
|e2是复数的虚数部分}
基本操作:
InitComplex(&Z,v1,v2)
操作结果:构造复数Z,其实部和虚部分别被赋以参数v1和v2的值。
DestroyComplex(&Z)
操作结果:复数Z被销毁。
GetReal(Z,&realPart)
初始条件:复数已存在。
操作结果:用realPart返回复数Z的实部值。
GetImag(Z,&ImagPart)
初始条件:复数已存在。
操作结果:用ImagPart返回复数Z的虚部值。
Add(z1,z2,&sum)
初始条件:z1,z2是复数。
操作结果:用sum返回两个复数z1,z2的和值。
}ADT Complex
抽象数据类型的描述方法
抽象数据类型可用(D,S,P)三元组表示
其中,D是数据对象,
S是D上的关系集,
P是对D的基本操作集。
抽象数据类型的表示和实现
抽象数据类型需要通过固有数据类型(高级编程语言中已实现的数据类型)来实现。
1.3算法和算法的衡量
一、算法
算法是为了解决某类问题而规定的一个有限长的操作序列。一个算法必须满足一下五个重要特性:
1.有穷性 对于任意一组合法输入值,在执行有穷步骤之后一定能结束,即:算法证的每个步骤都能在有限时间内完成;
2.确定性 对于每种情况下所应执行的操作,在算法中都有确切的规定,使算法的执行者或阅读者都能明确其含义及如何执行。并且在任何条件下,算法都只有一条执行路径;
3.可行性 算法中的所有操作都必须足够基本,都可以通过已经实现的基本操作运算有限次实现之;
4.有输入 作为算法加工对象的量值,通常体现为算法中的一组变量。有些输入需要在算法执行过程中输入,而有的算法表明上可以没有输入,实际上已被嵌入算法之中;
5.有输出 它是一组与“输入”与确定关系的量值,是算法进行信息加工后得到的结果,这种确定关系即为算法的功能。
二、算法设计的原则
设计算法时,通常应考虑到以下目标:
1.正确性
首先,算法应当满足以特定的“规格说明”方式给出的需求。
其次,对算法是否“正确”的理解可以有以下四个层次:
A.程序中不含语法错误;
B.程序对于几组输入数据能够得出满足要求的结果;
C.程序对于精心选择的、典型、苛刻且带有刁难性的几组输入数据能够得出满足要求的结果;
D.程序对于一切合法的输入数据都能得出满足要求的结果;
通常以第C层意义的正确性作为衡量一个算法是否合格的标准。
2.可读性
算法主要是为了人的阅读与交流,其次才是为计算机执行。因此算法应该易于人的理解;另一方面,晦涩难读的程序易于隐藏较多错误而难以调试。
3.健壮性
当输入数据非法时,算法应当恰当地作出反映或进行相应处理,而不是产生莫名其妙的输出结果。并且,处理出错的方法不应是中断程序的执行,而应是返回一个表示错误或错误性质的值,以便在更高的抽象层次上进行处理。
4.高效率与低存储量需求
通常,效率指的是算法执行时间;存储量指的是算法执行过程中所需的最大存储空间,两者都与问题的规模有关。
三、算法效率的衡量方法和准则
通常有两种衡量算法效率的方法:
事后统计法
缺点:1.必须执行程序
2.其它因素掩盖算法本质
事前分析估算法
和算法执行时间相关的因素:
1.算法选用的策略
2.问题的规模
3.编写程序的语言
4.编译程序产生的机器代码的质量
5.计算机执行指令的速度
算法的时间复杂度:
一个特定算法的“运行工作量”的大小,只依赖于问题的规模(通常用整数量n表示),或者说,它是问题规模的函数。
假如,随着问题规模n的增长,算法执行时间的增长率和f(n)的增长率相同,则可记作:T(n)= O(f(n))
称T(n)为算法的(渐近)时间复杂度
如何估算算法的时间复杂度?
算法 = 控制结构 + 原操作(固有数据类型的操作)
算法的执行时间 = ∑原操作(i)的执行次数×原操作(i)的执行时间
算法的执行时间与原操作执行次数之和成正比
从算法中选取一种对于所研究的问题来说是基本操作的原操作,以该基本操作在算法中重复执行的次数作为算法运行时间的衡量准则。
例一:
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];
}
基本操作:乘法操作
时间复杂度:O(n3)
例二
void select_sort(int a[],int n){
//将a中整数序列重新排列成自小至大有序的整数序列。
for(i = 0;i < n-1; ++ i ){
j = i;
for(k = i + 1;k < n; ++ k)
if (a[k] < a[j]) j = k;
if(j != i) a[j] ←→ a[i]
}
}//select_sort
基本操作:比较(数据元素)操作
时间复杂度:O(n2)
例三
void bubble_sort(int a[],int n){
//将a中整数序列重新排列成自小至大有序的整数序列。
for(i = n-1,change = TRUE; i > 1 && change; -- i){
change = FALSE;
for(j = 0; j < i; ++j)
if(a[j] > a[j + 1]){
a[j] ←→ a[j+1];
change = TRUE;
}
}
}//bubble_sort
基本操作:赋值操作 时间复杂度:O(n2)
四、算法的存储空间需求
算法的空间复杂度
S(n) = O(g(n))
表示随着问题规模n的增大,算法运行所需存储量的增长率与g(n)的增长率相同。
算法的存储量包括:
1.输入数据所占空间;
2.程序本身所占空间;
3.辅助变量所占空间。
若输入数据所占空间只取决于问题本身,和算法无关,则只需要分析除输入和程序之外的辅助变量所占额外空间。
若所需额外空间相对于输入数据量来说是常数,则称此算法为原地工作。
若所需存储量依赖于特定的输入,则通常按最坏情况考虑。