数据结构(从概念到算法)课件_第1页
数据结构(从概念到算法)课件_第2页
数据结构(从概念到算法)课件_第3页
数据结构(从概念到算法)课件_第4页
数据结构(从概念到算法)课件_第5页
已阅读5页,还剩50页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第2章线性表01020304目录CONTENTS05线性表的基本概念线性表顺序存储结构定义及实现线性表链式存储结构定义及实现顺序表与链表的比较应用实例06本章小结01PART线性表的基本概念线性表(LinearList)是由n(n≥0)个数据元素(a1,a2,…,an)构成的有限序列,记作L=(a1,a2,…,an)。其中线性表的表长即是表中所包含数据元素的个数,而空表指的是不含数据元素的线性表。线性表在理论上的定义如下:对于线性表L=(a1,a2,…,ai-1,ai,ai+1,…,an),有

(1)ai-1

ai之前,称

ai-1

ai的直接前驱(1<i≤n);

(2)ai+1在

ai之后,称

ai+1是

ai的直接后继(1≤i<n);

(3)首元素a1没有前驱;

(4)尾元素an没有后继;

(5)任意元素ai(1<i<n)有且仅有一个直接前驱和一个直接后继。线性表的基本概念线性表的抽象数据类型从逻辑上定义线性表这种数据结构的数据对象、数据对象之间的关系,以及相关的基本操作。其中,数据对象说明线性表中的每个数据元素均属于某个类型(如整型、实型或字符型等),用一个集合表示:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0}。其中,线性关系运用<ai-1,ai>序偶对来表示前驱和后继的关系。线性表的基本概念线性表的抽象数据类型定义如下:02PART线性表顺序存储结构定义及实现线性表顺序存储结构指的是将线性表中的数据元素依次存放到计算机存储器内一组地址连续的存储单元中,这种分配方式称为顺序分配或顺序映像。在顺序存储结构中,逻辑上相邻的两个数据元素在物理存储空间中也相邻。线性表顺序存储结构定义及实现其中,线性表(a1,a2,…,an)顺序存储结构的一般形式如下图所示。下图用一个“井”状结构来表示内存,每一个格子表示存储一个数据元素的空间,a1,a2,…,an按顺序存储在以b开始的连续地址空间中。b:表示表的首地址/基地址/元素a1的地址。p:表示1个数据元素所占据存储单元的数量。MaxLength:表示最大长度,通常为某个常数。线性表顺序存储结构定义及实现线性表顺序存储结构的定义如下:其中,elem是一个大小为MaxLength的数据元素数组;length为线性表表长;SqList为此结构类型定义的名称。线性表顺序存储结构定义及实现前面的顺序存储是静态分配的。静态分配是指在编译时分配给线性表一个固定大小的存储空间(一般是程序运行时逻辑空间的栈空间(StackSpace))。如果插入的元素超过这个存储空间的大小,就会发生溢出。为解决这一问题,引入了顺序存储结构的动态分配。线性表顺序存储结构定义及实现动态分配,即当数据元素超过所分配存储空间的大小时,在堆空间中再找一片更大的连续空间重新分配,将所有的数据元素放入。顺序存储的动态分配定义如下:其中,LIST_INIT_SIZE表示第一次为顺序表分配的存储空间大小;LISTINCREMENT表示次需要扩充存储空间时的增量;elem是一个元素的指针,保存存储空间中第一个数据元素的地址,并且一旦需要扩充线性表的存储空间,可能需要改变elem,使其指向新空间的起始位置。线性表顺序存储结构定义及实现1、插入元素顺序表插入新元素:设L.elem[0]…L.elem[MaxLength-1]中有length个元素,在第i个元素(L.elem[i-1])之前插入新元素e,其中1≤i≤length+1,当i等于length+1时,表示在尾部添加一个新元素。在线性表L=(a1,a2,…,ai-1,ai,ai+1,…,an)中的第i个元素前插入元素e,an、an-1……ai+1、ai均要依次往后移动,那么移动元素的下标范围是i-1~n-1或i-1~L.length-1,这里L.length的值为n。注意移动元素的方向是从an到ai方向依次把每个元素往后移动一个位置,如下图所示。线性表顺序存储结构定义及实现顺序表插入算法分为静态分配顺序表插入算法和动态分配顺序表插入算法。静态分配插入算法的基本思想为:先判断插入的位置是否合理,接着判断表长是否达到分配空间的最大值,然后从线性表中的最后一个元素到插入位置的所有元素,依次往后移动一个元素的位置,这样给待插入的元素留出一个空位置,最后把新增元素插入这个空位置,表长增加1,插入成功返回。在动态分配空间的顺序表中,如果插入元素使得表长超过所分配的空间大小,就利用realloc这个函数寻找更大片的连续地址空间。如果没有找到,说明还是会溢出(当然这种情况很少发生);如果找到了,就将基地址改为新的地址,分配的存储空间也增加。而动态分配插入新元素所做的操作与静态分配是一样的,均是移动相应元素。线性表顺序存储结构定义及实现2、删除元素在顺序表中删除元素也存在移动元素的过程,不过移动方向与插入元素相反。假如在顺序表中删除第i个元素ai,1≤i≤length,那么就将元素ai+1,…,an

