专题数据结构分类与抽象数据类型.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){
}
求矩形周长和面积的函数分别使用一个矩形引用参数,当然也可以改为值参。
【例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
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