1、第三章 线性表,3.1 线性表的逻辑结构,3.1.1 基本概念线性表(Linear list)是数据元素的一个有限序列,在这个序列中,每个元素有一个唯一的(直接)前趋和一个唯一的(直接)后继,第一个元素可以无前趋,而最后一个元素也可以无后继。线性表可记为 L = (a1, a2, ,an); 这里,ai为数据元素,n0为整数,ai-1称为ai的前趋(i2),ai+1称为ai 的后继(in),i=1, 2, , n,线性表中元素的个数称为线性表的长度。 无元素的表(n=0)称为空表,空表长度为0 按形式化方法,线性表定义为 LL=(D,S) D=a1,a2,an S=r r= aiD,i=2,
2、, n。,3.1.2 线性表抽象模型,这里,我们将线性表视为一个抽象对象/类(亦称接口),即不考虑它的具体数据结构存储,不考虑基本操作的实现,只考虑它的基本操作的接口(输入/输出),做为准备,先定义专用于线性表类的异常处理类TExceptionLinearlist。 class TExcepLinearList public:int errNo;char errMessageCNST_SizeErrMessage;TExcepLinearList(int mErrNo)errNo=mErrNo;strcpy(errMessage, errMessageList.GetMessage(errNo
3、); ;,在线性表类的成员函数中可能产生异常的地方检测到异常时,使用throw抛掷一个TExceptionLinearlist对象,产生一个该类型的异常,形式为:throw TExceptionLinearlist(no); 这里,no为具体的异常代码。该类只设构造函数。当在程序中使用throw TExceptionLinearlist(no); 时,所返回的TExceptionLinearlist对象的errNo被设置为no,且errMessage也被设置为no所对应的异常说明信息,下面是线性表抽象类: template /上面是模板声明,表明TElem是一个可变(调)类型,在使用TLine
4、arList0时动态决定 /有了这个声明,TLinearList0中可直接将TElem做为已知类型使用。 class TLinearList0 protected:public:long len;virtual TBool IsEmpty() if (len =0) return True; else return False;virtual TElem ,3.2 线性表的顺序存贮结构 3.2.1基本存储方法,具体地讲,线性表的顺序存贮(也称连续存贮)方法是,将表中各元素依它们的逻辑次序存贮在一片连续的存贮区域中,使任意两相邻元素之间的存贮单元个数相等。通常,元素之间不留空闲存贮区 在这种存贮
5、结构中,有下列关系成立Loc(ai)= (i-1)*c 这里,Loc(ai)表示第i个元素ai的相对地址。c是每个元素所占的单元个数,3.2.2 面向对象描述 (一)对象结构 下面是顺序存储结构对应的类TLinearListSqu: template /上面是模板声明,表明TElem是一个可变(调)类型。具体的类型在使用TLinearListSqu时 /动态决定。有了这个声明,TLinearListSqu中可直接将TElem做为已知类型使用。 class TLinearListSqu : public TLinearList0 /表示从TLinearList0派生 protected:TEle
6、m *room; /room相当于一维数组,其元素类型为可变类型TElemlong size;long lastVisited;TElem buffElem;long ResizeRoom(long newSize);long CopyRoom(TElem *objRoom, long n1, TElem *srcRoom, long n2);,public:TLinearListSqu(void);TLinearListSqu(long mSize);TLinearListSqu(void);virtual TElem ,(二)初始化 初始化的主要工作是设置存储区、给类变量赋初值,使线性表处
7、于空的可使用状态(可插入元素)。这里的初始化通过对象的构造函数实现。 有两个构造函数。第一个不分配存储空间,只进行相应的变量初始化工作。第二个构造函数负责分配指定数量的空间。 与构造函数对应的是析构函数,它的任务是当对象生命期结束后,释放所分配的存储空间。,template TLinearListSqu:TLinearListSqu() size=0;len=0;room=NULL; ; template TLinearListSqu:TLinearListSqu(long mSize) size=0;room=NULL;len=0;if (mSize TLinearListSqu:TLine
8、arListSqu(void) if (room!=NULL) delete room; /释放所分配的空间 ;,(三)元素直接访问 Get和Set类函数用于实现按序号(逻辑关系)访问元素。对顺序存储,它们的实现是直接的。 template TElem ,template TElem *TLinearListSqu:GetAddress(long idx) if (idx=len)throw TExcepLinearList(1);return ,(四)前驱/后继操作 对顺序存储结构,根据元素的序号求该元素的前驱/后继是很简单的。下面是对应的程序。 template TElem *TLinea
9、rListSqu:Prior(long idx) if (idx=len)throw TExcepLinearList(1);return ,(五)查找定位操作 这里给出的一组操作,用于根据元素内容,求出该元素的位置(序号)。由于表中可能有重复值的元素,所以满足条件的元素可能不只一个。 1. Locate(TElem ,else /sn不大于0时,从后往前计数for (i=len-1; i=0; i-)if (roomi=elem)k+;if (k= -sn) return i;if (k0) return i; /“sn“大于匹配的元素个数,返回最后匹配元素的下标 else return -
10、1; /未发现 ;,2. Locate(TElem ,3LocateFirst(TElem ,template long TLinearListSqu:LocateNext(TElem ,(六)插入操作 Insert(TElem /sn太小时,在最前面插入,if (len=size) /剩余空间不足时,调用成员函数重新分配空间ret=ResizeRoom(size+10); /将空间扩大为size+10,并保留原内容if (ret=k; i-)roomi+1 = roomi;roomk=elem;len+;return ,(七)删除操作 1Delete(long sn):该函数删除表中第sn个
11、元素(sn的含义同Locate),并将其值的地址返回。sn的处理方法同Insert template TElem *TLinearListSqu:Delete(long sn) long i, k;if (sn=len) throw TExcepLinearList(2);buffElem=roomk;for (i=k; ilen; i+)roomi = roomi+1;len-;if (2*len size) /如果空余空间已达到一定程度,就缩小之ResizeRoom(len+long(len/2.0);return ,2Delete(TIndexSelector ,3DeleteByInd
12、ex(long *idxTobeDel, long numIdx, TElem:*elemDeleted):该函数执行具体的删除操作。由于是根据下标列举idxTobeDel进行删除,所以处理方法有所不同 template long TLinearListSqu: DeleteByIndex(long *idxTobeDel, long numIdx, TElem *elemDeleted) long i, k;k=0;for (i=0; ilen; i+)if (i=idxTobeDelk)if (elemDeleted!=NULL) / elemDeleted为空时表示不保留所删除的元素el
13、emDeletedk = roomi;k+;else roomi-k =roomi;,len = len - k;if (2*len size) / 剩余空间足够大时,调用ResizeRoom释放多余的空间ResizeRoom(len+long(len/2.0);return k; ,3.3 异常处理与下标选择器,3.3.1 异常处理 在程序设计语言中,异常(Exception)是指程序运行中,由于运行环境或数据输入或操作不当,所出现的使程序不能物理地运行(而不论运行结果是否正确)的错误。这里,物理运行是指程序在实际环境下运行。 在C+中,为程序员提供了良好的处理异常的机制,其中心点是提供下列
14、语句:try-检测/捕获异常;catch-处理try所捕获的异常;throw-生成一个异常,交由try捕获; 我们这里直接采用C+的异常机制。,首先,设立一个表,存储异常代码和用于说明异常的文字信息。为方便,我们也将其定义为类,如下所示。 struct TErrMessageRec /异常记录 int no; /异常代码char msgCNST_SizeErrMessage; /异常信息 ; class TErrMessageList /异常表类 TErrMessageRec msgListCNST_MaxNumErrMessage; /用一维数组做异常表public:int len; /指示
15、异常表的长度(元素个数)TErrMessageList() /构造函数,这里主要是装入异常表int i=0;,/下面示例性地为异常表装入数据 /* 0*/ msgListi.no=i; strcpy(msgListi.msg, “Unknown error“); i+; /* 1*/ msgListi.no=i; strcpy(msgListi.msg, “Parameters Out of range“); i+; /* 2*/ msgListi.no=i; strcpy(msgListi.msg, “Parameters illegal“); i+; /* 3*/ msgListi.no=
16、i; strcpy(msgListi.msg, “Mmemory space low“); i+; /* 4*/ msgListi.no=i; strcpy(msgListi.msg, “Not found“); i+;len = i;char *GetMessage(int no) /成员函数,根据编号读出异常信息if (no=CNST_MaxNumErrMessage)return NULL;return msgListno.msg; ; 我们这里只是示意性地设置了一些出错代码。在实际中,可设置更详细更合理的信息。当该类的实例产生时,这些信息也被载入,为了后面的各异常处理类的使用,需定义一
17、个全局的出错代码表errMessageList: extern TErrMessageList errMessageList; 在该类的基础上,可以定义各种针对具体数据结构的异常处理类了,如下一节要定义的TExcepLinearList。这里,我们先定义一个通用的异常处理类TExcepComm. class TExcepComm public:int errNo;char errMessageCNST_SizeErrMessage;TExcepComm(int mErrNo)errNo=mErrNo;strcpy(errMessage, errMessageList.GetMessage(er
18、rNo); ;,3.3.2 下标选择器 对可按下标(序号)访问元素的结构,常常需要指定若干下标范围,为此,我们引入下标选择器。 TIndexSelector是一个字符串形式的下标选择器。其内容是用逗号分隔的若干项(称为选择项),每个选择项可以是一个整数,用以指出一个下标,或是用下划线“_”分隔的两个整数,用以指出一个下标范围,“_“前的数缺省时表示起始序号,“_“后的数缺省时表示结束序号。多个项则指出多个范围。正整数表示序号是从前(左)往后(右)数(递增),负整数表示序号是从后(右)往前(左)数(递减)。下标选择器的语法格式严格定义如下: IndexSelector := IndexScope
19、 | (IndexScope “,“ IndexSelector) IndexScope := INTEGER | ( INTEGER? “_“ INTEGER?) INTEGER := (“-“ | “+“)? DIGITS DIGITS :=DIGIT | ( DIGIT DIGITS) DIGIT := 0|1|2|3|4|5|6|7|8|9,例如,下面的串都是形式合法的下标选择器 5 1_ _8 _ 1,2 1,2_3, -1_-3 3_4,2,7_8, 10_,下面给出TIndexSelector类的定义,其中一些成员函数未给出具体实现,其实现留做作业。 struct TSelect
20、orItem /选择项类型 long lower; /项的下界long upper; /项的上界 ; class TIndexSelector TSelectorItem roomCNST_SizeIndexSelector; /存储选择项的一维数组,每个项用一个数组元素存储int lastVisitedItem;int LoadFromArray(TSelectorItem *se, int n, long maxIdx);long Standardize(void);public:int len;long maxIndex;TIndexSelector() len=0;TIndexSele
21、ctor(TSelectorItem *se, int n, long maxIdx);,int GetFirst(long ,3.4 线性表的链式存储-线性链表,顺序结构的缺点: 第一,当进行插入与删除时,需移动元素。如果线性表很大,移动量就非常大。 第二,由于它要求一片连续的存贮区域,所以存贮要求较高,不能利用小块存贮区。,3.4.1 链式存贮方法,线性表采用链式存贮结构时称为线性链表,它的具体存储方法也可能有多种,我们这里先介绍以后继或前驱地址为链的存储方法,这样的链表也称单链表。具体的存贮映射方法是,对线性表中每个元素ai,为它分配一块存贮区。存贮区的分配问题,本不属存贮方法的讨论范围
22、,但具体实现时,此问题不可回避。有两种分配方式,一是设计者对一片足够大的存贮区自行管理(分配与释放),这种方式称为静态方法。相应的链式结构称为静态链表。另一种方式是利用高级语言的动态存贮管理机制(如PASCAL中的new、dispose、C/C+中的malloc、free、new、delete等)。 在这种方式中,对存贮空间的使用不需涉及存贮管理的实现问题。在本教程中,以这种方式为主。内容前驱/后继地址,每个元素的存贮区分为两大部分:其中,内容部分用于存放元素本身的信息;“前驱/后继地址”部分存放该元素的前驱或后继的存贮地址。这里我们一般使用后继地址。 对表中最后一个结点,令其“后继地址”为空
23、,作为链表的结束标志 为了能方便地访问链表,设置链表头结点,记下链表中首结点的地址(有时也要记尾结点地址)和链表中当前结点个数等有关链表的信息。这种头结点作为链表的描述结点,是对链表的整体的描述,是整个链表的代表,故它的类型可做为线性表的类型。 一个典型的单链表的形式如图 33所示,3.4.2 线性链表的面向对象描述,(一)元素与关系描述 下面是链表中结点的C+描述,它代表着线性表的元素和关系。 template /上面是模板声明,表明TElem是一个可变(调)类型,在使用TLinkNode时动态决定 struct TLinkNode TElem info; /info的类型是可变的类模板TE
24、lemTLinkNode *next; /注: 这里的TLinkNode后可以省略 ;,(二)链表对象描述 链表对象应该是前面介绍的线性表抽象类TLinearList0的派生类,代表着整个线性链表。它需要记录首结点的地址和链表中当前结点个数等有关链表的信息,并针对其设置有关操作 template class TLinearListLink : public TLinearList0 protected:TLinkNode *head;TLinkNode *lastVisited;long lastVisitedIndex;TElem buffElem;void ReleaseAll();,pu
25、blic:TLinearListLink(void);TLinearListLink(void);virtual TElem virtual TLinkNode *SetNodeElem(TLinkNode *pNode,TElem ,3.4.3 线性链表的面向对象实现,下面给出TLinearListLink中成员函数的实现 (一)初始化 template TLinearListLink:TLinearListLink() head=NULL;len=0;lastVisitedIndex=0;lastVisited=NULL; ; template TLinearListLink:TLinearListLink() if (head!=NULL) ReleaseAll(); ;,template void TLinearListLink:ReleaseAll() /释放链表中所有元素TLinkNode *p, *q;p=head; /p指向当前要处理的结点while (p!=NULL) /从头到尾依次释放各结点q=p; /移动p之前,令q指向p所指结点,以防p后移后丢失p原指的结点p=p-next; /p后移一步,使得下次循环时,p指向下一个要处理的结点delete q; /释放q所指结点 ;,