依次往前移动,使得元素ai-1与元素ai+1相邻,ai元素就删除了,如下图所示:线性表顺序存储结构定义及实现顺序表删除元素算法的基本思想为:首先判断删除元素的下标是否存在,然后用一个for循环来移动元素,移动元素下标范围为i~length-1,最后修改表长为原表长减1。算法如下所示:03PART线性表链式存储结构定义及实现链式存储结构是指将线性表中的数据元素存放到计算机存储器内一组非连续存储单元中。在链式结构中,只能通过指针来维护数据元素间的关系。由于这个原因,对线性表中的元素只能进行顺序访问。单链表是链式存储结构中最基础、也是最具代表的一种存储结构形式。单链表是指线性表的每个结点分散地存储在内存空间中,先后依次用一个指针串联起来。单链表可以分为不带表头结点和带表头结点两种情形。线性表链式存储结构定义及实现不带表头结点的单链表如下图所示:结点包含两个属性:data称为数据域,它用于保存元素值;next称为指针域/链域,它用于保存直接后继元素结点的指针、维护元素间的线性关系。head为头指针,它用于存放首元素结点的指针。当head==NULL时,表示为空表,否则表示为非空表。线性表链式存储结构定义及实现带表头结点的单链表:①非空表。单链表中至少存储一个元素为非空表;其中,头指针head指向表头结点,表头结点的数据域不放元素,指针域指向首元素结点

a1。②空表。单链表中还没有存储数据元素为空表;当head->next==NULL时,表示为空表,否则表示为非空表。线性表链式存储结构定义及实现首先定义单链表的结构类型,每个结点有两个部分:一个部分是数据域data;另一个部分是指针域next。具体定义如下。指向结点的指针变量head、p、q可定义为:线性表链式存储结构定义及实现1、先进先出单链表单链表建立后,如果想要输出单链表中元素的值,则先加入单链表的元素会先输出、后加入的后输出。所以这个单链表也可以称为“先进先出”单链表。该单链表中一个结点的数据结构按如下方式定义:线性表链式存储结构定义及实现可以通过尾插法创建单链表,算法步骤如下。(1)生成表头结点,head和tail都指向表头结点。(2)输入元素的值

