1.3 抽象数据类型的表示与实现
- 抽象数据类型独立于具体实现,将数据和操作封装在一起,使得用户程序只能通过抽象数据类型定义的某些操作来访问其中的数据,从而实现信息隐藏。
- 抽象数据类型和类的概念实际上反映了程序或软件设计的两层概念:抽象数据类型相当于在概念层(或称抽象层 )上描述问题,而类相当于在实现层上描述问题。
类C语言对其做简要说明:
- 预定义常量及类型:
-
//函数结果状态代码 #define ok 1 #define ERROR 0 #definr OVERFLOW -2 //Status是函数返回值类型,其值是函数结果状态代码。 typedef int Status
-
数据结构的表示(存储结构)用类型定义(typedef)描述;数据元素类型约定为ElemType,由用户在使用该数据类型时自行定义。
-
基本操作的算法都用如下格式的函数来描述:
函数类型 函数名(函数参数表){ //算法说明 语句序列 }//函数名
-
当函数返回值为函数结果状态代码时(第一条代码块的内容),函数定义为Status类型。
-
在形参表中,以“&”打头的参数即为引用参数。传递引用给函数与传递指针的效果是一样的,形参变化实参也发生变化,但引用使用起来比指针更加方便、高效。
-
内存的动态分配与释放:
-
使用new 和delete动态分配和释放内存空间:
-
分配空间:指针变量 = new 数据类型;
-
释放空间:delete 指针变量;
-
-
-
赋值语句:
-
简单赋值:变量名 = 表达式;
-
串联赋值:变量名 1 = 变量名 2 = 。。。 变量名 n = 表达式;
-
成组赋值:(变量名1,。。。,变量名n)=(表达式1,。。。,表达式n);
-
结构赋值:结构名1 = 结构名 2 ;结构名 =(值1,值2,。。。,值n);
-
条件赋值:变量名 = 条件表达式?表达式T:表达式F;(条件成立返回T)
-
交换赋值: 变量名 1 <--> 变量名 2;
-
-
选择语句:
-
条件语句 1 :if(表达式)语句;
-
条件语句 2 :if(表达式)语句;else语句;
-
开关语句(Switch):
Switch(表达式){ case值1:语句序列1;break; case值2:语句序列2;break; ...... case值n:语句序列n;break; default: 语句序列 n+1; }
-
-
循环语句:
-
for语句;
-
while语句;
-
do-while语句;(执行在前条件在后)
-
-
结束语句;
-
return 表达式;
-
return;
-
case 或循环结束语句break;
-
异常结束语句 exit (异常代码);
-
-
输入输出语句使用C++流式输入输出的形式:
-
输入语句:cin>>变量1>>.....>>变量n;
-
输出语句:cout<<表达式1<<.....<<表达式n;
-
-
基本函数:
-
求最大值 Max (表达式1,.....,表达式n);
-
求最小值 Min (表达式1,......,表达式n);
-
下面以复数为例,给出一个完整的抽象数据类型的定义、表示和实现。
(1)定义部分:
ADT Complex{
数据对象:D={e1,e2|e1,e2∈R,R是实数集}
数据关系:S={<e1,e2>|e1是复数实部,e2是复数虚部}
基本操作:
Creat(&C,x,y)
操作结果:构成复数C,其实部和虚部分别被赋以参数x和y的值。
GetReal(C)
初始条件:复数C已经存在。
操作结果:返回复数C的实部值。
GetImag(C)
初始条件:复数C已经存在。
操作结果:返回复数C的虚部值。
Add(C1,C2)
初始条件:C1,C2是复数。
操作结果:返回两个复数C1,C2的和。
Sub(C1,C2)
初始条件:C1,C2是复数。
操作结果:返回两个复数C1,C2的差。
}ADT Complex
(2)表示部分,复数的存储表示(用类型定义typedef描述)
typedef struct{ //复数类型
float Realpart ; //实部
float Imagepart ; //虚部
}Complex;
(3)实现部分(对应定义部分一步步实现)
void Create(&Complex C, float x , float y){
//构建一个复数
C.Realpart = x;
c.Imagepart = y;
}
float GetReal(Complex C){
//取复数C=x+yi的实部
return C.Realpart;
}
float GetImag(Complex C){
//取复数C=x+yi的虚部
return C.Imagepart;
}
Complex Add(Complex C1,Complex C2){
//求两个复数C1 C2的和、
Complex sum;
sum.Realpart = C1.Realpart + C2.Realpart;
sum.Imagepart = C1.Imagepart + C2.Imagepart;
return sum;
}
Complex Sub(Complex C1,Complex C2){
//求两个复数C1 C2的差,
Complex difference;
difference.Realpart = C1.Realpart - C2.Realpart;
difference.Imagepart = C1.Imagepart - C2.Imagepart;
return difference;
}
1.4 算法和算法分析
算法是研究数据结构的重要途径。
- 算法的定义及特性
- 算法是为了解决某类问题而规定的一个有限长的操作序列。
- 算法必须满足的五个重要特性:
- 有穷性。
- 确定性:在算法中都有确切的规定,不会产生二义性。
- 可行性:算法中的所有操作都可以通过已经实现的基本操作运算执行有限次来实现。
- 输入:一个算法有零个或多个输入。
- 输出:一个算法有一个或多个输出,无输出的算法没有任何意义。
- 评价算法优劣的基本方法
- 正确性。在合理的数据输入下,能够在有限的运行时间内得到正确的结果
- 可读性。首先便于人们理解和交流,其次是机器可执行性。
- 健壮性。当输入非法数据时,好的算法能够适当的做出正确反应或进行相应的处理。
- 高效性。包括时间和空间两方面,可用时间复杂度和空间复杂度来衡量(是衡量算法的两个主要指标)。
- 算法的时间复杂度
- 衡量算法效率的方法主要有两类:事后统计法和事前分析估计法。通常采用事前分析估计法。
- 问题规模:是算法求解问题输入量的多少,是问题大小的本质表现,一般用整数n表示。n越大算法执行时间越长。
- 语句频度:一条语句的重复执行次数。
- 算法的时间复杂度定义
- 为了客观地反映一个算法的执行时间,可以只用算法中的“基本语句”的执行次数来度量算法的工作量。
- 所谓基本语句指得是算法中重复执行次数和算法的执行时间成正比的语句,它的算法运行时间的贡献最大。
- 我们用“O”来表示数量级,一般情况下,算法中基本语句重复执行的次数是问题规模n的某个函数f(n),算法的时间量度记作:T(n)= O(f(n))
- 它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称时间复杂度。
- 数据符号“O”的严格定义:
- 若T(n)和f(n)是定义在正整数集合上的两个函数,则T(n)=O(f(n))表示存在正的常数C和n0,使得当n ≥ n0时都满足0 ≤ T(n) ≤ Cf(n).
- 该定义说明了函数T(n)和f(n)具有相同的增长趋势,并且T(n)的增长至多趋向于函数f(n)的增长。符号“O”用来描述增长率的上限,它表示当问题规模n>n0时,算法的执行时间不会超过f(n).
- 算法的时间复杂度分析案例
- 分析的基本方法为:找出所有语句中语句频度最大的那条语句作为基本语句。
- 在计算算法时间复杂度时,可以忽略所有低次幂项和最高次幂的系数。
- 常量阶:算法的执行时间不随问题规模n的增加而增长,算法中语句频度就是某个常数。即使这个常数再大,算法的时间复杂度都是O(1)。
- 常见的时间复杂度按数量级递增排列依次为:常量阶O(1)、对数阶、线性阶O(n)、线性对数阶、平方价、立方阶、。。。。、k次方阶、指数阶。尽可能选择使用多项式阶的算法,避免使用指数阶的算法。
- 算法空间复杂度
- 算法所需存储空间的量度,渐进空间复杂度,简称空间复杂度,它也是问题规模n的函数,记作:S(n)=O(f(n))
- 若算法执行是所需的辅助空间相对于输入数据量而言是个常量,则称这个算法原地工作,辅助空间为O(1).