抽象数据类型.docx
《抽象数据类型.docx》由会员分享,可在线阅读,更多相关《抽象数据类型.docx(11页珍藏版)》请在冰豆网上搜索。
抽象数据类型
专题1数据结构分类与抽象数据类型
1.1数据结构分类
数据结构讨论现实世界和计算机世界中的数据及其相互之间的联系,这体现在逻辑和存储两个层面上,相应称之为逻辑结构和存储结构。
也就是说,在现实世界中讨论的数据结构是指逻辑结构,在计算机世界中讨论的数据结构是指存储结构,又称为物理结构。
数据的逻辑结构总体上分为4种类型:
集合结构、线性结构、树结构和图结构。
数据的存储结构总体上也分为4种类型:
顺序结构、链接结构、索引结构和散列结构。
原则上,一种逻辑结构可以采用任一种存储结构来存储(表示)。
对于现实世界中的同一种数据,根据研究问题的角度不同,将会选用不同的逻辑结构;对于一种逻辑结构,根据处理问题的要求不同,将会选用不同的存储结构。
对于复杂的数据结构,不论从逻辑层面上还是从存储层面上看,都可能包含有多个嵌套层次。
如假定一种数据结构包含有两个层次,第一层(顶层)的逻辑结构可能是树结构,存储结构可能是链接结构;第二层(底层)的逻辑结构可能是线性结构,存储结构可能是顺序结构。
第一层结构就是数据的总体结构,第二层结构就是第一层中数据元素的结构。
数据的逻辑结构通常采用二元组来描述,其中一元为数据元素的集合,另一元为元素之间逻辑关系的集合,每一个逻辑关系是元素序偶的集合,如就是一个序偶,其中x为前驱,y为后继。
当数据的逻辑结构存在着多个逻辑关系时,通常对每个关系分别进行讨论。
逻辑结构的另一种描述方法是图形表示,图中每个结点表示元素,每条带箭头的连线表示元素之间的前驱与后继的关系,其箭头一端为后继元素,另一端为前驱元素。
数据的存储结构通常采用一种计算机语言中的数据类型来描述,通过建立数据存储结构的算法来具体实现。
数据的逻辑结构或存储结构也时常被简称为数据结构,读者可根据上下文来理解。
下面通过例子来说明数据的逻辑结构。
假定某校教务处的职员简表如表1.1所示。
该表中共有10条记录,每条记录都由6个数据项组成。
此表整体上被看为一个数据,每个记录是这个数据中的数据元素。
由于每条记录的职工号各不相同,所以可把职工号作为记录的关键字,在下面构成的各种数据结构中,将用记录的关键字代表整个记录。
表1.1教务处职员简表
职工号
姓名
性别
出生日期
职务
部门
01
万明华
男
1962.03.20
处长
02
赵宁
男
1968.06.14
科长
教材科
03
张利
女
1964.12.07
科长
考务科
04
赵书芳
女
1972.08.05
主任
办公室
05
刘永年
男
1959.08.15
科员
教材科
06
王明理
女
1975.04.01
科员
教材科
07
王敏
女
1972.06.28
科员
考务科
08
张才
男
1967.03.17
科员
考务科
09
马立仁
男
1975.10.12
科员
考务科
10
邢怀常
男
1976.07.05
科员
办公室
【例1.1】一种数据结构的二元组表示为set=(K,R),其中
K={01,02,03,04,05,06,07,08,09,10}
R={}
在数据结构set中,只存在有元素的集合,不存在有关系,或者说关系为空。
这表明只考虑表中的每条记录,不考虑它们之间的任何关系。
把具有此种特点的数据结构称为集合结构。
集合结构中的元素可以任意排列,无任何次序。
【例1.2】一种数据结构的二元组表示为linearity=(K,R),其中
K={01,02,03,04,05,06,07,08,09,10}
R={<05,01>,<01,03>,<03,08>,<08,02>,<02,07>,<07,04>,
<04,06>,<06,09>,<09,10>}
对应的图形表示如图1.1所示。
图1.1数据的线性结构示意图
结合表1.1,细心的读者不难看出:
R是按职员年龄从大到小排列的关系。
在数据结构linearity中,数据元素之间是有序的,每个数据元素有且仅有一个直接前驱元素(除结构中第一个元素05外),有且仅有一个直接后继元素(除结构中最后一个元素10外)。
这种数据结构的特点是数据元素之间的1对1(1∶1)联系,即线性关系。
我们把具有这种特点的数据结构叫做线性结构。
【例1.3】一种数据结构的二元组表示为tree=(K,R),其中
K={01,02,03,04,05,06,07,08,09,10}
R={<01,02>,<01,03>,<01,04>,<02,05>,<02,06>,<03,07>,
<03,08>,<03,09>,<04,10>}
对应的图形表示如图1.2所示。
图1.2数据的树结构示意图
结合表1.1,细心的读者不难看出:
R是职员之间领导与被领导的关系。
图1.2像倒着画的一棵树,在这棵树中,最上面的一个没有前驱只有后继的结点叫做树根结点,最下面一层的只有前驱没有后继的结点叫做树叶结点,除树根和树叶之外的结点叫做树枝结点。
在一棵树中,每个结点有且只有一个前驱结点(除树根结点外),但可以有任意多个后继结点(树叶结点可看作为含0个后继结点)。
这种数据结构的特点是数据元素之间的1对N(1∶N)联系(N≥0),即层次关系,我们把具有这种特点的数据结构叫做树结构,简称树。
【例1.4】一种数据结构的二元组表示为graph=(K,R),其中
K={01,02,03,04,05,06,07}
R={<01,02>,<02,01>,<01,04>,<04,01>,<02,03>,<03,02>,
<02,06>,<06,02>,<02,07>,<07,02>,<03,07>,<07,03>,
<04,06>,<06,04>,<05,07>,<07,05>}
对应的图形表示如图1.3所示。
从图1.3可以看出,R是K上的对称关系。
为了简化起见,我们把〈x,y〉和〈y,x〉这两个对称序偶用一个无序对(x,y)或(y,x)来代替;在示意图中,我们把x结点和y结点之间两条相反的有向边用一条无向边来代替。
这样R关系可改写为:
R={(01,02),(01,04),(02,03),(02,06),(02,07),
(03,07),(04,06),(05,07)}
对应的图形表示如图1.4所示。
如果说R中每个序偶里的两个元素所代表的职员是好友的话,那么R关系就是人员之间的好友关系。
图1.3数据的图结构示意图图1.4图1.3的等价表示
从图1.3或1.4可以看出,结点之间的联系是M对N(M∶N)联系(M≥0,N≥0),即网状关系。
也就是说,每个结点可以有任意多个前驱结点和任意多个后继结点。
我们把具有这种特点的数据结构叫做图结构,简称图。
从图结构、树结构和线性结构的定义可知,树结构是图结构的特殊情况(即M=1的情况),线性结构是树结构的特殊情况(即N=1的情况)。
为了区别于线性结构,我们把树结构和图结构统称为非线性结构。
集合结构是整个数据结构中的一种特殊情况,其元素之间不存在任何关系。
【例1.5】一种数据结构的二元组表示为B=(K,R),其中
K={k1,k2,k3,k4,k5,k6}
R={R1,R2}
R1={,,,,}
R2={,,,,}
若用实线表示关系R1,虚线表示关系R2,则对应的图形表示如图1.5所示。
图1.5带有两个关系的一种数据结构示意图
从图1.5可以看出:
数据结构B是图结构。
但是,若只考虑关系R1则为树结构,若只考虑关系R2则为线性结构。
下面简要讨论数据的存储结构。
存储数据不仅要存储数据中的每个数据元素,而且要存储元素之间的逻辑关系。
具体地说,若数据是集合结构,则只需要存储所有数据元素,不需要存储它们之间的任何关系;若数据是线性结构、树结构或图结构,则除了要存储所有数据元素外,还要相应存储元素之间的线性关系、层次关系或网状关系。
数据的存储结构分为顺序、链接、索引和散列4种。
顺序存储对应一块连续的存储空间,该空间的大小要大于等于存储所有元素需占有的存储空间的大小,存储元素之间的联系(即逻辑结构)通常不需要附加空间,而是通过元素下标之间的对应关系反映出来,只要简单的计算就可以得到一个元素的前驱或后继元素的下标。
顺序存储空间一般需要通过定义数组类型和数组对象来实现。
在链接存储结构中,元素之间的逻辑关系通过存储结点之间的链接关系反映出来,每个存储结点对应存储一个元素,同时存储该元素的前驱和后继元素所在结点的存储位置,或者说同时存储指向其前驱元素结点和后继元素结点的指针,通过这些指针能够直接访问到其前驱元素和后继元素。
链接存储空间通过定义元素的存储结点类型和对象来实现,所有存储结点可以占用连续的存储空间(即数组空间),也可以占用不连续的存储空间,此空间是由动态分配的每个结点的空间形成的。
索引存储是首先把所有数据元素按照一定的函数关系划分成若干个子表,每个子表对应一个索引项,然后采用一种存储结构存储所有子表的索引项和采用另一种存储结构存储所有子表中的元素。
如存储汉字字典时,需要采用索引存储,首先按偏旁部首划分所存汉字为若干子表,得到偏旁部首表,对于每个部首再按所属汉字的笔画多少划分子表,得到检字表,检字表中的每个汉字对应汉字解释表(即字典主体)中的一个条目;然后再分别存储部首表、检字表和汉字解释表。
这里检字表是汉字解释表的索引,而偏旁部首表又是检字表的索引,它是汉字解释表的二级索引。
当存储的数据量很大时,通常都需要采用索引存储,并且时常使用多级索引。
在索引存储中,各级索引表和主表(即数据元素表)通常都以文件的形式保存在外存磁盘上,访问任一数据元素时,都要根据该数据元素的特征依次访问各级索引表和最后访问主表,存取外存的次数至少等于建立索引的级数加1。
散列存储方法是按照数据元素的关键字通过一种函数变换直接得到该元素存储地址的方法,该存储地址为相应数组空间中的下标位置。
用于散列存储所有数据元素的相应数组空间称为散列表。
通过定义用于计算散列存储地址的函数和定义存储数据元素的散列表能够实现散列存储结构。
以上简要叙述了数据结构的有关概念,在以后的各专题中将会做深入和具体的讨论。
1.2抽象数据类型
抽象数据类型(AbstractDataType,ADT)由一种数据结构和在该数据结构上的一组操作所组成。
抽象数据类型包含一般数据类型的概念,但含义比一般数据类型更广、更抽象。
一般数据类型由具体语言系统内部定义,直接提供给编程者定义用户数据,因此称它们为预定义数据类型。
抽象数据类型通常由编程者定义,包括定义它所使用的数据、数据结构以及所进行的操作。
在定义抽象数据类型中的数据部分(含数据结构在内)和操作部分时,可以只定义数据的逻辑结构和操作说明,不考虑具体的存储结构和操作的具体实现,这样能够为用户提供一个简明的使用接口,然后再另外给出具体的存储结构和操作的具体实现,使得操作声明与实现分开,从而符合面向对象的程序设计思想。
抽象数据类型在C++语言中是通过类类型来描述的,其数据部分通常定义为类的私有或保护的数据成员,它只允许该类或派生类直接使用,操作部分通常定义为类的公共的成员函数,它既可以提供给该类或派生类使用也可以提供给外部定义的类和函数使用。
在本书中,为了便于叙述和分析数据结构和算法,使读者容易理解和接受,所以在实现所定义的抽象数据类型时,把数据部分用一种已知的数据类型(如结构或数组等)来实现,把操作部分中的每个操作用普通函数来实现,这样能够同读者熟悉的C语言、C++语言,甚至其他计算机语言很好地兼容起来。
一种抽象数据类型的定义将采用如下书写格式:
ADT<抽象数据类型名>is
Data:
<数据描述>
Operations:
<操作声明>
end<抽象数据类型名>
【例1.6】假定把矩形定义为一种抽象数据类型,其数据部分包括矩形的长度和宽度,操作部分包括初始化矩形的尺寸、求矩形的周长和求矩形的面积。
假定该抽象数据类型名用RECtangle(矩形)表示,定义矩形长度和宽度的数据用length和width表示,并假定其类型为浮点(float)型,初始化矩形数据的函数名用InitRectangle表示,求矩形周长的函数名用Circumference(周长)表示,求矩形面积的函数名用Area(面积)表示,则矩形的ADT(抽象数据类型)描述如下:
ADTRECtangleis
Data:
一个矩形r,其长度和宽度分别用length和width表示
Operations:
//初始化矩形r的长度和宽度值为len和wid
voidInitRectangle(Rectangle&r,floatlen,floatwid);
//求矩形r的周长并返回
floatCircumference(Rectangle&r);
//求矩形r的面积并返回
floatArea(Rectangle&r);
endRECtangle
这里假定数据部分的矩形r是类型名为Rectangle的一个结构对象,该类型的具体定义如下:
structRectangle{
floatlength,width;
};
下面给出每个操作的具体实现。
(1)初始化矩形尺寸
voidInitRectangle(Rectangle&r,floatlen,floatwid){
r.length=len;//把len值赋给r的length域
r.width=wid;//把wid值赋给r的width域
}
该函数把两个值参len和wid的值分别赋给引用参数r的length域和width域,实现对一个矩形r的初始化。
(2)求矩形周长
floatCircumference(Rectangle&r){
return2*(r.length+r.width);
}
(3)求矩形面积
floatArea(Rectangle&r){
returnr.length*r.width;
}
求矩形周长和面积的函数分别使用一个矩形引用参数,当然也可以改为值参。
【例1.7】把二次多项式ax2+bx+c设计成一种抽象数据类型,假定起名为QUAdratic,该类型的数据部分为三个系数项a,b和c,操作部分为:
(1)初始化a,b和c的值,假定它们的默认值均为0;
(2)做两个多项式加法,返回它们的和;
(3)根据给定x的值计算多项式的值并返回;
(4)计算方程ax2+bx+c=0的两个实数根,对于有实根、无实根和不是二次方程(即a==0)这三种情况都要返回不同的整数值,以便返回后做不同的处理;
(5)按照ax**2+bx+c的格式输出二次多项式,在输出时要注意去掉系数为0的项,并且当b和c的值为负时,其前不能出现加号。
该抽象数据类型可具体定义如下:
ADTQUAdraticis
Data:
一个二次多项式q,其二次项、一次项和常数项的系数分别用a,b,c表示
Operations:
//初始化二项式,用于赋给a,b,c值的每个对应参数的默认值设为0
voidInit(Quadratic&q,floataa=0,floatbb=0,floatcc=0);
//两个二项式相加
QuadraticAdd(Quadratic&q1,Quadratic&q2);
//二项式求值
floatEval(Quadratic&q,floatx);
//求二项式方程的根,两个实根由引用参数r1和r2带回
intRoot(Quadratic&q,float&r1,float&r2);
//输出二项式
voidPrint(Quadratic&q);
endQUAdratic
这里假定数据部分的二次多项式q是类型名为Quadratic的一个结构对象,该类型的具体定义如下:
structQuadratic{
floata,b,c;
};
下面给出每个操作的具体实现。
(1)初始化a,b和c的值
voidInit(Quadratic&q,floataa,floatbb,floatcc)
{
q.a=aa;q.b=bb;q.c=cc;
}
(2)做两个多项式加法,返回它们的和
QuadraticAdd(Quadratic&q1,Quadratic&q2)
{
Quadraticq;
q.a=q1.a+q2.a;
q.b=q1.b+q2.b;
q.c=q1.c+q2.c;
returnq;
}
(3)根据给定x的值计算多项式的值并返回
floatEval(Quadratic&q,floatx)
{
return(q.a*x*x+q.b*x+q.c);
}
(4)求二项式方程的根,两个实根由引用参数r1和r2带回
intRoot(Quadratic&q,float&r1,float&r2)
{
if(q.a==0)return-1;//不是二次方程返回-1
floatx=q.b*q.b-4*q.a*q.c;
if(x>=0){
r1=float(-q.b+sqrt(x))/(2*q.a);
//sqrt(x)为计算x的平方根,该函数存在于系统头文件math.h中
r2=float(-q.b-sqrt(x))/(2*q.a);
return1;//有实根返回1
}
elsereturn0;//无实根返回0
}
(5)输出二项式
voidPrint(Quadratic&q)
{
if(q.a)cout<if(q.b){
if(q.b>0)cout<<"+"<elsecout<}
if(q.c){
if(q.c>0)cout<<"+"<elsecout<}
cout<}
假定使用如下调试程序:
#include
#include
structQuadratic{
floata,b,c;
};
//初始化二项式
voidInit(Quadratic&q,floataa=0,floatbb=0,floatcc=0);
//两个二项式相加
QuadraticAdd(Quadratic&q1,Quadratic&q2);
//二项式求值
floatEval(Quadratic&q,floatx);
//求二项式方程的根,两个实根又引用参数r1和r2带回
intRoot(Quadratic&q,float&r1,float&r2);
//输出二项式
voidPrint(Quadratic&q);
//各函数定义已经在上面给出,这里从略
voidmain()
{
Quadratica,b,c;
Init(a,2,5);
Print(a);
cout<floatx1,x2;
intn=Root(a,x1,x2);
if(n==1)cout<Init(b,3,-8,4);
Print(b);
n=Root(b,x1,x2);
if(n==1)cout<c=Add(a,b);
Print(c);
}
程序运行结果如下:
2x**2+5x
52
0-2.5
3x**2-8x+4
20.666667
5x**2-3x+4