Presentation is loading. Please wait.

Presentation is loading. Please wait.

第二章 线性表.

Similar presentations


Presentation on theme: "第二章 线性表."— 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 所以,我们可以得出线性表第i个元素地址的求址公式为:
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 {
01 { int j; if ((i<0)||(i>L.n)) /* 判断输入的位置i是否合法 */ { printf(“插入位置i不合法!”); return(0); /* 返回插入失败的标志 */ } if (L.n==MAXSIZE) { printf(“表已满,无法插入!”) return(0); } for (j=n-1;j>=i;j--); /* i位置以及之后所有元素后移一位 */ L.elem[j+1]=L.elem[j]; L.elem[i]=x; /* 将x放入第i个位置 */ L.n=L.n+1; /* 顺序表元素个数加1 */ 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;
01 { 02 int j; 03 if ((i<0)||(i>L.n-1)) /* 判断要删除元素的位置i是否合法 */ { printf(“删除位置i不合法!”); return(0); /* 返回删除失败的标志 */ } 08 for (j=i+1;j>=n-1;j++); /* i+1位置以及之后所有元素前移一位 */ 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 {
01 { int i; i=0; while ((i<L.n)&&(L.elem[i]!=x)) /* 当位置i的元素不等于x时,继续向下比对 */ i=i+1; if (i==L.n) /* 判断比对位置是否超出地址范围 */ return(-1); /* 返回查找不成功的标志 */ else 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.2.2节算法2-6。

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

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

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

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

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

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

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

21 2.3.2单链表基本操作4 4.头插法建立链表 00 NODE *CreateFromHead() 01 {
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++) { scanf("%d",&x); p=applyNODE(x); /* 为x申请结点p */ p->next=head->next; head->next=p; } 15 }

22 2.3.2单链表基本操作5 5.尾插法建立链表 00 NODE *CreateFromTail() 01 {
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++) { scanf("%d",&x); p=applyNODE(x); /* 为x申请结点p */ q->next=p; /* p连接到表尾q之后 */ q=p; /* p结点作为新的表尾,更新表尾q */ } 15 }

23 2.3.2单链表基本操作6 1 p->next=q->next; 2 q->next=p;
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) 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 */ 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;
01 { 02 NODE *p; 03 printf("\nThe line are:"); 04 p=head->next; /* p指向链表的第一个元素 */ 05 while(p!=NULL) /* 判断p是否已到链表的末尾 */ { printf("%d ",p->data); /* 输出p的值 */ p=p->next; /* p指向下一个结点 */ } 10 }

28 2.3.2单链表基本操作11 11.查找给定值结点 00 NODE *Search(NODE *head,int x) 01 {
01 { 02 NODE *p; 03 p=head->next; 04 while((p!=NULL)&&(p->data!=x)) 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是否已到链表头结点 */ {printf("%d ",p->data); p=p->next; } 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 */ q=headB->next; 06 while(q->next!=headB) q=q->next; /* 找到表headB的表尾q */ 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 2.3.4 双向链表 引入:单链表中求前趋困难 data next prior

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表示数据可能的类型 */ 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 { int k; StaList [0].cursor=-1; /* 设置已用静态链表的游标域为-1,即已用静态链表为空 */ spare=1; /* 设置备用链表头指针初值 */ for(k=av;k<Maxsize-1;k++) StaList [k].cursor=k+1; /* 将空闲结点链接起来 */ space[Maxsize-1]. cursor=-1; /* 标记链尾 */ }

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 { int j,k,m; j=spare ; /* j为从备用链表中取到的可用结点的下标,j即为新结点下标 */ spare =StaList[spare].cursor; /* 修改备用链表的头指针 */ StaList[j].data=X; /* 为新结点赋值 */ k= StaList[0].cursor ; /* k为已用静态链表的第一个元素的下标 */ for(m=0;m<i-1;m++) /* 寻找第i个元素的位置k */ k= StaList[k].cursor ; StaList[j].cursor=StaList[k].cursor; /* 修改新结点游标域,指向第i个结点的后继 */ 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 { int j,k,m; k= StaList[0].cursor ; /* k为已用静态链表的第一个元素的下标 */ for(m=1;m<i-1;m++) /* 寻找第i-1个元素的位置k */ k= StaList[k].cursor ; j=StaList[k].cursor ; /* 取得第i个元素的位置j */ StaList[k].cursor=StaList[j].cursor; /* 修改第i-1个元素的游标域,指向第i个结点的后继 */ StaList[j].cursor=spare; /* 修改被删除结点游标域,指向备用链表头指针 */ spare=j; /* 修改备用链表头指针为被删除结点下标j */ 10 }

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

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;
01 { 02 NODE *p,*q; 03 p=head->next; 04 head->next=NULL; while(p!=NULL) /* 将链表中的结点依次取出,直到p指向表尾 */ { q=p; /* q指向当前要倒置的结点 */ p=p->next; /* p指向下一个要倒置的结点 */ q->next=head->next; head->next=q; /* 把当前结点q用头插法插入到链表表头 */ } 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)若线性表中元素个数可预测,则采用顺序存储结构有更高的存储密度;如个数常变或变化较大,为避免冗余或溢出,可采用链式存储结构更加灵活。

51 本章小结 本章基本内容


Download ppt "第二章 线性表."

Similar presentations


Ads by Google