1、第二章 线性表,2.1 线性表的定义,2.1.1 线性表的逻辑结构线性表是有限元素(e0,e1, .,ei,.,en-1)的有序序列的集合。其中n是有穷自然数,表中的每个元素ei具有相同的特性,表中元素占用空间大小相同,记为:size,n是表的长度。当n=0时,表为空;当n0时,e0是第一个元素,en-1 是最后一个元素。“有序”是指线性元素间的相互位置关系。ei-1是 ei的直接前驱元素,而元素ei一定在元素ei+1之前,称ei+1是ei的直接后继元素。而且,每个元素只有一个直接前驱元素(除第一个元素),也仅有一个直接后继元素(除最后一个元素)。,2.1 线性表的定义,2.1 线性表的定义,
2、2.1.2 线性表的抽象数据类型,2.1 线性表的定义,. 线性表的顺序存储及操作 2.2.1 线性表顺序存储 1. 线性表顺序存储概念线性表顺序存储方式,是将线性表中的数据元素连续顺序地存放于存储器中相邻的单元 。线性表占用的第一个存储单元的地址,就是线性表的首地址,也是线性表中第一个数据元素(e0)的首地址。 “首地址”有两种理解:一是相对于线性表本身,是线性表的始点,即“0号地址”,这就是通常所说的“相对地址”;二是相对于计算机的物理存储空间,线性表的始点相对于计算机物理存储空间则是一个“物理地址”,一般记为:location(e0),通常称为“绝对地址”或“基地址”。,2.2线性表的顺
3、序存储及操作,线性表中每个数据元素占用size字节空间,length(length=n)表示线性表中元素的个数,MaxSize表示线性表可以存储元素的最大空间。,元素ei的地址则可以立即由下面公式求出。location(ei)=location(e0)+isize请注意:这个公式是元素序号由0到n-1安排,若元素序号是由j开始安排,则上面公式应改为:location(ei)=location(ej)+(i-j)size,2.2线性表的顺序存储及操作,2. 线性表顺序存储结构定义 线性表结构: typedef struct EType *element;int length;int MaxSiz
4、e; LinearList; LinearList L,L1,L2;,2.2线性表的顺序存储及操作,学生信息的情况数据元素结构类型(EType)及线性表 typedef struct unsigned number10;char name8;char sex2;int age;char place20; student;typedef struct student *element;int length;int MaxSize; LinearList;LinearList stud1, stud2;,2.2线性表的顺序存储及操作,2.2.2 线性表顺序存储结构下的操作 1. 构造空线性表L空线
5、性表是指表中没有一个数据元素,但数据元素的空间和线性表结构存在 构造空线性表L算法(Creat) viod Creat(LinearList 算法的时间复杂性是O(1)。,2.2线性表的顺序存储及操作,2.2线性表的顺序存储及操作,2. 输出线性表L中的所有数据元素输出线性表L中所有数据元素(Output) viod Output(LinearList 算法的时间复杂性是O(length )。,2.2线性表的顺序存储及操作,3. 线性表L中取第k个元素线性表L中将第k个元素取至x中算法(GetElem) bool GetElem(LinearList 算法的时间复杂性是O(1 )。,2.2线性
6、表的顺序存储及操作,2.2线性表的顺序存储及操作,4. 线性表L中查找元素x 线性表L中查找元素x(Search) int Search(LinearList 算法的时间复杂性是O(length)。 一般是已知某个查找关键字(Searchkey),通过查找关键字与线性表中的数据元素的关键字的比较完成查找过程的,因此,算法查找的比较条件也可以表达为:if (L.elementi.key = Searchkey),2.2线性表的顺序存储及操作,5. 线性表L中第k个数据元素之后插入元素x 线性表L中第k个数据元素之后插入元素x运算(Insert) Status Insert(LinearList
7、算法的时间复杂性是O(length)。,2.2线性表的顺序存储及操作,2.2线性表的顺序存储及操作,6. 线性表L中删除第k个数据元素线性表L中删除第k个数据元素算法(Delete) Status Delete(LinearList 算法的时间复杂性是O(length-k)。,2.2线性表的顺序存储及操作,2.2线性表的顺序存储及操作,线性表特征: 优点:线性表的顺序存储方式,它有着逻辑关系上相邻的两个数据元素在物理位置上也相邻的特征。因此,只要知道第一个元素的位置(即下标),则可以利用寻址公式高效地存取一个元素。 缺点: 其一,在插入或删除的过程中有着大量元素的移动,运算时间较长。 其二,在
8、为长度可变化的线性表预先分配空间时,必须按最大表长MaxSize空间分配,因而造成存储空间得不到充分利用。 第三,表的空间不容易扩充。为此,在有着频繁插入、删除运算时,不宜采用顺序存储结构。,2.2线性表的顺序存储及操作,2.3 简单链表存储结构及操作,2.3.1 简单链表的存储 1. 简单链表的存储概念线性表的链式存储结构的特点,是用物理上不一定相邻的存储单元来存储线性表的元素,为了保证线性表之间的逻辑上的连续性,存储元素ei时,除了存储它本身的内容以外,还必须附加一个指针域(也叫链接域)来指出元素ei的直接后继元素ei+1的存储地址。,2.3 简单链表存储结构及操作,2.3 简单链表存储结
9、构及操作,2.3.1 简单链表的存储 1. 简单链表的存储概念线性表的链式存储结构的特点,是用物理上不一定相邻的存储单元来存储线性表的元素,为了保证线性表之间的逻辑上的连续性,存储元素ei时,除了存储它本身的内容以外,还必须附加一个指针域(也叫链接域)来指出元素ei的直接后继元素ei+1的存储地址。,2.3 简单链表存储结构及操作,2.3.1 简单链表的存储 1. 简单链表存储结构定义动态存储空间分配方式下线性表的数据元素的类型: typedef struct EType data;ChainNode *link; ChainNode; ChainNode *N, *N1; 表头结点结构定义如
10、下: typedef struct HeadEType Hdata;ChainNode *first; ChainList;ChainList *L, *L1, *stud;,2.3 简单链表存储结构及操作,2.3 简单链表存储结构及操作,2.3.2 简单链表的操作 1. 利用动态存储分配构造简单链表(空链表)构造一个简单链表就是构造一个,即只产生一个仅有表头结点的链表,表头结点的链接域first的值首先设为空(NULL)值,表头结点的数据域填入表头的相应数据。并返回(带回)表头结点的结点指针。构造带表头的简单链表算法(Creat) viod Creat(ChainList ,2.3 简单链表
11、存储结构及操作,2. 将简单链表数据元素进行输出 输出简单链表L中的数据元素算法(Output) viod Output(ChainList ,2.3 简单链表存储结构及操作,3. 确定简单链表的长度计算简单链表L中数据元素结点数算法(Length) int Length(ChainList ,2.3 简单链表存储结构及操作,4. 删除简单链表中的所有数据元素结点删除后,链表只剩链表表头结点,链表表头的链接域被置为空。 删除过程中,不能简单将表头结点的链接域设为空,应该将链表中的每一个数据结点的逐一删除,并释放每个结点所占用的存储空间。 删除简单链表中的所有数据元素结点(Destroy) vi
12、od Destroy(ChainList ,2.3 简单链表存储结构及操作,2.3 简单链表存储结构及操作,5. 简单链表L中查找第k个元素算法中设定一个指针current,用于指向链表中的一个数据结点,另设一个计数器index,记载指针current已经指向链表中的第几个数据结点。当index的值等于k值时,表示已经找到了第k个数据元素。当current为空时,说明指针已经指到了链表的最后一个结点的后面,说明不存在第k个数据结点,查找失败。,2.3 简单链表存储结构及操作,简单链表L中查找第k个元素取至x中算法(GetElem) bool GetElem(ChainList / k值太大,不
13、存在第k个结点 ,2.3 简单链表存储结构及操作,6.简单链表中查找数据元素x这是已知数据元素值的查找。 简单链表L中查找元素x(Search) ChainNode * Search(ChainList 实际中,一般是已知某个查找关键字(Searchkey),通过查找关键字与线性表中的数据元素的关键字的比较完成查找过程的,因此,算法查找的比较条件也可以表达为:while (current & current-data.key != Searchkey),2.3 简单链表存储结构及操作,7. 简单链表L中第k个数据元素之后插入元素x 在动态存储分配方式下链表的某个结点(current所指结点)之
14、后插入一个q所指结点。当找到current后,在current后面插入一个新的元素q时,只要先申请到空白结点q,将新结点数据值x填入其中,然后分别修改原结点current和新结点q的链接域即可。,2.3 简单链表存储结构及操作,2.3 简单链表存储结构及操作,简单链表L中第k个数据元素之后插入元素x算法(Insert) Status Insert(ChainList ,2.3 简单链表存储结构及操作,如果已知插入点的指针,要在插入点前面插入新结点,这时就要从链表表头开始,先找到插入点的直接前驱结点(current),然后,再插入新结点,如果插入点是第n个结点,算法的时间复杂性是O(n-1)。如
15、果插入点的指针是InsertP,找插入点的直接前驱结点(current)的主要算法步骤如下:ChainNode *current = L-first; while (current -link != InsertP ,2.3 简单链表存储结构及操作,7. 从简单链表中删除一个数据元素在动态存储分配方式下,删除链表的某个结点(current所指结点)。只要将current的直接前驱元素q找到,然后对q的链接域作下述修改,即执行:q-link=current-link的操作就可以删除结点current。实际上,它是将current的直接前驱结点q的链接指针直指current的直接后继元素结点。链表
16、中的被删除结点从链表中断开后,还要将结点空间释放。,2.3 简单链表存储结构及操作,2.3 简单链表存储结构及操作,删除简单链表L中第k个数据结点算法(Delete) Status Delete(ChainList ,2.3 简单链表存储结构及操作,2.4 双向链表,2.4.1 双向链表的存储 双向链表的结点类型: typedef struct EType data;DoubleNode *plink;DoubleNode *nlink; DoubleNode;其中,nlink指向current的直接后继结点,plink指向current的直接前驱结点。链表中最后一个结点的nlink域为NUL
17、L,第一个结点的plink域为NULL。 表头结点的结构类型定义如下: typedef struct HeadEType Hdata;DoubleNode *first; DoubleChainList;,2.4 双向链表,2.4 双向链表,2.4.2 双向链表的操作 1. 双向链表L中第k个数据元素之后插入元素x 插入过程是首先从表头开始查找到第k个数据元素的结点,用current指针指向第k个结点,完成插入。,2.4 双向链表,2.4 双向链表,Status Insert(DoubleChainList ,2.3 简单链表存储结构及操作,if (k) / 插入在current 之后 q-n
18、link = current -nlink; q-plink = current; DoubleNode *p = current -nlink ; p-plink = q; current -nlink = q; else / 作为第一个元素结点插入 q-nlink = L-first; q-plink = NULL; DoubleNode *p = L-first; p-plink = q; L-first = q; return OK; ,双向链表L中第k个数据元素之后插入元素x算法(Insert),Status Delete(DoubleChainList index+),2.3 简单
19、链表存储结构及操作,q = q-nlink; if (!q) return ERROR; current = q; / current 指向第k个结点 q = current -plink; p = current -nlink; q-nlink = p; p-plink = q; delete current; / 释放被删除结点current的空间 return OK; ,2. 从双向链表中删除一个数据元素,删除双向链表L中第k个数据结点算法(Delete),2.5 单向循环链表和双向循环链表 2.5.1 单向循环链表的存储简单链表的最后一个结点的链接域的值始终为空,若将最后一个结点的链接
20、域值定义为指向开头(表头结点或第一个数据结点)。则形成一个环,称为单向循环链表或称为循环链表。 循环链表的最后一个结点的链域指向表头结点,而循环链表为空时表头结点指针指向自身。判断指针指向链表的最后一个结点的方法是:current-link = L-first;循环链表的最后一个结点的链域指向第一个数据元素结点,而循环链表为空时表头结点指针为空(与简单链表的空状态没有区别)。判断指针指向链表的第一个结点的方法是:current = L-first;,2.5 单向循环链表和双向循环链表,2.5 单向循环链表和双向循环链表,2.5.2 双向循环链表的存储双向循环链表是利用双向链表中最后一个结点的直
21、接后继链接域(nlink),使其值为指向表头结点或链表中的第一个数据元素结点。这样形成一个双向环,称为双向循环链表。 双向循环链表的特点是只要已知链表中某个结点的指针(current),就可以通过该指针“周游” 双向循环链表中的所有结点,这时不需要知道链表表头结点的指针。 判断指针指向链表的最后一个结点的方法是: current-nlink = L-first; 判断指针指向链表的第一个结点的方法是: current = L-first; 另外,如从给定的结点指针p0处开始访问其后的第k个结点,则搜寻的过程为: current = p0 -nlink; int index=1; while (
22、current!=p0 ,2.5 单向循环链表和双向循环链表,2.5 单向循环链表和双向循环链表,2.6 模拟指针方式构造简单链表 2.6.1 模拟链表的存储单向循环链表的存储模拟指针(Simulated Pointer)方式构造简单链表,也称为利用静态存储分配构造简单链表。在这种结构下,存储空间是从一个事先定义的数组中申请,使用后又将使用过的空间归还到数组中,链表中的空间不是由系统分配的,而是由用户自己分配的,每个数据元素空间是数组中的一个数组元素,所以,指针的值也不是存储空间的物理地址,而是数组的下标。因此,也将这种链表的指针定义为“模拟指针”。 对存储池中的空间,定义使用GetNode过
23、程从这个存储池中每次取一个结点空间(一个数组元素空间),使用FreeNode过程将使用过的空间归还到存储池中。这两个操作 “等价于”C+中动态申请存储空间过程(new)和释放存储空间过程(delete)。,2.6 模拟指针方式构造简单链表,为了实现指针的模拟,需要设计用于释放和分配结点空间的过程。首先,定义一个足够大的数组(node),称为存储池(storage pool),当前未使用的结点空间将被放入这个存储池中。开始,这个存储池中包含了所有数组元素空间:node 0 :MaxSize -1 数据元素的结点仍然是由两个部分组成:数据域和链接域。对存储池中的所有空间,是采用链表方式将所有的可用
24、空间 “链”在一起,由一个称为avail的指针指向。初始状态时,可将定义的node数组中任何一个数组元素空间作为起点,图中是以0下标的空间作为起点,每个数组元素与其后面的数组元素“链”在一起,即在每个数组元素的链接域中填入下一个数组元素的下标,最后一个数组元素的链接域中填入“空”(约定为-1)。,2.6 模拟指针方式构造简单链表,2.6 模拟指针方式构造简单链表,L1: A1,A2,A3,A4,A5,A6,A7 L2: B1,B2 L3: C1,C2,C3,C4,C5,C6,静态存储分配方式下,链表中的每个结点结构: typedef struct EType data;int link; Si
25、mNode;链接域link的类型不是指针类型,而是整型(因为数组的下标是整型)。存储池的表头结点结构的定义: typedef struct SimNode *node; /指向数组(存储池)的指针int MaxSize; /存储池大小int avail; /指向存储池链表的第一个结点 SimChainList;整个存储池的核心部分是一个由node指向的数组;另外,用MaxSize记载存储池的大小,即node数组的大小;用avail指向存储池链表(未用空间)的第一个结点,avail在逻辑上被看作指针,但由于静态分配的空间,所以空间的地址是整型。,2.6 模拟指针方式构造简单链表,2.6.2 模拟
26、链表的操作 1. 模拟链表的初始化模拟链表的初始化过程就是定义一个数组,并将整个数组链成一个存储池。其基本思想是,将定义的结点数组的链域从第一个元素开始逐个地链接到下一结点元素,即结点link域的值以下一个元素的下标赋值,而最后一个结点的link域以-1赋值。 模拟链表的初始化算法(Creat) void Creat(SimChainList ,2.6 模拟指针方式构造简单链表,2.模拟链表中结点空间的分配分配结点的思想是:将avail链表第一结点作为一可用结点空间取走(从avail链表中删除),可用空间链表表头指针指向avail链表的下一结点。 模拟链表分配结点空间算法(GetNode) i
27、nt GetNode(SimChainList ,2.6 模拟指针方式构造简单链表,3.模拟链表中结点空间的释放当发生删除时,释放的结点仍要归还到可用结点链表中去,归还(释放)结点的基本思想是:将被删结点作为一可用结点插入到可用结点链表avail中,作为第一结点。 模拟链表释放结点空间算法(FreeNode) void FreeNode(SimChainList ,2.6 模拟指针方式构造简单链表,4.模拟链表中插入结点的运算由于模拟链表是一个数组空间,所以插入时要提供某链表的第一个结点的下标地址first参数。first实际上是静态存储空间上某个链表的首地址。 first指向的链表中第k 个
28、结点后面插入结点算法(Insert) Status Insert(SimChainList ,2.6 模拟指针方式构造简单链表,5. 模拟链表中删除结点的运算first指向的链表中删除第k 个结点算法(Delete) Status Delete(SimChainList ,2.6 模拟指针方式构造简单链表,6 . 模拟链表的输出 模拟链表输出算法(FreeNode) void Output(SimChainList ,2.6 模拟指针方式构造简单链表,2.7 多重链表,2.7 多重链表,2.8 链表应用 2.8.1 结点移至表首运算 简单链表L中将第k个数据结点移至表首算法(MoveFirst
29、) Status MoveFirst (ChainList ,2.8 链表应用,2.8 链表应用,2.8.2 链表的逆向运算 简单链表L的逆向的算法(Invert) Status Invert (ChainList ,2.8 链表应用,2.8 链表应用,2.8.3 多项式的相加运算 设多项式为:pn(x)=anxn+an-1xn-1+.+a1x+a0在此链表中,每个结点设立三个域:指数域,系数域和链接域,分别用exp, coef, link表示,其结构定义为: typedef struct int exp;float coef;struct NODE *link; NODE;合并运算规则是:若
30、两项的指数相等,则系数相加,若两项指数不等,则两项照抄到结果中。因此,可以设立两个链指针,分别指向两个已知多项式中的结点,若指针所指结点的指数相等,则系数相加,形成结果结点,当结果结点的系数为零时,删除这一结点,并同时把两指针都前进一项;若指数不相等,多项式指数大的项作为结果结点,并将其指针向前进一项,多项式指数小的指针不动;这样一直进行下去,直到处理完毕为止。,2.8 链表应用,2.8 链表应用,多项式链表L1和L2相加,结点存放在L1中算法(AddPoly) void AddPoly(ChainList ,2.8 链表应用,case 3:x= new NODE; x-coef=q-coef
31、; x-exp=q-exp;x-link=p; p0-link=x; p0=x;q=q-link;break; while (q!=NULL) x= new NODE;x-coef=q-coef;x-exp=q-exp;x-link=p; p0-link=x; p0=x;q=q-link;break; ,2.8.4 十字链表结构的应用 在十字链表中,每个结点由五个数据域组成: row,col,val:分别表示元素的行、列、数据值。 down(下域):链接同一列中下一个非零元素指针。 right(右域):链接同一行中下一个非零元素指针。每一行的非零元素链接成带表头结点的循环链表,同一列的非零元素链接成带表头结点的循环链表。因此,每个非零元素既是第i行循环链表中的一个结点,又是第j列循环链表中的一个结点,故称为十字链表。 typedef struct int row;int col;valtype val;NODE *down;NODE *right; NODE;,2.8 链表应用,2.8 链表应用,