1 / 48

第二章 线性表

第二章 线性表. 2 . 1 线性表的定义及运算 2 . 1 . 1 线性表的定义及逻辑特征 2 . 1 . 2 线性表上运算的定义 2 . 1 . 3 线性表的存储结构 2 . 2 顺序表 2 . 2 . 1 顺序表的定义及表示 2 . 2 . 2 线性表运算在顺序表上的实现 2 . 2 . 3 顺序表应用举例 2 . 3 链表 2 . 3 . 1 链表的定义及形式 2 . 3 . 2 单链表 2 . 3 . 3 循环链表 2 . 3 . 4 双链表 * 2 . 3 . 5 静态链表

louie
Download Presentation

第二章 线性表

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第二章 线性表 2.1 线性表的定义及运算 2.1.1 线性表的定义及逻辑特征 2.1.2 线性表上运算的定义 2.1.3 线性表的存储结构 2.2 顺序表 2.2.1 顺序表的定义及表示 2.2.2 线性表运算在顺序表上的实现 2.2.3 顺序表应用举例 2.3 链表 2.3.1 链表的定义及形式 2.3.2 单链表 2.3.3 循环链表 2.3.4 双链表 *2.3.5 静态链表 2.3.6 单链表的应用举例 2.4 顺序表和链表的比较

  2. 第二章 线性表 • 2.1 线性表的定义及运算 • 线性表的定义及逻辑特征 • 线性表(Linear List):是具有相同属性的n(n≥0)个数据元素的有限序列。 • 线性表的长度:数据元素的个数n。当n=0时称为空表,即表中不含任何元素。通常将非空的线性表(n>0)记为: L =(a1,a2,…,ai-1,ai,ai+1,…,an) • 线性表是线性结构,它的逻辑特征:有且仅有一个开始结点和一个终端结点,其余的内部结点都有且仅有一个前趋和一个后继。对于一个非空的线性表,有且仅有一个表头元素a1,即开始结点,它没有前趋但有后继;对于ai,当 i=2,…,n-1 时,有且仅有一个前趋 ai-1,有且仅有一个后继 ai+1;有且仅有一个表尾元素an,即终端结点,它无后继有前趋。

  3. 线性表可以用二元组表示为: linear_list =(D,S),r∈S, D ={a1,a2,…,ai-1,ai,ai+1,…,an} r ={< a1,a2>,< a2,a3>,…,< ai-1,ai >, < ai,ai+1>,…,< an-1,an > } • 线性表的逻辑结构图示 • 通用类型标识符 DataType 来表示数据元素的类型 例:typedef char DataType;

  4. 线性表上运算的定义 (1)线性表初始化initList(L):构造一个空的线性表。 (2)求线性表的长度lengthList(L):返回线性表L中所含元素的个数。 (3)取第i个元素getElem(L,i):当1≤i≤n,返回线性表L中第i个元素的值。 (4)按值查找locateElem(L,x):x与表中元素的类型相同,在表L中查找值为x的数据元素,若找到,返回L中第一次出现的值为x的那个元素的序号,称为查找成功;否则,返回一特殊值(0)表示查找失败。 (5)插入insertElem(L,i,x):在线性表L的第i个位置插入一个值为x的新元素,这样使原序号为 i,i+1,…,n的数据元素的序号变为i+1,i+2,…,n+1,插入后表长等于原表长加1,插入位置可以为1≤i≤n+1。 (6)删除操作deleteElem(L,i):在线性表L中删除序号为i的数据元素,删除后使序号为 i+1,i+2,…,n 的元素的序号变为 i,i+1,…,n-1,新表长等于原表长减1,删除位置可以为1≤i≤n。

  5. 线性表的存储结构 • 顺序存储、链接存储、索引存储、散列存储,这四种方法都可以用来存储线性表。 • 顺序存储和链接存储是线性表最常用的两种存储方法。 • 线性表用顺序方式存储就称为顺序表。 • 线性表用链接方式存储就称为链表。

  6. 2.2 顺序表 • 顺序表的定义及表示 • 顺序表(Sequential List)是采用顺序存储结构的线性表。也就是将线性表中逻辑上相邻的结点存储在物理上相邻的单元中,使得线性表的所有元素按逻辑次序依次存放到一组地址连续的存储空间内。 • 顺序表存储结构用C语言描述如下: #define MAXSIZE 1024 typedef int DataType; typedef struct { DataType data[MAXSIZE]; int last; } SeqList; SeqList SL;

  7. 线性表的顺序存储结构图示 • 线性表第i个数据元素的地址公式: LOC(ai) = LOC(a1)+(i-1)×k 1≤i≤n • 顺序表L一般定义为SeqList类型的指针变量: SeqList *L =&SL; • 用L来引用结构体SeqList类型变量的各个域 • 线性表的表长为:(*L).last+1 或 L->last+1 • 线性表中第一个数据元素为: (*L).data[0]或 L->data[0] • 最后一个元素表示为: (*L).data[(*L).last] 或 L->data[L->last] • 第i个元素表示为: (*L).data[i-1] 或 L->data[i-1]

  8. 线性表运算在顺序表上的实现 • 1. 插入 插入后使原长度为n的表 (a1,a2,…,ai-1,ai,ai+1,…,an) 成为长度为n+1 的表 (a1,a2,…,ai-1,x,ai,ai+1,…,an) • 算法中的主要步骤: 步骤1:由于是插入运算,首先要检查是否有插入位置,即顺序表是否已满,若顺序表已满(L->last==MAXSIZE-1),则产生溢出错误,返回插入失败; 步骤2:在有插入位置的前提下,还要检查插入位置是否合适,即是否满足1≤i≤n+1,若不满足条件,则插入位置错误,返回插入失败; 步骤3:在步骤1、2都顺利通过的前提下,实现后移操作; 步骤4:插入新结点; 步骤5:将表长加1;返回插入成功。

  9. 图2.3 顺序表中插入元素的过程

  10. 插入算法的C函数如下: int insertElem (SeqList *L,int i,DataType x) { int j; if(L->last==MAXSIZE-1) { printf("overflow"); return(0);} /*检查表空间是否已满*/ if((i<1 )||(i>L->last +2)) { printf("error");return 0; } /*检查插入位置的正确性*/ for(j=L->last;j>=i-1;j--) L->data[j+1]=L->data[j]; /* 结点后移 */ L->data[i-1]=x; /*新元素插入*/ L->last + +; /*表长加1,last仍指向最后元素*/ return 1; /*返回插入成功*/ }

  11. 时间复杂度分析 • 最好情况下时间复杂度为O(1) • 最坏情况下时间复杂度为O(n) • 算法的平均时间复杂度 在长度为n的线性表中插入一个元素时所需移动数据元素次数的期望值(平均移动次数)为: 等概率情况下,平均移动次数为:

  12. 2.删除 删除后使原表长为n的线性表 (a1,a2,…,ai-1,ai,ai+1,…,an) 变成长度为n-1 的线性表 (a1,a2,…,ai-1, ai+1,…,an) 图2. 4 顺序表中删除元素的过程

  13. 删除算法的C函数如下: int deleteElem (SeqList *L,int i) { int j; if(i<1 || i>L->last+1) /*检查空表及删除位置的合法性*/ { printf ("第%d个元素不存在",i); return 0; } for(j=i;j<=L->last;j++) L->data[j-1]=L->data[j]; /*向前移动元素*/ L->last --; /*表长减1*/ return 1; /*删除成功*/ } • 删除时要检查删除位置的有效性,空表和大于表长的位置是不能删除的。 • 时间复杂度分析 • 最好情况下时间复杂度为O(1) • 最坏情况下时间复杂度为O(n) • 在等概率情况下,平均时间复杂度也为O(n)

  14. 3. 按值查找 按值查找算法的C函数如下: int LocateElem (SeqList *L, DataType x) { int i; for(i=0;i<=L->last ;i++) if( L->data[i]== x) return i+1; /*返回的是序号*/ return 0; } • 时间复杂度分析 • 最好情况下时间复杂度为O(1) • 最坏情况下时间复杂度为O(n) • 在等概率情况下,平均比较次数为(n+1)/2,平均时间复杂度为O(n)。

  15. 2.3 链表 • 线性表的顺序存储方法的优缺点 • 线性表的顺序存储方法有明显的优点: • (1)这种方法最简单,最容易被大多数人理解和使用。 • (2)可以随机存取表中任一元素,存储位置可以通过一个简单直观的公式来表示,访问元素速度快。 • (3)不需要增加额外的空间来表示结点间的逻辑关系,存储利用率高。 • 线性表的顺序存储方法的弱点: • (1)除表尾的位置外,在做插入、删除时需要移动大量数据元素,效率较低。 • (2)由于顺序表要求分配一块连续的存储区域,不能利用小块存储区。 • (3)一般采用静态存储分配方法。在程序执行前,要预先分配存储空间,当表长变化较大时,难以确定合适的存储规模。若按可能达到的最大长度进行分配,则可能一部分空间不能得到充分利用;若事先对表长估计不足,则插入操作又可能造成表空间的溢出。

  16. 链表的定义及形式 • 链表(Linked List)是采用链接方式存储的线性表。链接存储需要通过附加指针来表示结点之间的关系。 • 线性表链接存储的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。 • 数据元素ai的存储映象:对每个数据元素ai来说,除了存储其自身的信息之外,还需要和ai一起存放表示元素间逻辑关系的信息(后继或前趋的指针,即附加的指针域)。这两部分信息组成数据元素ai的存储映象,称为结点(node)。 • 根据链表附加指针域个数的不同,可以将链表分成不同的形式。 • 最常用的链表形式是每个结点附加一个指针域,指向后继结点,称为单链表; • 如果每个结点附加两个指针域,分别指向前趋结点和后继结点,则称之为双链表; • 在单链表上进行简单的改进(利用终端结点的空指针域指向表头)可以得到单循环链表; • 在双链表上进行简单的改进(也是利用空指针域)可以得到双循环链表。 • 根据链表存储空间分配方式的不同,可以将链表分为动态链表和静态链表。

  17. 单链表结点结构 • 单链表 • 单链表的定义 • 单链表中第一个数据元素的存储地址保存在头指针里,而其它数据元素的地址都保存在前趋结点的指针域中,所以整个链表的存取必须从头指针开始 • 单链表可由头指针唯一确定,通常用 “头指针”来标识一个单链表,如 单链表L是指某链表的头指针为L。 例:L=(a,b,c,d,e,f,g) 采用单链表存储

  18. 单链表的存储结构用C语言描述如下: typedef struct Node /*结点类型定义*/ { DataType data; /*数据域*/ struct Node *next;/*指针域*/ }LinkList; LinkList *L,*p;  /*定义指针变量*/ • 调用标准函数malloc()和free(p)申请和释放空间 • 申请空间:p =(LinkList *)malloc(sizeof(LinkList)); • 释放空间:free(p) • 单链表结构的一般形式

  19. 假设指针变量p指向单链表中第i个结点,请区分以下几种表示法:假设指针变量p指向单链表中第i个结点,请区分以下几种表示法: • p:是一个LinkList *类型的指针,它保存的是数据域为ai的 结 点的首地址; • *p:是一个LinkList类型的结构体变量,它的值包括两个分量 p->data和p->next; • p->data:是一个DataType类型的变量,表示p所指结点中的 数据域,它的值是元素ai; • p->next:是一个LinkList *类型的指针,表示p所指结点的指针域,它的值是数据域为ai+1的结点的首地址,即q=p->next。p->next是指向线性表中第i+1个结点的指针,即p->next->data的值是 ai+1 。同理r=p->next->next,即r=q->next。 • p=p->next,它的作用是将p所指结点中的指针域的值赋给指针p,形象的可表示为指针p沿着“链”向后移动了一个结点。

  20. 单链表上基本运算的实现 (1)建立单链表---- 头插法和尾插法 • 头插法 • 基本思想:头插法即在链表的头部插入结点建立单链表。建立单链表从空表开始,重复读入数据,每读入一个数据即向系统申请存储空间建立一个新结点,并将读入的数据存放在新结点的数据域中,然后插在链表的头部,直到读入结束标志-1为止。

  21. 头插法建立单链表算法的C函数如下: LinkList *createHeadList( ) /*以头插法建立不带头结点的单链表*/ { LinkList *L=NULL; /*置空表*/ LinkList *s; int x; /*设数据元素的类型为int*/ scanf("%d",&x); while (x!=-1) /*以-1为输入结束标志*/ { s=( LinkList *)malloc(sizeof(LinkList)); s->data=x; s->next=L; L=s; scanf ("%d",&x); } return L; /*返回表头指针*/ }

  22. 尾插法 • 基本思想:尾插法是在单链表的尾部插入结点建立单链表。尾插法建立单链表时每次将新结点插入到链表的尾部,此时可设置一个尾指针r,用来指向链表中的尾结点,能够方便的实现将新结点插入到链表的尾部,而不用每次插入时都从头到尾扫描一遍单链表来找到尾结点。

  23. 尾插法建立单链表算法的C函数如下: LinkList *createTailList( ) /*以尾插法建立不带头结点的单链表*/ { LinkList *s, *r , *L; int x; /*设数据元素的类型为int*/ L=NULL; /*置空表*/ r=NULL; scanf("%d",&x); while (x!=-1) { s=( LinkList *) malloc(sizeof(LinkList)); s->data=x; if (!L) L=s; /*第一个结点的处理*/ else r->next=s; /*其他结点的处理*/ r=s; /*r 指向新的尾结点*/ scanf("%d",&x); } if ( r ) r->next=NULL; /*对于非空表,尾结点的指针域置空*/ return L; }

  24. 头结点的作用 • 头结点的加入使得链表一经建立就非空(至少有头结点),从而解决了“空表”问题。 • 在第一个结点前插入一个结点,或者删除第一个结点,都与头指针无关,只需修改头结点的指针域即可,与在表的其他位置上的操作完全一致,无须特殊处理。

  25. 引入头结点后的头插法和尾插法 • 尾插法建立带头结点的单链表算法的C函数如下: LinkList *createTailList ( ) /*以尾插法建立带头结点的单链表*/ { LinkList *L=( LinkList *)malloc(sizeof (LinkList)); /*建立头结点,申请结点存储空间*/ LinkList *s, *r=L; /*尾指针指向头结点*/ int x; /*设数据元素的类型为int*/ scanf("%d",&x); while (x!= -1) /*以-1作为结束符*/ { s=( LinkList *)malloc(sizeof(LinkList)); s->data=x; r->next=s; /*把新结点插入到尾指针后*/ r=s; /*r 指向新的尾结点*/ scanf("%d",&x); } r->next=NULL; /*尾结点的指针域为空*/ return L; }

  26. 用头插法建立带头结点的单链表,算法的C函数如下:用头插法建立带头结点的单链表,算法的C函数如下: LinkList *CreateHeadList( ) /*以头插法建立带头结点的单链表*/ { LinkList *L=( LinkList *)malloc(sizeof(LinkList)); /*建立头结点,申请结点存储空间*/ LinkList *s; int x; /*设数据元素的类型为int*/ L->next=NULL; /*置空表*/ scanf("%d",&x); while (x!=-1) /*-1作为结束符*/ { s=( LinkList *)malloc(sizeof(LinkList)); s->data=x; s->next=L->next; L->next=s; /*插入到表头*/ scanf ("%d",&x); } return L; }

  27. (2)查找运算---按序号查找 和按值查找(即定位) • 按序号查找 • 基本思想:在单链表中查找第i个数据元素,需从链表的 头指 针开始,顺着结点的next域依次“走过”前i-1个结点, 然后从第i-1个结点的指针域中取出第i个结点的地址 返回即可。 • 按序号查找算法的C函数如下: LinkList *getElem(LinkList *L, int i) /*在带头结点的单链表L中查找第i个结点*/ { LinkList *p=L; int j=0; /*从头结点开始扫描*/ while (p && j<i ) { p=p->next; j++; } if ((!p)||(j>i)) /*p空时,到达表尾,j>i时,输入了小于0的位置 */ { printf(“\n位置错\n”); return NULL;} else return p; } • 在等概率假设下,算法的平均时间复杂度为O(n),平均查找长度为:

  28. 例2.1 求带头结点的单链表的表长。 int lengthList (LinkList *L) /*求带头结点的单链表的表长*/ { LinkList * p=L->next; /* p指向表头结点*/ int j=0; while (p) { p=p->next; j++ ;} /* p所指的是第 j 个结点*/ return j; }

  29. 按值查找(即定位) • 基本思想:在单链表中查找给定的元素值,需顺着链表的头指针扫描单链表,在扫描过程中,判断当前结点数据域的值是否等于x,若是,返回该结点的指针,查找成功;否则继续扫描下一个结点。如果扫描过程一直进行到表尾,此时p=NULL,表示在单链表中不存在值为x的结点,查找失败,返回空(NULL)。 • 按值查找算法的C函数如下: LinkList *locateElem(LinkList *L, DataType x) /*在带头结点单链表L中查找值为x的结点*/ { LinkList *p=L->next; while ( p && p->data != x) p=p->next; return p; } • 时间复杂度也是O(n)

  30. (3)插入运算 • 根据给定的初始条件不同,可以分指定结点和指定位置两种情况下进行插入操做。 • 从给定的插入条件,还可以将插入运算分成前插和后插两种,一般后插比前插更容易。

  31. 在指定结点情况下进行插入运算 后插: ① s=( LinkList *)malloc(sizeof(LinkList)); ② s->data=x; ③ s->next=p->next; ④ p->next=s; • 前插: • ① s=( LinkList *)malloc(sizeof(LinkList)); ② s->data=x; • ③ while (q->next!=p ) q = q ->next; • ④ s->next=p; ⑤ q->next=s;

  32. 在指定结点时后插运算的C函数如下: void insertElemAfNode (LinkList * p, DataType x) /*在带头结点的单链表L中p所指的结点后插入值为x的元素*/ { LinkList *s; s=( LinkList *)malloc(sizeof(LinkList)); s->data=x; s->next=p->next; p->next=s; } 后插算法的时间复杂度为O(1)。

  33. 在指定结点时前插运算的C函数如下: void insertElemBeNode ( LinkList *L, LinkList * p, DataType x) /*在带头结点的单链表L中p所指的结点前插入值为x的元素*/ { LinkList * q,*s; s=( LinkList *)malloc(sizeof(LinkList)); s->data=x; q=L; /*从头结点开始查找结点p的前趋结点q */ while (q->next!=p ) q = q ->next; s->next=p; q->next=s; } 平均时间复杂度为O(n)

  34. 在指定位置情况下进行插入运算 • 若已知结点序号i,在第i个结点后或前插入一个数据元素x, 也就是在元素ai前或后插入元素x,即为给定位置的插入运算。 • 在第i个结点前插入一个数据元素x的实现过程

  35. 在第i个结点前进行插入运算的C函数如下: int insertElem ( LinkList *L, int i, DataType x) /*在带头结点的单链表L中第i个元素前插入值为x的元素*/ { LinkList * p,*s; int j; p=L; j=0; while (p && j<i-1 ) { p=p->next; j=j+1; } /*查找第i-1个结点的地址*/ if ((!p)||(j>i-1)) /* i小于1或大于表长+1*/ { printf("error");return 0; } s=(LinkList *)malloc(sizeof(LinkList)); /*申请结点存储空间*/ s->data=x; /*给结点数据域赋值*/ s->next=p->next; p->next=s; /*新结点插入在第i-1个结点后面*/ return 1; } 在等概率情况下,时间复杂度为O(n)。

  36. 例2.2 在带头结点的单链表中值为y的结点前插入值为x的结点。 int inserty_x ( LinkList *L, DataType y, DataType x) /*在带头结点的单链表L中值为y的结点前插入值为x的结点*/ { LinkList * p,*q,*s; /* 结点*q为*p的前趋结点*/ p=L->next; q=L; while (p && p->data!=y ) /*查找值为y的结点及前趋点的地址*/ { q=p; p=p->next; } if (!p) /*链表中无值为y的结点*/ { printf("error"); return 0; } s=( LinkList *)malloc(sizeof(LinkList)); /*申请结点存储空间*/ s->data=x; /*给结点数据域赋值*/ s->next=p; q->next=s; /*新结点插入在*q结点的后面*/ return 1; } 在等概率情况下,算法的平均时间复杂度为O(n)。

  37. (4)删除运算--在给定结点和给定位置删除 • 在给定结点情况下进行删除 • 在给定结点时删除运算的C函数如下: void deleteElemNode (LinkList * L, LinkList *p) /*在带头结点的单链表L上删除结点*p */ { LinkList *q; q=L; while (q->next!=p ) q = q ->next; /*从头结点开始查找结点*p的前趋结点*q */ q->next =p->next; /*q指向结点p的后继结点*/ free(p); /*释放q占用空间 */ } • 平均时间复杂度为O(n)

  38. 在给定位置情况下进行删除 • 在给定位置时删除第i个结点运算的C函数如下: int deleteElem (LinkList *L, int i, DataType *e) /*在带头结点的单链表L上删除第i个结点*/ { LinkList *p,*q; int j; p=L; j=0; while ((p->next) && (j<i-1) ) /*查找第i-1个结点*/ { p=p->next; j++; } if ((!p->next)||( j>i-1)) /* p->next为空,删除结点不存在,j>i-1时,位置错*/ { printf("error");return 0; } q=p->next; /*q指向第i个结点*/ p->next=q->next; /*改变指针*/ *e=q->data; /*指针e带回所删结点数据域的值*/ free(q); /*释放q占用空间 */ return 1; } • 平均时间复杂度为O(n)

  39. 例2.3 写一算法,删除带头结点的单链表L中的重复结点,即实现如图所示的操作,图(a)为初始的单链表,图(b)为删除重复结点后的单链表。

  40. 例2.4 写一算法逆置带头结点的单链表L,要求逆置后的单链表利用L中的原结点,不可以重新申请结点空间。 void reverse (Linklist *L) { Linklist *p,*q; /*p为剩余结点形成单链表的头指针,q保存欲插入L头的结点的地址*/ p=L->next; /*p指向第一个数据结点*/ L->next=NULL; /*将原链表置为空表L*/ while (p) { q=p; p=p->next; q->next=L->next; /*将当前结点插到头结点的后面*/ L->next=q; } }

  41. 例2.5假设有两个带头结点的单链表A、B,按元素值非递减有序,编写算法将A、B归并成一个按元素值非递增有序的链表C,要求链表C利用A、B中的原结点空间,不可以重新申请结点。例2.5假设有两个带头结点的单链表A、B,按元素值非递减有序,编写算法将A、B归并成一个按元素值非递增有序的链表C,要求链表C利用A、B中的原结点空间,不可以重新申请结点。 LinkList *union(LinkList *A,LinkList *B) { LinkList *C, *p,*q,*s; p=A->next;q=B->next; /*p、q分别指向单链表A、B的第一个结点*/ C=A; /*C表的头结点利用A表头结点的空间*/ C->next=NULL; while (p&&q) { if (p->data<=q->data) /*从链表A、B上取出当前data值较小者暂存到s中*/ { s=p; p=p->next; } else { s=q; q=q->next; } s->next=C->next; /*将s插入到C表的头部*/ C->next=s; } if (!q) q=p; /* 将剩余结点形成的链的链头保存到q中*/ while (q) /* 将剩余结点依次取出暂存到s中,将s插入到C表的头部*/ { s=q; q=q->next; s->next=C->next; C->next=s; } free(B); return *C; } • 算法的时间复杂度为O(m+n)。

  42. 循环链表 • 循环链表(Circular Linked List)是利用链表中某些结点的指针域的值为空(NULL)的特点,将空指针域改存一些更有意义的信息而形成的。 • 在单循环链表上的操作基本与单链表相同,稍微不同的是p,将原来运算中的循环结束条件由判断指针是否为空,变为判断是否等于头指针。 • 空表的条件是L->next==L而非L->next==NULL。 • 指针p到达表尾的条件是p->next==L而非p->next==NULL • 将指向头结点的指针改为指向尾结点的单循环链表

  43. 将指向头结点的指针改为指向尾结点的单循环链表将指向头结点的指针改为指向尾结点的单循环链表 *尾结点的指针为r,头结点的指针为 r->next,链表为空的条件是r->next==r *查找开始结点和尾结点的时间都是O(1) • 将两个单链表L1、L2合并成一个单链表,即将L2的第一个结点链接到L1的尾结点上 。 操作步骤如下: p= r1 –>next; /*保存L1 的头结点地址*/ r1->next=r2->next->next; /*把L2链到L1的尾部*/ free(r2->next); /*释放L2的头结点*/ r2->next=p; /*形成循环链表*/

  44. 双链表 • 每个结点附加两个指针域,一个指向后继结点、一个指向前趋结点。由这种结点组成的链表中有两条方向不同的链,因此称为双(向)链表(Double Linked List) • 双链表的存储结构用C语言描述如下: typedef struct DNode { DataType data; struct DNode *prior,*next; } DLinkList; DLinkList *L; • 双链表的对称特性 :p->prior->next = p = p->next->prior 带头结点的双循环链表

  45. 双链表上的运算 • 在进行插入、删除操作时,除了要修改后继结点的next域,还需修改前趋结点的prior域 • (1)插入 *将*s插入到*p的前面,插入过程: ①s=(DLinkList *)malloc(sizeof(DLinkList)); ②s->data=x; ③s->prior=p->prior; ④s->next=p; ⑤p->prior->next=s; ⑥p->prior=s; *需要注意的是在修改指针的过程中要保证不要断链了 *算法的时间复杂度为O(n)

  46. 双链表上的运算 (2)删除 *删除*p,操作如下: ①p->prior->next=p->next; ②p->next->prior=p->prior; ③free(p); *算法的时间复杂度为O(n)

  47. 2.4 顺序表和链表的比较 • 1 .逻辑关系的表示 • 2.存储密度 • 3.存储分配方式 • 4.存取方法 • 5.插入、删除操作 • 6.实现语言

More Related