e,当元素不是结束标记时,重复下列操作,否则转至步骤(3)。①生成新结点p,e保存到p结点的数据域。②使用tail->next=p;将p结点链接到单链表的表尾。③使用tail=p;让tail指向当前的表尾结点。(3)使用tail->next=NULL;将最后一个结点的指针域赋值为空。(4)返回head,完成“先进先出”单链表的创建。线性表链式存储结构定义及实现2、先进后出单链表单链表建立后,如果想要输出单链表中元素的值,则先加入单链表的元素会后输出、后加入的先输出。这个单链表称为“先进后出”单链表。为实现创建“先进后出”单链表,每当输入一个元素后,生成的结点不是放在表尾而是插入表头,成为新的首元素结点,使用这种插入方式创建单链表的方法俗称首插法,首插法具体如下图所示。当前单链表中已有i个元素结点,元素输入次序为a1,…,ai,现输入第i+1个元素ai+1,具体操作为:第①步生成新结点p,并保存新元素ai+1;第②步通过p->next=head->next使得新结点指针指向原首结点;第③步通过head->next=p让表头结点的指针域指向新结点ai+1,不再指向ai,将新结点作为首元素,即可完成将新结点插入表头的操作。线性表链式存储结构定义及实现3、插入元素一般情况下插入元素的操作如下:(1)在已知p指针指向的结点后插入一个元素x首先用一个指针f指向新结点,该结点的数据域为x,然后此新结点next域赋值为p指针指向结点的next域,最后p指针指向结点的next域赋值为f。线性表链式存储结构定义及实现(2)在已知p指针指向的结点前插入一个元素x因为单链表每个结点只有一个指针指向其后继结点,如果在结点前插入一个新结点,就需要得到p指向结点的前驱结点指针,假设该指针为q,如下图所示。这样问题就转换成在指针q指向的结点之后插入一个结点,即将该问题(2)转换成问题(1)求解。线性表链式存储结构定义及实现循环单链表:有的时候,为了应用方便,我们可以将链表中最后一个结点的next域指向链表的第一个结点而形成一个环,这种单链表称为循环单链表。带表头结点的循环单链表示意图如下图所示。其中(a)为非空循环单链表,(b)为空循环单链表。线性表链式存储结构定义及实现在有些情况下,需要对单链表尾结点进行访问。为了提高算法的效率,此时还可采用只带尾指针tail,不带头指针的循环链单链表。在这种只带尾指针的循环单链表方式中,tail->next的值就是表头结点的指针,其相当于头指针,所以能方便地完成头指针循环单链表的各种操作。其中,(a)为只带尾指针的非空循环单链表,(b)为空循环单链表。线性表链式存储结构定义及实现双向链表:单链表和循环单链表每个结点中只有一个指针指向其后继。对于循环单链表,一个结点需要访问其前驱结点时要顺着next域扫描整个链表一遍,此时效率显然不高。这里为了方便访问结点的前驱结点而引入双向链表。在双向链表中,其中,每个结点除了数据域之外,还有两个指针域(一个指向其直接前驱结点,另一个指向其直接后继结点)。数据结构的定义如下。线性表链式存储结构定义及实现双向链表的一般形式如图(a)和图(b)所示,它们分别列出了非空双向链表和空双向链表。其中,L为头指针,L指向表头结点;通过首结点的next指针域可以依次访问各个结点;通过尾结点的prior指针域可以逆序访问链表中的各个结点。我们可以根据L->next==NULL是否成立来判断是否为空表。线性表链式存储结构定义及实现在实际应用中,一般会在双向链表中加上循环,形成双向循环链表。双向循环链表的一般形式如下图所示,我们可以根据L->next==L或L->prior==L是否成立来判断是否为空表。线性表链式存储结构定义及实现从双向链表中删除结点时,需要注意两个指针的变化。例如,已知双向链表中包含结点A、B、C,指针p指向结点B,删除B,那么所做的操作如下。向双向链表中插入结点时,也需要注意两个指针的变化。例如,已知双向链表中包含两个相邻结点A和C,指针p指向结点C,现在插入一个新的结点到A和C之间,由f指向该待插入的结点B,那么所做的操作如下。04PART顺序表与链表的比较

优点缺点

顺序表(1)顺序表是一种随机存取结构,存取任何元素的时间是一个常数,速度快;(2)结构简单,逻辑上相邻的元素在物理上也是相邻的;(3)不需要使用指针,节省存储空间;(4)顺序表在实现上使用的是连续的内存空间,我们可以借助CPU的缓存机制预读顺序表中的数据,所以访问效率更高(1)插入和删除元素要移动大量元素,需要消耗大量时间;(2)需要一块连续的存储空间;(3)插入元素可能发生“溢出”,不易扩充;(4)自由区中的存储空间不能被其他数据占用(共享),存在浪费空间的问题

链表

