1 / 51

第二章 线性表

第二章 线性表. 学习要点:. 线性(表)结构的基本概念和特征。 线性表的顺序存储结构和在此结构下各种基本操作实现。 线性表的链式存储结构和在此结构下各种基本操作实现。 分析比较线性表的两种存储结构的特点,优点和不足。 单链表的头结点与开始结点,循环链表的循环条件,具有对称结构的双向链表。. 2.1 线性表概念. 一对一的线性结构 2.1.1 线性表逻辑结构 学生信息记录表,除了表头和表尾,每个记录有且仅有一个直接前趋和一个直接后继。. 形式 ( a 0 ,a 1 ,a 2 , … , a n-1 ) 符合线性表的四个特征

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. 学习要点: • 线性(表)结构的基本概念和特征。 • 线性表的顺序存储结构和在此结构下各种基本操作实现。 • 线性表的链式存储结构和在此结构下各种基本操作实现。 • 分析比较线性表的两种存储结构的特点,优点和不足。 • 单链表的头结点与开始结点,循环链表的循环条件,具有对称结构的双向链表。

  3. 2.1 线性表概念 一对一的线性结构 2.1.1 线性表逻辑结构 • 学生信息记录表,除了表头和表尾,每个记录有且仅有一个直接前趋和一个直接后继。 形式 (a0 ,a1 ,a2 ,… , an-1) 符合线性表的四个特征 相关概念:直接前趋,直接后继,长度,空表

  4. 2.1.2 线性表的ADT描述 ADT List is {基本操作: (1)List CreatList() 创建并返回一个空线性表; (2)ClearList(List L) 清空线性表L中的元素; (3)int LengthList(List L) 返回线性表L中的元素个数; (4)int InsertPre(List L,int i, DataType x) 在线性表L中的第i个位置之前插入值为x的元素,并返回插入成功与否的标志; (5)int InsertPost(List L,int i, DataType x) 在线性表L中的第i个位置之后插入值为x的元素,并返回插入成功与否的标志; (6)int DeletePos(List L,int i) 在线性表L中的删除第i个位置上的元素,并返回删除成功与否的标志; (7)int search(List L, DataType x) 在线性表L中的查找值为x的元素,并返回查找成功与否的标志; (8)Display(List L) 输出线性表L中的元素。 } ADT List

  5. 接下来学习线索: 存储方式:顺序,链式 行为特征

  6. 2.2 线性表的顺序存储结构 2.2.1顺序存储结构 • 用一组连续的地址空间依次存放线性表里的元素 a0 a1 a2 … an-1 Loc(a0) 起始地址(基地址) Loc(a1) = Loc(a0) + length Loc(a2) = Loc(a1) + length = Loc(a0) + 2* length Loc(a3) = Loc(a2) + length = Loc(a0) + 3* length …… 所以,我们可以得出线性表第i个元素地址的求址公式为: Loc(ai) = Loc(a0) + i* length

  7. 2.2.1 顺序存储结构2 定义顺序表数据类型 00 struct SeqList 01 { 02 int MAXNUM; /* 顺序表中可以存放元素的个数 */ 03 int n; /* 实际存放线性表中元素的个数,n≤MAXNUM-1 */ 04 DataType *element; 05 }; 06 typedef struct SeqList *PSeqList; • 逻辑相邻=物理相邻 只要知道基地址,就可以通过计算得到任一元素地址,故线性表可实现随机存取,即访问任一元素时间相等。

  8. 2.2.2 基于顺序存储基本操作 1、插入:在第i个位置前插入元素

  9. 算法2.1 在第i个位置前插入元素 00 int InsertPre(List L,int i, DataType x) 01 { 02 int j; 03 if ((i<0)||(i>L.n)) /* 判断输入的位置i是否合法 */ 04 { 05 printf(“插入位置i不合法!”); 06 return(0); /* 返回插入失败的标志 */ 07 } 08 if (L.n==MAXSIZE) 09 { 10 printf(“表已满,无法插入!”) 11 return(0); 12 } 13 for (j=n-1;j>=i;j--); /* i位置以及之后所有元素后移一位 */ 14 L.elem[j+1]=L.elem[j]; 15 L.elem[i]=x; /* 将x放入第i个位置 */ 16 L.n=L.n+1; /* 顺序表元素个数加1 */ 17 return(1); /* 返回插入成功的标志 */ 18 }

  10. 2.2.2 基于顺序存储基本操作2 2、删除第i个位置元素

  11. 算法2.2 删除第i个元素 00 int DeletePos(List L,int i) 01 { 02 int j; 03 if ((i<0)||(i>L.n-1)) /* 判断要删除元素的位置i是否合法 */ 04 { 05 printf(“删除位置i不合法!”); 06 return(0); /* 返回删除失败的标志 */ 07 } 08 for (j=i+1;j>=n-1;j++); /* i+1位置以及之后所有元素前移一位 */ 09 L.elem[j-1]=L.elem[j]; 10 L.n=L.n-1; /* 顺序表元素个数减1 */ 11 return(1); /* 返回删除成功的标志 */ 12 }

  12. 2.2.2 基于顺序存储基本操作3 3、查找给定值 00 int search(List L, DataType x) 01 { 02 int i; 03 i=0; 04 while ((i<L.n)&&(L.elem[i]!=x)) /* 当位置i的元素不等于x时,继续向下比对 */ 05 i=i+1; 06 if (i==L.n) /* 判断比对位置是否超出地址范围 */ 07 return(-1); /* 返回查找不成功的标志 */ 08 else 09 return(i); /* 返回查找到的给定值所在下标 */ 10 }

  13. 例2-1:有两个顺序表LA和LB,其元素均为非递减有序序列,将它们合并成一个顺序表LC,要求LC也是非递减有序序列,且不增加新结点。如LA=(2,4,4,6),LB=(1,3,3,5),则LC=(1,2,3,3,4,4,5)。例2-1:有两个顺序表LA和LB,其元素均为非递减有序序列,将它们合并成一个顺序表LC,要求LC也是非递减有序序列,且不增加新结点。如LA=(2,4,4,6),LB=(1,3,3,5),则LC=(1,2,3,3,4,4,5)。 程序见2.2.2节算法2-6。

  14. 2.2.2 基于顺序存储基本操作4 顺序表特点总结: • 本质:元素地址体现逻辑关系 • 表现:连续地址空间 • 实现:数组类型 • 特点:随机存取,静态管理,在插入、删除时可 能要移动大量元素。

  15. 2.2.2 基于顺序存储基本操作5 顺序表局限性: • 移动大量元素 • 预留最大空间 • 有冗余 链式存储可根据需要申请空间,解决上述问题。

  16. 2.3 线性表的链式存储 一个单链表的例子: (只有一个指针域) 结点定义:struct node {int data; struct node *next; }; typedef struct node NODE; 2.3.1单链表定义 • 用一组任意的地址空间存储线性表里的元素

  17. Data next 1 21 43 7 8 13 13 9 1 19 17 null 25 19 37 31 5 7 37 15 19 43 4 25 练习:画出图示存储空间的单链表结构 head=31

  18. 2.3.2单链表基本操作 1.申请结点并赋值 00 NODE *ApplyNODE(DataType x) 01 { 02 NODE *p; 03 p=(NODE *) malloc (sizeof(NODE)); /* malloc()函数为动态申请分配空间函数,sizeof()函数可取得NODE结点的大小 */ 04 p->data=x; 05 p->next=NULL; 06 return(p); 07 }

  19. 2.3.2单链表基本操作2 2. 初始化链表 00 NODE *InitList() 01 { 02 NODE *head; 03 head=(NODE *) malloc (sizeof(NODE)); 04 head->next=NULL; 05 return(head); 06 }

  20. 2.3.2单链表基本操作3 3.插入结点到表头: • 已知一个结点p以及链表head,执行插入p结点到表头的操作 。 p->next=head->next; head->next=p; 插入的结点p作为head之后的第一个元素,成为了新的表头结点。

  21. 2.3.2单链表基本操作4 4.头插法建立链表 00 NODE *CreateFromHead() 01 { 02 NODE *head,*p,*q; 03 int i,n,x; 04 printf("\nInput the length of the line:"); 05 scanf("%d",&n); /* 键盘读取链表的长度n */ 06 head=Initlist(); /* 初始化链表head */ 07 printf("\nInput %d datas:",n); 08 for (i=0;i<n;i++) 09 { 10 scanf("%d",&x); 11 p=applyNODE(x); /* 为x申请结点p */ 12 p->next=head->next; 13 head->next=p; 14 } 15 }

  22. 2.3.2单链表基本操作5 5.尾插法建立链表 00 NODE *CreateFromTail() 01 { 02 NODE *head,*p,*q; 03 int i,n,x; 04 printf("\nInput the length of the line:"); 05 scanf("%d",&n); /* 链表的长度n */ 06 head=q=Initlist(); /* 初始化链表head,q代表表尾结点 */ 07 printf("\nInput %d datas:",n); 08 for (i=0;i<n;i++) 09 { 10 scanf("%d",&x); 11 p=applyNODE(x); /* 为x申请结点p */ 12 q->next=p; /* p连接到表尾q之后 */ 13 q=p; /* p结点作为新的表尾,更新表尾q */ 14 } 15 }

  23. 2.3.2单链表基本操作6 6.插入结点到指定结点之后 • 已知一个结点p以及链表中的某结点q,执行插入p结点到q结点之后的操作 。 1 p->next=q->next; 2 q->next=p; 在这两个语句中,必须先执行第1句,否则先将p赋予了q的指针域,会造成q与q原来的后继断开,找不到其后继结点地址的后果。

  24. 2.3.2单链表基本操作7 7.插入结点到表尾 • 已知一个结点p以及链表head,执行插入p结点到表尾的操作。 00 NODE *Searchrear(NODE *head) 01 { 02 NODE *q; 03 q=head; 04 while (q->next!=NULL) 05 q=q->next; 06 }

  25. 2.3.2单链表基本操作8 8.删除某结点的后继 • 已知链表中的某结点p,执行删除p的后继的操作 q=p->next; /*用q记录要删除的结点*/ p->next=q->next; /*p的指针域直接跳过q结点*/ free(q); /*释放已被删除的结点*/

  26. 2.3.2单链表基本操作9 9.删除某结点 • 已知一个结点p以及链表head,执行删除p结点的操作 00 NODE *delete(NODE *head, NODE *p) 01 { 02 NODE *q; 03 q=head; 04 while (q->next!=p) /* 找p的后继q */ 05 q=q->next; 06 q->next=p->next; /* 删除p结点 */ 07 free(p); 08 return(head); 09 }

  27. 2.3.2单链表基本操作10 10.输出单链表 00 void Display(NODE *head) 01 { 02 NODE *p; 03 printf("\nThe line are:"); 04 p=head->next; /* p指向链表的第一个元素 */ 05 while(p!=NULL) /* 判断p是否已到链表的末尾 */ 06 { 07 printf("%d ",p->data); /* 输出p的值 */ 08 p=p->next; /* p指向下一个结点 */ 09 } 10 }

  28. 2.3.2单链表基本操作11 11.查找给定值结点 00 NODE *Search(NODE *head,int x) 01 { 02 NODE *p; 03 p=head->next; 04 while((p!=NULL)&&(p->data!=x)) 05 p=p->next; 06 return(p); 07 }

  29. 练习: • 查找链表中值为x的元素,返回其地址或空。 上机: • 键盘输入n,然后建立一个长度为n的单链表,最后输出此单链表; • 输入数字k,在单链表中查找k并输出查找结果。

  30. 2.3.3 循环链表 • 首尾相连

  31. 2.3.3 循环链表2 • 例2-2:现有循环单链表head,输出链表中的所有元素。 • 算法2.13 输出循环单链表 1 void Display(NODE *head) 2 {NODE *p; 3 printf("\nThe line are:"); 4 p=head->next; 5 while(p!=head) /* 判断p是否已到链表头结点 */ 6 {printf("%d ",p->data); 7 p=p->next; 8 } 9 }

  32. 2.3.3 循环链表3 • 例:合并 图2-15(a) headA和headB合并前 图2-15(b) headA和headB合并后

  33. 2.3.3 循环链表4 • 算法2.16带头结点的循环单链表的合并 00 NODE *merge1(NODE *headA, NODE *headB) 01 { 02 NODE *p,*q; 03 p=headA->next; 04 while(p->next!=headA) p=p->next; /* 找到表headA的表尾p */ 05 q=headB->next; 06 while(q->next!=headB) q=q->next; /* 找到表headB的表尾q */ 07 q->next=headA; /* 修改q的指针域指向headA的头结点 */ 08 p->next=headB->next; /* 修改p的指针域指向headB的表头结点 */ 09 q->next=headA; /* q的指针域指向headA头结点 */ 10 free(headB); /* 释放headB的头结点 */ 11 return(headA); 12 }

  34. 2.3.3 循环链表5 • 算法2.17带尾指针的循环单链表的合并 00 NODE *merge2(NODE *rearA, NODE *rearB) 01 { 02 NODE *p; 03 p=rearA->next; /* 找到表rearA的头结点p */ 04 rearB->next=p; /* 修改rearB的指针域指向rearA的头结点 */ 05 rearA->next=rearB->next->next; /* 修改rearA的指针域指向rearB的表头结点 */ 06 free(rearB->next); /* 释放rearB的头结点 */ 07 return(rearB); /* 返回rearB的尾指针 */ 08 }

  35. prior next data 2.3.4 双向链表 • 引入:单链表中求前趋困难

  36. 2.3.4 双向链表2 1.插入结点到指定结点之前 • 已知双向链表中的某结点p以及一个孤立结点s,执行插入s结点到p结点之前的操作。 01 s->next=p; /* s的后继指向p */ 02 s->prior=p->prior; /* s的前驱指向p的前驱 */ 03 p->prior->next=s; /* p的前驱的后继指向s */ 04 p->prior=s; /* p的前驱指向s */

  37. 2.3.4 双向链表3 2.删除某结点 • 已知双向链表中的某结点p,执行删除p的操作。 01 p->prior->next=p->next; /* p的前驱的后继指向p的后继 */ 02 p->next->prior=p->prior; /* p的后继的前驱指向p的前驱 */ 03 free(p); /* 释放已被删除的p结点 */

  38. 2.3.5 静态链表 一个静态链表的例子: 设StaList为静态链表数组,shead为头结点指针。则StaList[shead].cursor指示了链表的第一个结点在数组中的位置。

  39. 2.3.5 静态链表2 • 静态链表的数据类型可用C语言定义如下: 00 struct node 01 { DataType data; /* Data表示数据可能的类型 */ 02 int cursor; 03 }; 04 typedef struct node Stanode; /* 定义静态链表结点类型 */ 05 Stanode StaList[Maxsize]; /* Maxsize表示链表可能达到的最大长度 */

  40. 2.3.5 静态链表3 1.静态链表初始化 00 void initial(Stanode StaList[Maxsize],int spare) 01 { 02 int k; 03 StaList [0].cursor=-1; /* 设置已用静态链表的游标域为-1,即已用静态链表为空 */ 04 spare=1; /* 设置备用链表头指针初值 */ 05 for(k=av;k<Maxsize-1;k++) 06 StaList [k].cursor=k+1; /* 将空闲结点链接起来 */ 07 space[Maxsize-1]. cursor=-1; /* 标记链尾 */ 08 }

  41. 2.3.5 静态链表4 2.插入 在线性表(A,B,C,D,E)的第3个结点C之后插入一值为X的元素

  42. 2.3.5 静态链表5 算法2-19 在静态链表的第i个元素之后插入元素X 00 void InsAfter(Stanode StaList[Maxsize],int spare,int i,DataType X) 01 { 02 int j,k,m; 03 j=spare ; /* j为从备用链表中取到的可用结点的下标,j即为新结点下标 */ 04 spare =StaList[spare].cursor; /* 修改备用链表的头指针 */ 05 StaList[j].data=X; /* 为新结点赋值 */ 06 k= StaList[0].cursor ; /* k为已用静态链表的第一个元素的下标 */ 07 for(m=0;m<i-1;m++) /* 寻找第i个元素的位置k */ 08 k= StaList[k].cursor ; 09 StaList[j].cursor=StaList[k].cursor; /* 修改新结点游标域,指向第i个结点的后继 */ 10 StaList[k].cursor=j; /* 修改第i个结点游标域,指向新结点 */ 11 }

  43. 2.3.5 静态链表6 3.删除 在线性表(A,B,C,D,E)中删除第3个结点C之后

  44. 2.3.5 静态链表7 算法2-20 删除静态链表的第i个元素 00 void Delete(Stanode StaList[Maxsize],int spare,int i) 01 { 02 int j,k,m; 03 k= StaList[0].cursor ; /* k为已用静态链表的第一个元素的下标 */ 04 for(m=1;m<i-1;m++) /* 寻找第i-1个元素的位置k */ 05 k= StaList[k].cursor ; 06 j=StaList[k].cursor ; /* 取得第i个元素的位置j */ 07 StaList[k].cursor=StaList[j].cursor; /* 修改第i-1个元素的游标域,指向第i个结点的后继 */ 08 StaList[j].cursor=spare; /* 修改被删除结点游标域,指向备用链表头指针 */ 09 spare=j; /* 修改备用链表头指针为被删除结点下标j */ 10 }

  45. 2.3.5 静态链表8 4.输出静态链表 00 void display(Stanode StaList[Maxsize]) 01 { 02 int j,k,m; 03 k= StaList[0].cursor ; /* k为已用静态链表的第一个元素的下标 */ 04 while (k!=-1) /* 当前游标还未到末尾时,继续输出 */ 04 { 05 printf("cursor=%d,data=%d\n",k,StaList[k].data); 06 k= StaList[k].cursor ; /* 游标k指向当前结点k的后继 */ 07 } 08 }

  46. 2.3.6 单链表应用 1.单链表的倒置 例2-5:已知单链表head如图2-34(a)所示,编写算法,将链表倒置,倒置后的链表如图2-34(b)所示。 图2-34(a) 倒置前单链表 图2-34(b) 倒置后单链表

  47. 2.3.6 单链表应用2 1.单链表的倒置 00 NODE *reverse(NODE *head) 01 { 02 NODE *p,*q; 03 p=head->next; 04 head->next=NULL; 05 while(p!=NULL) /* 将链表中的结点依次取出,直到p指向表尾 */ 06 { 07 q=p; /* q指向当前要倒置的结点 */ 08 p=p->next; /* p指向下一个要倒置的结点 */ 09 q->next=head->next; 10 head->next=q; /* 把当前结点q用头插法插入到链表表头 */ 11 } 12 return head; 13 }

  48. 2.3.6 单链表应用3 2.两个有序链表的合并 例2-6:已知两个单链表headA和headB,元素均递增有序,编写算法,将headA和headB合并成一个按元素递增的单链表headC,要求用headA和headB中的原结点组成,不能重新申请结点。 算法分析:利用headA和headB原本已经递增有序的特点,设定两个指针p和q分别指向headA和headB的表头,进行比较(程序第8行),将当前值较小者插入到C表表尾,并后移一个结点。如此循环反复,直到p或q为空。最后判断headA或headB中哪个链表有剩余的结点,插入到headC中,由程序第19-22行完成。

  49. 2.3.6 单链表应用4 3.一元多项式的计算 例2-7:编写程序,通过键盘输入两个一元多项式headA和headB,能够按照指数升序排列建立并输出多项式,然后对它们进行相加运算,结果存储到headA中并将结果输出。如输入的一元多项式分别是x1=10-8x+6x2+3x5和x2=17+8x+3x2+5x5+4x6,则它们相加的结果为x1=x1+x2=27+9x2+8x5+4x6。

  50. 2.4线性表存储结构比较 1.空间性能比较 2.时间性能比较 结论: (1)若要求经常按位存取,很少插入删除,或者线性表元素位置基本不变时,可采用顺序存储结构;而常做插入删除操作的,可采用链式存储结构。 (2)若线性表中元素个数可预测,则采用顺序存储结构有更高的存储密度;如个数常变或变化较大,为避免冗余或溢出,可采用链式存储结构更加灵活。

More Related