1、数据结构讲义,1,2018年9月20日,教学内容:2.1 线性表逻辑结构;2.2 线性表的顺序存储及运算实现;2.3 线性表的链式存储和实现。 教学目的:理解线性表的定义及其运算; 理解顺序表和链表的定义、组织形式、结构特征和类型说明;掌握在这两种表上实现的插入、删除和按值查找的算法;了解循环链表、双(循环)链表的结构特点和在其上施加的插入、删除等操作。,第二章 线性表,数据结构讲义,2,2018年9月20日,教学重点:线性表的定义及逻辑上的特点;顺序表上插入、删除和定位运算的实现;单链表的结构特点及类型描述;头指针和头结点的作用及区别;定位、删除、插入运算在单链表上的实现;循环链表、双链表的
2、结构特点,循环链表、双链表上删除与插入运算的实现。 教学难点:链式存储的表示及其运算的实现;头结点在链表中的作用;指针操作;链表中删除、插入运算中的指针操作顺序;双链表上指针的操作顺序。 教学时数: 8学时,数据结构讲义,3,2018年9月20日,第二章 线性表 线性表是线性结构的基本形式,2.1 线性表的逻辑结构 2.2 线性表的顺序存储及运算实现 2.3 线性表的链式存储和运算实现 2.4 顺序表和链表的比较,数据结构讲义,4,2018年9月20日,数据结构的内容包括三个层次的五个“要素”,数据结构讲义,5,2018年9月20日,2.1 线性表的逻辑结构,线性表的定义线性表的基本操作,数据
3、结构讲义,6,2018年9月20日,2.1 线性表的逻辑结构,线性表的定义线性结构的特点是数据元素之间是一种线性关系,数据元素“一个接一个的排列”。线性表是具有相同数据类型的n(n=0)个数据元素的有限序列,记为:(a1,a2, ai-1,ai,ai+1,an);其中:n为表长, n0 时称为空表。 表中相邻元素之间存在顺序关系:ai-1 称为ai的直接前趋,ai+1称为ai的直接后继。a1, an-1有且仅有一个直接后继;a2, an有且仅有一个直接前趋。,数据结构讲义,7,2018年9月20日,2.1 线性表的逻辑结构,线性表的基本操作 : 线性表初始化:Init_List(L) 求线性表
4、长度:Length_List(L) 取表元:Get_List(L,i) 按值查找:Locate_List(L,x) 插入操作:Insert_List(L,i,x) 删除操作:Delete_List(L,i),数据结构讲义,8,2018年9月20日,借助于C+的类来定义一个线性表的抽象类型,线性表类的声明如下:Const int MaxSize=;Template Class SeqListPublic;int Length();T Get_List(i);int Locate(T x);void Insert(int i,T x); T Delete(i);Private:T dataMaxS
5、ize;int length;,数据结构讲义,9,2018年9月20日,需要说明的是:某数据结构上的基本运算,不是它的全部运算,而是一些常用的基本的运算,而每一个基本运算在实现时也可能根据不同的存储结构派生出一系列相关的运算来。在上面各操作中定义的线性表仅仅是一个抽象在逻辑结构层次的线性表,尚未涉及到它的存储结构。,数据结构讲义,10,2018年9月20日,2.2 线性表的顺序存储及运算实现,线性表的顺序存储 顺序表上基本运算的实现 顺序表应用举例,数据结构讲义,11,2018年9月20日,2.2.1 线性表的顺序顺序存储,线性表的顺序存储是指在内存中用地址连续的一块存储空间顺序存放线性表的各
6、元素,用这种存储形式存储的线性表称其为顺序表。,存储的特点:物理上的相邻实现了逻辑相邻的表示。,数据结构讲义,12,2018年9月20日,顺序存储能随机访问第i个元素:设 a的存储地址为Loc(a),每个数据元素占d个存储地址,则第i个数据元素的地址为:Loc(ai)=Loc(a)+(i-1)*d 1In,last的作用?,存储的特点:物理上的相邻实现了逻辑相邻的表示。,数据结构讲义,13,2018年9月20日,用C支持的数据类型将上面的存储思想描述出来:设数据元素的类型为: DataType, 则:DataType dataMAXSIZE; int last;从结构性上考虑,通常将 data
7、 和 last 封装成一个结构作为顺序表的类型: typedef struct DataType dataMAXSIZE; int last; SeqList;,数据结构讲义,14,2018年9月20日,定义顺序表变量L:SeqList L ;,则线性表中的元素a1 an 存放在: L.data0 L.dataL.last 中L.last 是最后元素an的相对地址。,数据结构讲义,15,2018年9月20日,采用C/C+描述算法时定义一个指向SeqList 类型的指针更方便:SeqList *L ; L=(SeqList *)malloc(sizeof(SeqList);(或者L=new Se
8、qList)/来获得线性表的存储空间/L中存放的是顺序表的地址,注意表示:表长为(*L).last+1或 Llast+1。线性表的存储区域为 (*L).data或L-data 。线性表中数据存储空间为: Ldata0 LdataLlast。,数据结构讲义,16,2018年9月20日,2.2.2 顺序表上基本运算的实现, 顺序表的初始化顺序表的初始化即构造一个空表。对表是一个加工型的运算,因此,将 L设为指针参数,首先动态分配存储空间,然后,将表中 last 指针置为1,表示表中没有数据元素。,数据结构讲义,17,2018年9月20日,【算法2-1】 SeqList *init_SeqList(
9、) SeqList *L;L=new SeqList; /申请顺序表的存储空间if (L) L-last=-1;return L; /返回顺序表的存储地址elsereturn 1; /申请不成功,返回错误代码-1 ,设调用函数为主函数,主函数对初始化函数的调用如下:main()SeqList *L;L=Init_SeqList(); ,数据结构讲义,18,2018年9月20日,插入运算,线性表的插入是指在表的第i个位置上插入一个值为 x 的新元素。,数据结构讲义,19,2018年9月20日,【算法2-2】 顺序表的插入算法 int Insert_SeqList(SeqList *L,int i
10、,DataType x)int j;if (L-last=MAXSIZE-1) /检查表空间 return -1; /表空间已满,不能插入,返回错误代码-1if (iL-last+2) /检查插入位置的正确性return 0; /插入位置参数错,返回错误代码0for (j=L-last;j=i-1;j-)L-dataj+1=L-dataj; /结点移动 L-datai-1=x; /新元素插入L-last+; /last指向新的最后元素return 1; /插入成功,返回成功代码1 ,数据结构讲义,20,2018年9月20日,插入算法的时间性能分析,顺序表插入运算时间主要消耗:数据的移动。 计算
11、数据移动的次数:某次插入数据的移动次数与具体位置有关。求平均性能。 在第i个位置上插入 x ,从 ai 到 an 都要向下移动一个位置,共需要移动 ni1个元素。i 的取值范围为 :1 i n+1(即有 n1个位置可以插入)。设在第i个位置上作插入的概率为Pi:,数据结构讲义,21,2018年9月20日,在等概率情况下:Pi=1/ (n+1) ,则平均移动数据元素的次数则为:,这说明:在顺序表上做插入操作需移动表中一半的数据元素。显然顺序表上插入时间复杂度为(n)。,数据结构讲义,22,2018年9月20日,删除运算,线性表的删除运算是指将表中第 i 个元素从线性表中去掉。,数据结构讲义,23
12、,2018年9月20日,【算法 2-3】 顺序表的删除算法 int Delete_SeqList(SeqList *L,int i)int j;if (iL-last+1) /检查空表及删除位置的合法性return 0; /不存在第i个元素,返回错误代码0for (j=i;jlast;j+)L-dataj-1=L-dataj; /数据元素向前移动L-last-; /last指向新的最后元素return 1; /删除成功,返回成功代码1 ,数据结构讲义,24,2018年9月20日,删除算法的时间性能分析,与插入运算相同,其时间主要消耗在了移动元素上。计算数据移动的次数:某次删除数据的移动次数与具
13、体位置有关。求平均性能。 删除第i个元素时,其后面的元素 ai+1an 都要向上移动一个位置,共移动了 n-i 个元素。i 的取值范围为 :1 i n(即有 n个位置可以删除)。设在第i个位置上作删除的概率为Pi,平均移动数据元素的次数:,数据结构讲义,25,2018年9月20日,这说明顺序表上作删除运算时大约需要移动表中一半的元素。 显然该算法的时间复杂度为(n)。,在等概率情况下:Pi=1/ n ,则平均移动数据元素的次数则为:,数据结构讲义,26,2018年9月20日,按值查找,按值查找是指在线性表中查找与给定值x相等的数据元素。,【算法2-4】 顺序表的按值查找算法 int Locat
14、ion_SeqList(SeqList *L,DataType x)int i;i=0;while (ilast /查找成功,返回数据元素在顺序表中的存储位置 ,数据结构讲义,27,2018年9月20日,主要运算是比较。比较的次数与 x 在表中的位置有关,也与表长有关。如:当 a1=x 时,比较一次成功。当 ai=x 时比较 i次成功。i 的取值范围为 :1 i n设的等概率查找第i个位置上元素i,则平均比较次数:,算法的性能分析,数据结构讲义,28,2018年9月20日,2.2.3 顺序表应用举例,例2.1 将顺序表 (a1,a2,. ,an) 重新排列为以 a1 为界的两部分:a1 前面的
15、值均比 a1 小,a1 后面的值都比 a1 大。 (划分的方法有多种,下面介绍的划分算法其思路简单,性能较差。),数据结构讲义,29,2018年9月20日,基本思路:从第二个元素开始到最后一个元素,逐一向后扫描:当前数据元素 aI 比 a1 大时,表明它已经在 a1 的后面,不必改变它与 a1 之间的位置,继续比较下一个。当前结点若比 a1 小,说明它应该在 a1 的前面,此时将它上面的元素都依次向下移动一个位置,然后将它置入最上方。,数据结构讲义,30,2018年9月20日,【算法2-5】顺序表划分算法 void part(SeqList *L)int i,j;DataType x,y; x
16、=L-data0; /将“基准”缓存 x 中for (i=1;ilast;i+)if (L-dataidatai;for (j=i-1;j=0;j-) /前面所有数据元素向后移动Ldataj+1=L-dataj;L-data0=y; /将当前元素置入最前面位置 ,数据结构讲义,31,2018年9月20日,即最坏情况下移动数据时间性能为()。改变思路,可以设计出O(n)级别的算法。最好情况:不需移动,分析:本算法中,有两重循环,外循环执行n1次,内循环中移动元素的次数与当前数据的大小有关,当第个元素小于 a1 时,要移动它上面的 i-1个元素,再加上当前结点的保存及置入,所以移动 i-1+2次。
17、在最坏情况下,a1 后面的结点都小于 a1 ,故总的移动次数为 :,前页,数据结构讲义,32,2018年9月20日,例2.2 有顺序表A和B,其元素均按从小到大的升序排列,编写一个算法将它们合并成一个顺序表C,要求C的元素也是从小到大的升序排列。 A: 1, 4 ,6, 9 B:1, 5, 7, 8, 10 C: 1 ,1 ,4 ,5, 6, 7, 8 ,9, 10,数据结构讲义,33,2018年9月20日,算法思路:依次扫描通过A和B的元素,比较当前的元素的值,将较小值的元素赋给C,如此直到一个线性表扫描完毕,然后将未完的那个顺序表中余下部分赋给C即可。C的容量要能够容纳A、B两个线性表相加
18、的长度。,数据结构讲义,34,2018年9月20日,【算法2-6】有序表的合并算法 void merge(SeqList A,SeqList B,SeqList *C)int i,j,k; /i,j,k分别为顺序表A、B、C当前元素指针i=0; j=0; /i,j分别指向顺序表A、B当前待处理元素k=0; /k指向顺序表C带插入元素位置while (idatak=A.datai;k+; i+;else C-datak=B.dataj;k+; j+;,数据结构讲义,35,2018年9月20日,算法的时间性能是O(m+n),其中m是A的表长,n是B的表长。,while (idatak=A.data
19、i;k+; i+;while (jdatak=B.dataj;k+; j+;C-last=k-1; /将last指针指向最后一个元素 ,前页,数据结构讲义,36,2018年9月20日,例2.3 比较两个线性表的大小。两个线性表的比较依据下列方法:设A、B是两个线性表,均用向量表示,表长分别为m和n。 A和B分别为 A 和 B 中除去最大共同前缀后的子表。例如A=(x,y,y,z,x,z), B=(x,y,y,z,y,x,x,z),两表最大共同前缀为 (x,y,y,z) 。 则A=(x,z),B=(y,x,x,z)若A= B= 空表,则A=B;若A=空表且B空表,或两者均不空且A首元素小于B首元
20、素,则AB。,数据结构讲义,37,2018年9月20日,算法思路:首先找出A、B的最大共同前缀;然后求出A和B,之后在按比较规则进行比较,AB 函数返回1;A=B返回0;AB返回-1。,数据结构讲义,38,2018年9月20日,算法的时间性能是O( m+n )。,【算法2-7】比较线性表大小算法 int compare(int A,int B,int m,int n) int i=0,j,AS,BS,ms,ns; /AS,BS作为A,Bwhile (i0 | ms0 /A表大于B表 ,数据结构讲义,39,2018年9月20日,2.3 线性表的链式存储和运算实现,单链表循环链表双向链表静态链表单
21、链表应用举例,顺序表存储特点:用物理上的相邻实现了逻辑上的相邻。 它要求用连续的存储单元顺序存储线性表中的各元素,因此,对顺序表的插入、删除需要通过移动数据元素来实现,影响了运行效率。 本节介绍线性表链式存储结构,是通过“链”建立起数据元素之间的逻辑关系,不要求逻辑上相邻的两个数据元素物理上也相邻,因此不需要用地址连续的存储单元来实现。,数据结构讲义,40,2018年9月20日,2.3.1 单链表,链表是通过一组任意的存储单元来存储线性表中的数据元素的,对每个数据元素ai,除了存放数据元素的自身的信息 ai 之外,还需要和ai一起存放其后继 ai+1 所在的存贮单元的地址,这两部分信息组成一个
22、“结点”。存放数据元素信息的称为数据域,存放其后继地址的称为指针域。,数据结构讲义,41,2018年9月20日,链表是由一个个结点构成的。结点定义如下: typedef struct node DataType data;struct node *next; LNode,*LinkList;定义头指针变量: LinkList H;,链表的表示:,结点的表示:,数据结构讲义,42,2018年9月20日,相关知识结点的申请:p=new LNode;结点的释放:delete p;,数据结构讲义,43,2018年9月20日,2.3.2 单链表上基本运算的实现,在链表的头部插入结点建立单链表,建立单链表
23、,在链表的头部插入结点建立单链表的算法,数据结构讲义,44,2018年9月20日,【算法2-8】建立单链表的算法一 #define Flag /Flag为根据实际情况设定的结束数据输入的标志数据 LinkList Creat_LinkList1()LinkList L;LNode *s;int x; /设数据元素的类型为intL=NULL; /首先置为空表cinx;while (x!=Flag) s=new LNode;s-data=x;s-next=L;L=s;cinx;return L; ,数据结构讲义,45,2018年9月20日,在单链表的尾部插入结点建立单链表,数据结构讲义,46,20
24、18年9月20日,【算法2-9】建立单链表的算法二 #define Flag /Flag为根据实际情况设定的结束数据输入的标志数据 LinkList Creat_LinkList2()LinkList L; LNode *s,*r;int x; /设数据元素的类型为intL=r=NULL; cinx;while (x!=Flag) /Flag表示输入结束 s=new LNode; s-data=x;if (L=NULL)L=s; /第一个结点的处理elser-next=s; /其他结点的处理r=s; /r 指向新的尾结点cinx;if (r!=NULL)r-next=NULL; /对于非空表,
25、最后结点的指针域置空指针return L; ,数据结构讲义,47,2018年9月20日,头结点:“第一个结点”的问题;空表问题;,数据结构讲义,48,2018年9月20日, 求表长,设L是带头结点的单链表(线性表的长度不包括头结点)。【算法2-10】带头结点的单链表求表长算法 int Length_LinkList1(LinkList L)(LNode *p; int i;p=L; /p指向头结点i=0;while (p-next) p=p-next;i+; /p指向第 i 个结点return i; ,数据结构讲义,49,2018年9月20日,设L是不带头结点的单链表,【算法2-11】不带头结
26、点的单链表求表长算法 int Length_LinkList2(LinkList L)LNode *p;int i;p=L; i=0; /初始化表长变量为0while (p) i+;p=p-next; /p指向第 i+1 个结点return i; ,数据结构讲义,50,2018年9月20日, 查找操作,按序号查找 Get_Linklist(L,i)算法思路:从链表的第一个元素结点起,判断当前结点是否是第i个,若是,则返回该结点的指针,否则继续后一个,表结束为止。没有第个结点时返回空指针。,数据结构讲义,51,2018年9月20日,LNode *Get_LinkList(LinkList L,I
27、nt i)/在单链表L中查找第i个元素结点,找到返回其指针;否则返回空指针LNode *p;int j;p=L-next; /p指向第1个数据元素结点j=1;while (p!=NULL ,数据结构讲义,52,2018年9月20日,按值查找即定位 Locate_LinkList(L,x)算法思路:从链表的第一个元素结点起,判断当前结点值是否等于x,若是,返回该结点的指针,否则继续后一个,表结束为止。找不到时返回空指针。,【算法2-13】按值查找链表数据元素算法 LNode *Locate_LinkList(LinkList L,DataType x)/在单链表L中查找值为x的结点,找到后返回其
28、指针,否则返回空LNode *p;p=L-next; /p指向第1个数据元素结点while (p!=NULL ,算法2-12、算法2-13的时间复杂度均为O(n)。,数据结构讲义,53,2018年9月20日, 插入操作,在某结点后面插入新结点:设p指向单链表中某结点,s指向待插入的值为x的新结点,将*s插入到*p的后面。 操作如下: s-next=p-next; p-next=s; 注意:两个指针的操作顺序不能交换。,数据结构讲义,54,2018年9月20日,在某结点前面插入新结点:设指向链表中某结点,指向待插入的值为x的新结点,将*s插入到*p的前面。与后插不同的是:首先要找到*p的前驱*q
29、,然后再完成在*q之后插入*s。设单链表头指针为L,操作如下: q=L; while (q-next!=p)q=q-next; /找*p的直接前驱 s-next=q-next; q-next=s;,数据结构讲义,55,2018年9月20日,插入运算 Insert_LinkList(L,i,x)算法思路:1.找到第i-1个结点;若存在继续2,否则结束。2.申请、填装新结点。3.将新结点插入,结束。,算法2-14的时间复杂度为O(n)。,数据结构讲义,56,2018年9月20日,算法2-14的时间复杂度为O(n)。,【算法2-14】链表的插入算法 int Insert_LinkList(LinkL
30、ist L,int i,DataType x)/在单链表L的第i个位置上插入值为x的元素LNode *p,*s;p=Get_LinkList(L,i-1); /查找第i-1个结点if (p=NULL) return 0; /第i-1个不存在,不能进行插入操作else s=new LNode; /申请、填装结点s-data=x; s-next=p-next; /新结点插入在第i-1个结点的后面p-next=s;return 1; ,数据结构讲义,57,2018年9月20日, 删除操作,要实现对结点*p的删除,首先要找到*p的前驱结点*q,然后完成指针的操作即可。,删除结点:设p指向单链表中某结点
31、,删除*p。,q=L; while(q-next!=p) q=q-next;/找*p前驱;q-next=p-next; /指针的操作free(p);时间复杂性为O(n)。,数据结构讲义,58,2018年9月20日,若要删除*p的后继结点(假设存在),则可以直接完成:s=p-next;p-next=s-next;free(s); 该操作的时间复杂性为O(1) 。,图2-15 删除p的后继结点,数据结构讲义,59,2018年9月20日,删除运算:Del_LinkList(L,i)算法思路:1.找到第i-1个结点;若存在继续2,否则结束。2.若存在第i个结点则继续3,否则结束。3.删除第i个结点,结
32、束。,数据结构讲义,60,2018年9月20日,算法2-15的时间复杂度为O(n)。,【算法2-15】链表的删除运算 int Del_LinkList(LinkList L,int i) /删除单链表L上的第i个数据结点LNode * p,*s;p=Get_LinkList(L,i-1); /查找第i-1个结点if (p=NULL) return -1; /第i个结点的前驱结点不存在,不能进行删除操作,返回错误代码-1else if (p-next=NULL) return 0; /第i个结点不存在,不能进行删除操作,返回错误代码0else s=p-next; /s指向第i个结点p-next=
33、s-next; /从链表中摘除s结点delete s; /释放s结点空间return 1; ,数据结构讲义,61,2018年9月20日,2.3.3 循环链表,对于单链表而言,最后一个结点的指针域是空指针,如果将该链表头指针置入该指针域,则使得链表头尾结点相连,就构成了单循环链表。,数据结构讲义,62,2018年9月20日,在单循环链表上的操作基本上与非循环链表相同,只是将原来判断指针是否为NULL变为是否是头指针而已,没有其它较大的变化。对于单循环链表则可以从表中任意结点开始遍历整个链表;另外,有时对链表常做的操作是在表尾、表头进行,此时可以改变一下链表的标识方法,不用头指针而用一个指向尾结点
34、的指针R来标识,可以使得操作效率提高。,数据结构讲义,63,2018年9月20日,例:对两个单循环链表H1 、H2的连接操作,是将H2的第一个数据结点接到H1的尾结点,如用头指针标识,则需要找到第一个链表的尾结点,其时间复杂性为O(n),而链表若用尾指针r1、r2来标识,则时间性能为O(1)。操作如下: P= Rnext; /保存第一个表的头结点指针 R-next=R2-next-next; /头尾连接 free(R2-next); /释放第二个表的头结点 R2-next=P; /组成循环链表,数据结构讲义,64,2018年9月20日,2.3.4 双向链表,单链表的结点中只有一个指向其后继结点
35、的指针域next,找后继的时间性能是O(1),找前驱的时间性能是O(n); 可以用空间换来时间:付出空间的代价使得找前驱的时间性达到O(1):每个结点再加一个指向前驱的指针域。用这种结点组成的链表称为双向链表。,数据结构讲义,65,2018年9月20日,双向链表结点的定义如下: typedefstruct dlnode datatype data;structdlnode *prior,*next; DLNode, *DLinkList;,p-prior-next = p= p-next-prior,数据结构讲义,66,2018年9月20日,和单链表类似,双向链表通常也是用头指针标识,也可以带
36、头结点和做成循环结构。通过某结点的指针p即可以直接得到它的后继结点的指针p-next,也可以直接得到它的前驱结点的的指针p-prior。这样在有些操作中需要找前驱时,则勿需再用循环。,数据结构讲义,67,2018年9月20日,在双向链表中插入一个结点: s-prior=p-prior;p-prior-next=s;s-next=p;p-prior=s;,上面指针操作的顺 序不是唯一的,但也不是任意的:操作必须要放到操作的前面完成,否则*p的前驱结点的指针就丢掉了。,数据结构讲义,68,2018年9月20日,设p指向双向链表中某结点,删除*p。操作如下:p-prior-next=p-next;p
37、-next-prior=p-prior;free(p);,在双向链表中删除指定结点:,数据结构讲义,69,2018年9月20日,2.3.5 静态链表,右图表现的是一个较大规模的结构数组sdMAXSIZE。 其中有两个链表 -SL和AV。 (1) 链表SL是一个带头结点的单链表,表示了用户的线性表(a1, a2, a3, a4, a5); (2) 单链表AV是将当前 sd 中的空结点组成的链表。,链式存储的思想也可以通过静态指针来实现。 用静态指针实现的链表称为静态链表。,SL是用户的线性表; AV模拟的是系统的存储池中空闲结点组成的链表。,数据结构讲义,70,2018年9月20日,数组sd的定
38、义如下: #define MAXSIZE /足够大的数 typedef struct DataType data;int next; /指针域SNode;SNode sdMAXSIZE; int SL,AV; /两个头指针变量,数据结构讲义,71,2018年9月20日,申请结点:自己模拟而不使用系统的函数。,所得到的结点地址(下标)存入了 t 中。 不难看出当AV表非空时,摘下了第一个结点给用户),if(AV != -1) t=AV;AV=sdAV.next; ;,数据结构讲义,72,2018年9月20日,当用户不再需要某个结点时,需通过该结点的相对地址 t 将它还给AV。,释放结点,(交给A
39、V表的结点链在了AV的头部。 ),相关操作为:sdt.next=AV;AV=t;,数据结构讲义,73,2018年9月20日,例 在带头结点的静态链表SL的第i个结点之前插入一个值为x的新结点。 (设静态链表的存储区域sd为全局变量),【算法2-16】静态链表的插入算法 SNode sdMAXSIZE; int Insert_SList(int SL,DataType x,int i)int j; int p,s;p=SL; j=0;while (sdp.next!=-1 /插入位置不正确,插入失败,返回失败代码-1 ,数据结构讲义,74,2018年9月20日,2.3.6 单链表应用举例,例2-
40、5 已知单链表H,写一算法将其倒置。,算法思路:依次取原链表中的每个结点,将其作为第一个结点插入到新链表中去,指针p用来指向原表中当前结点,p为空时结束。,数据结构讲义,75,2018年9月20日,【算法2-17】链表的倒置算法 void reverse(Linklist H)LNode *p;p=H-next; /p指向第一个数据元素结点H-next=NULL; /将原链表置为空表H while (p) q=p;p=p-next;q-next=H-next; /将当前结点插到头结点的后面H-next=q; ,数据结构讲义,76,2018年9月20日,例2 已知单链表L,写一算法,删除其重复结
41、点。,算法思路:用指针p指向第一个数据元素结点,从它的后继结点开 始到表的结束,查找与其值相同的结点并删除之;p指向下一个结点;依此类推,p指向最后结点时算法结束。,数据结构讲义,77,2018年9月20日,【算法2-18】删除链表中重复结点算法 void pur_LinkList(LinkList H)LNode *p,*q,*r;p=H-next; /p指向第一个数据元素结点while (p) q=p;while (q-next) /从p结点的后继结点开始找重复结点if (q-next-data=p-data) r=q-next; /找到重复结点,用指针r指向该结点 q-next=r-ne
42、xt; /从链表中摘除r结点delete r; /释放r结点所占空间elseq=q-next;p=p-next; /p指向下一个结点,继续 ,数据结构讲义,78,2018年9月20日,例3 设有两个单链表A、B,其中元素递增有序,编写算法将A、B归并成一个按元素值递减(允许有相同值)有序的链表C,要求用A、B中的原结点形成,不能重新申请结点。,算法思路:利用A、B两表有序的特点,依次进行比较,将当前值较小者摘下,插入到C表的头部,得到的C表则为递减有序的。,数据结构讲义,79,2018年9月20日,【算法2-19】2个链表归并算法 LinkList merge(LinkList A,LinkL
43、ist B) /设A、B均为带头结点的单链表LinkList C; LNode *p,*q;p=A-next; q=B-next;C=A; C-next=NULL; /C表的头结点delete B; /释放B表头结点空间while (p ,数据结构讲义,80,2018年9月20日,2.4 顺序表和链表的比较,本章介绍了线性表的逻辑结构及它的两种存储结构:顺序表和链表。顺序存储有三个优点:(1) 方法简单,各种高级语言中都有数组,容易实现。(2) 不用为表示结点间的逻辑关系而增加额外的存储开销。(3) 顺序表具有按元素序号随机访问的特点。,数据结构讲义,81,2018年9月20日,但它也有两个缺
44、点: 在顺序表中做插入删除操作时,平均移动大约表中一半的元素,因此对n较大的顺序表效率低。 需要预先分配足够大的存储空间,估计过大,可能会导致顺序表后部大量闲置;预先分配过小,又会造成溢出。链表的优缺点恰好与顺序表相反。,数据结构讲义,82,2018年9月20日,在实际中怎样选取存储结构呢?通常有以下几点考虑: 基于存储的考虑对线性表的长度或存储规模难以估计时,不宜采用顺序表;链表不用事先估计存储规模,但链表的存储密度较低。 基于运算的考虑如果经常做的运算是按序号访问数据元素,顺序表优于链表;在顺序表中做插入、删除操作时平均移动表中一半的元素,在链表中作插入、删除操作,虽然也要找插入位置,但操作主要是比较操作,从这个角度考虑后者优于前者。 基于环境的考虑顺序表容易实现,任何高级语言中都有数组类型,链表的操作是基于指针的,相对来讲前者简单些,也是用户考虑的一个因素。两种存储结构各有长短,通常“较稳定”的线性表选择顺序存储,而频繁做插入删除的即动态性较强的线性表宜选择链式存储。,