(1)插入和删除元素不需要移动大量元素,不需要消耗大量时间;(2)不需要一块连续的存储空间;(3)链表本身没有大小的限制,天然地支持动态扩容,插入元素一般不会发生“溢出”;(4)用多少就取多少,不存在自由区中的存储空间不能被其他数据占用(共享)的情况,从而避免浪费空间的问题(1)不是一种随机存取结构;(2)逻辑上相邻的元素在物理上不一定是相邻的;(3)需要使用指针,存储密度小于1,浪费一定的空间;(4)额外存储指针结点,频繁进行增删操作容易造成内存碎片;(5)链表在内存中并不是连续存储,对CPU缓存不友好,无法有效预读05PART应用实例递增有序单链表生成算法:例:输入一列整数,以

0

为结束标志,生成递增有序单链表(不包括

0)。首先以不带表头结点的单链表作为存储结构来设计算法。假设有一个递增有序的单链表,结点元素是1,4,10,17,…,78,现要插入一个新元素7,那么先从头指针开始访问单链表的结点,比较每个结点中的元素与7的大小,如果比7小,就往后进行访问;如果碰到一个结点的元素值比7大就停止访问,说明新元素7应该插入到此结点之前。前分析过,如果要把新结点插入某个结点之前,还需要用指针指向此结点的前驱结点,因此在从头指针开始扫描单链表时,应该用q、p两个指针来指向可能要插入的位置。开始时,p指向首结点,q指向刚访问过的结点,初始值为NULL。当p指向的结点数据小于待插入数据时,说明待插入数据要插入p结点之后的某个位置上,目前无法确定,这样需要首先将p赋予q,再移动p指向下一个结点,p和q同步移动。当p指向的结点数据大于待插入数据时,说明待插入数据要插入p结点之前,也就是刚访问过的结点q之后,完成定位。应用实例一般情况下,当q、p都不为空时,确定位置后,f指向的新结点就插入q与p之间,f指向新结点的next域赋值为p,q指向结点的next域赋值为f,具体操作为:f->next=p;q->next=f。但这种情况只是一般情形。在扫描单链表过程中,还可能碰到p、q可能为NULL的情况,此时需要进行特殊处理。(1)p、q同时为空,意味着往空表中插入第一个结点。(2)仅p为空,q不为空,尾部插入,即数据插入单链表的尾部。(3)仅q为空,p不为空,首部插入,即插入的数据作为单链表的第一个结点。(4)p、q均不为空,在p与q之间插入一个新结点。应用实例为了应对p、q可能为NULL的情况,保证算法的正确性,给出了下面的不带头结点插入算法。图中流程图的左边是通过p、q

两个指针从头指针开始扫描单链表,扫描过程中判断比较结点数据域值与插入结点数据域值;如果找到,就停止扫描,进行流程图右边的操作——将指针f

指向新插入的结点,接下来判断p、q

两个指针是否为空,分为①、②、③、④4

个分支(其中,①代表空表插入;②代表尾部插入;③代表首部插入;④代表一般插入),然后针对4

种不同的情况分别采取不同的操作。应用实例应用实例InsertList1算法是针对递增有序单链表插入一个新的元素,那么对输入的一组数生成一个递增有序的单链表只需要在主函数中调用此算法即可。每输入一个数,就调用此函数生成一个递增有序单链表,直到输入0为止。此主函数算法如下。应用实例为了生成带表头结点的递增有序单链表,给出下面的算法:首先从头指针head开始扫描单链表,按q=head;p=head->next进行初始化,然后根据待插入整数值的大小确定插入位置,用q、p两个指针来指向可能要插入的位置。由于在此情况下该递增有序单链表一定有一个不为空的头结点head,因此在确定插入位置后,q永远不为空。应用实例InsertList2算法是针对递增有序单链表插入一个新的元素,那么对输入的一组数生成一个递增有序的单链表只需在主函数中不断调用此算法。每输入一个数,就调用此函数把该整数插入该递增有序单链表中,直到输入0为止。此主函数算法如下。应用实例单链表插入、删除算法:单链表插入算法是在单链表的指定位置插入新元素,其中输入参数主要包

