第一章 绪论
1.3 抽象数据函数
数据类型:一个值的集合和定义在这个值集上一组操作的总称。
例:C语言中,提供int, char, float, double等基本数据类型,数组、结构体、共用体、枚举等构造数据类型,还有指针、空(void)类型等。
用户也可用typedef自己定义数据类型
typedef struct {
int num;
char name[20];
float score;
} STUDENT;
STUDENT stu1,stu2, *p;
抽象数据类型(ADT):指一个数学模型以及定义在该模型上的一组操作。“抽象”的意义在于数据类型的数学抽象特性。
例:矩阵 +(求转置、加、乘、求逆、求特征值)构成一个矩阵的抽象数据类型
数据结构 +定义在此数据结构上的一组操作 =抽象数据类型
描述方法:
形式定义:我们用一个三元组(D,S,P)来表示一个 抽象数据类型 ,其中D是数据对象,S是D上的关系集,P是对D的基本操作集。
格式: ADT抽象数据类型名{
数据对象:〈数据对象的定义〉
数据关系:〈数据关系的定义〉
基本操作:〈基本操作的定义〉
} ADT抽象数据类型名
基本操作的定义格式:
基本操作名(参数表(赋值参数引用参数,以“&”打头))
初始条件:〈初始条件描述〉
操作结果:〈操作结果描述〉
例:抽象数据类型三元组的定义:
ADT Triplet{
数据对象:D={e1,e2,e3 |e1,e2,e3∈Elemset}
数据关系:R1={
基本操作:
InitTriplet(&T,v1,v2,v3)
操作结果:构造了三元组T,
元素e1,e2和e3分别被赋以
参数v1,v2和v3的值。
DestroyTriplet(&T)
操作结果:三元组T被销毁。
Get(T,i,&e)
初始条件:三元组T已存在, 1<=i<=3.
操作结果:用e返回T的第i元的值。
Put(&T,i,e)
初始条件:三元组T已存在,1<=i<=3.
操作结果:改变T的第i元的值为e。
IsAscending(T)
初始条件:三元组T已存在。
操作结果:如果T的3个元素按升序排列,则返回1,否则返回0。
IsDescending(T)
初始条件:三元组T已存在。
操作结果:如果T的3个元素按降序排列,则 返回1,否则返回0。
Max(T,&e)
初始条件:三元组T已存在。
操作结果:用e返回T的3个元素中的最大值。
Min(T,&e)
初始条件:三元组T已存在。
操作结果:用e返回T的3个元素中的最小值。
}ADT Triplet
1.4 算法和算法分析
算法:是对特定问题求解步骤的描述,是指令的有限序列。
算法五个重要特性:
有穷性:对于任意一组合法输入值,在执行有穷步之后结束,即算法中的每个步骤都能在有限时间内完成;
确定性:每条指令都有确切的含义,在任何条件下算法都只有一条执行路径;
可行性:算法中的所有操作都必须足够基本,都可以通过已经实现的基本操作运算有限次实现之;
有输入:有零个或多个输入,取自特定的对象集合;
有输出:有一个或多个输出,是算法进行信息加工后得到的结果。
算法设计的原则
正确性:在合理的数据输入下,能在有限的运算时间内得到正确结果;
对算法是否“正确”的理解可以有以下四个层次:
a.程序中不含语法错误;
b.程序对于几组输入数据能够得出满足要求的结果;
c.程序对于精心选择的、典型、苛刻且带有刁难性的几组输入数据能够得出满足要求的
结果;
d.程序对于一切合法的输入数据都能得出满足要求的结果;
可读性:易于人对算法的理解;另一方面,晦涩难读的程序易于隐藏较多错误而难以调试;
健壮性: 当输入的数据非法时,算法应当恰当地作出反应或进行相应处理,而不是产生莫名奇妙的输出结果。并且,处理出错的方法不应是中断程序的执行,而应是返回一个表示错误或错误性质的值,以便在更高的抽象层次上进行处理。
高效率与低存储量需求:通常,效率指的是算法执行时间;存储量指的是算法执行过程中所需的最大存储空间。两者都与问题的规模有关。
算法效率的衡量方法和准则
有两种衡量算法效率的方法:
1.事后统计法:利用计算机内记时功能,用一组或多组相同的统计数据区分。
2.事前分析估计法:求出算法的一个时间界限函数。
和算法执行时间相关的因素:
算法选用的策略
问题的规模
编写程序的语言
编译程序产生机器代码质量
机器执行指令速度
时间复杂度:程序运行从开始到结束所需要的时间。
设解决一个问题的规模为n,基本操作被重复执行的次数是n的一个函数f(n),假如,随着问题规模n的增长,算法执行时间的增长率和f(n)的增长率相同,则可记作:
T (n) = O(f(n))
其中T(n)叫算法的渐进时间复杂度,简称时间复杂度。
算法=控制结构+原操作(固有数据类型的操作)
算法的执行时间=∑原操作(i)的执行次数╳原操作(i)执行时间
从算法中选取一种对于所研究的问题来说是基本运算的原操作,以该原操作重复执行的次数作为算法运行时间度衡量准则。
例1:NXN矩阵相乘
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]; }
显然,被称做问题的基本操作的原操作应是其重复执行次数和算法的执行时间成正比的原操作,多数情况下是最深层循环内的语句中的原操作,它的执行次数和包含它的语句频度相同。
例2.{++x;s=0;} T(n)=O(1) 常量阶
例3.for(i=1; i<=n; ++i)
{++x; s+=x;} T(n)= O(n) 线性阶
例4. for(i=2; i<=n; ++i)
for(j=2; j<=i-1; ++j)
{++x; a[i,j]=x;}
++x语句频度为:1+2+3+…+n-2
=(n-1)(n-2)/2
=(n2-3n+2)/2
T(n)= O(n2) 平方阶
Ο(1)<Ο(log2n)<Ο(n)<Ο(n2)<Ο(n3)<Ο(2n)
例5:Void bubble-sort(int a[ ],int n)
for( i=n-1,change=TURE; i>=1 && change; - -i)
{ change=false;
for(j=0; j
if (a[j]>a[j+1])
{ a[j]←→a[j+1]; change=TURE;}
}
最好情况:0次
最坏情况:1+2+3+…+n-1 = n(n-1)/2
平均时间复杂度为:O(n2)
算法的存储空间需求
空间复杂度:算法所需存储空间的度量,记作:S(n)=O(f(n))
算法的存储量包括:(1)输入数据所占空间;
(2)程序本身所占空间;
(3)辅助变量所占空间。
注意:算法的所有性能之间都存在着或多或少的相互影响,因此,当设计一个算法,特别是大型算法时,要综合考虑算法的各项性能、算法的使用频率、算法处理的数据量的大小、算法描述语言的特性及算法运行的机器系统环境等各方面因素,才能设计出比较好的算法。