括头指针L、位置i、数据元素e,而输出为成功返回OK,否则ERROR。首先分析这个算法,在指定位置i上插入一个新的元素,使得插入的元素成为链表的第i个元素,那么需要运用指针p从头扫描单链表,并对所访问的结点进行计数。如果在第i个位置结束,指针p指向第i个位置,新元素就要插入指针p指向的结点之前;之前分析过,如果插入某个结点之前,就需要另一个辅助指针q来指向指针p的前驱结点。实际上,我们是可以简化此过程的,即计数到i-1的时候就停止,指针p指向这个结点,然后新元素插入指针p所指向结点的后面就达到目的了。扫描的过程即是执行p=L,当指针p指向的结点不为空时,则需执行p=p->next指令i-1次,从而定位到第i-1个结点。当i<1或指针p指向的结点为空时,则插入点错是错误的,否则新结点加到p指向结点之后。应用实例应用实例单链表删除元素:在单链表中删除某个结点,一定先找到将要被删除的结点,还要找到其前驱结点。例如,单链表中有3个结点,分别是A、B、C,q指针指向数据域为A的结点,p指针指向数据域为B的结点。如果删除数据域为B的结点,首先执行q->next=p->next;,即A的next域指向的地址(B的

next域),然后执行free(p);释放

p所指向的结点空间。第一个删除算法是在带表头结点的单链表中删除元素值为e的结点,第一步扫描此单链表以找到元素值为e的结点。应用实例这段代码执行后,要么p指针为空,要么p所指向结点数据域的值为e;找到要删除的结点后,就可以进行删除操作了。删除操作的代码如下:应用实例第二个算法是在单链表中删除指定位置的元素。在这个Delete算法中,删除第i个元素时,该结点的前驱结点同样重要,因此定位结点也是定位在i-1这个位置上,即执行p=L;,L是头指针,当p->next不为空时执行p=p->next;i-1次,从而定位到第i-1个结点。接着,判断i值,当i<1或p->next为空时删除点位置出错,否则p指向后继结点的后继,从而将第i个元素结点从单链表中删除。应用实例单链表合并算法:将两个带表头结点的有序单链表La和Lb合并为有序单链表Lc,该算法利用原单链表的结点。单链表合并示意图如图所示。应用实例根据上图,单链表La有元素2、5,Lb有元素3、8、20,在合并中要用到3个指针pa、pb、pc。其中pa用来扫描单链表La,pb用来扫描单链表Lb,pc用来指向合并过程中得到的链表Lc的当前表尾结点,合并操作就是通过这3个指针的变化来完成的。开始时,Lc中还没有结点,所以尾结点就是表头结点,pc首先指向La的表头结点,pa、pb分别指向单链表La和Lb的首结点。然后比较pa和pb所指向两个结点的数据域值大小,如果pa指向结点的数据域值小,就将pa指向的结点插入pc指向的结点后面,pc指向刚插入的结点,pa往后面移动,否则就将pb指向的结点插入pc指向的结点后面,pc指向刚插入的结点,pb往后面移动。接着不断按照这种模式比较pa和pb所指向结点的数据域值大小,pa、pb、pc指针根据不同的情况发生变化,直到pa或pb为空为止。如果pa为空,表示链表La已经扫描结束,则将pb指向的结点以及后面所有结点链接在pc指向的结点之后;反之,如果pb为空,表示链表Lb已经扫描结束,则将pa指向的结点以及后面所有结点链接在pc指向的结点之后。应用实例单链表的逆置:假定以单链表作为线性表L=(a1,a2,…,an)的存储结构,现要求设计算法,将线性表逆置为L=(an,an-1,…,a1)。这个问题就涉及将一个单链表中结点翻转的操作,下面给出该问题求解的4种算法,并进行效率分析。1.递归算法一:采用递归的思想完成逆置操作。当为空单链表时,作为递归出口,直接返回;对非空单链表,首先将最后一个结点从单链表中移出,将剩下长度减一的单链表翻转过来,再将刚移出的结点作为首结点插入这个单链表中。该算法每次为了移出最后一个结点,需要对单链表遍历一次,共需要做n次移出结点操作,平均每次访问结点的个数为n/2,所以算法的时间复杂度T(n)=O(n2);递归深度为n,每一次递归调用时,都会在运行时逻辑空间的栈空间中分配单元,所以空间复杂度S(n)=O(n)。应用实例2.递归算法二:同样是采用递归算法,这里换一种移出结点的方法。当单链表结点个数不大于1时,作为递归出口,直接返回;对有2个或2个以上结点的单链表,首先将第一个结点从单链表中移出,将剩下长度

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论