数据结构专接本复习纲要3.docx
- 文档编号:25424511
- 上传时间:2023-06-08
- 格式:DOCX
- 页数:19
- 大小:21.35KB
数据结构专接本复习纲要3.docx
《数据结构专接本复习纲要3.docx》由会员分享,可在线阅读,更多相关《数据结构专接本复习纲要3.docx(19页珍藏版)》请在冰豆网上搜索。
数据结构专接本复习纲要3
“数据结构”专接本复习纲要2006.1
第三章链表
一、链表概念
1、什么是链表
链表是一种存储结构,它把每个元素存储在链表的一个结点中,元素之间的逻辑关系通过设在结点中的指针域来表示。
例如,对于线性表,结点的指针域记录该元素后继元素的地址。
对于二叉树,结点的左指针域记录该结点左孩子的地址。
2、静态链表和动态链表
所谓静态链表是指链表结点的存储空间是通过预先定义数组的方式来获取的,而预先定义的数组的大小是固定的,在程序运行中不能改变,故称为静态链表。
例,有线性表P=(5,8,1,9,3)则可以按静态链表存储如下
89531
24-110
01234……
这里,指针2等指示的是该元素前驱的位置(地址)。
动态链表中结点的存储空间是在程序运行过程中通过动态申请空间的函数得到的。
链表中各结点的存储空间在物理上不一定是连续的。
例如,对于上述的线性表,可以按链表存储如下
58193Λ
在C语言中,用于动态申请空间的函数最常用的有malloc(),释放空间的函数有free()。
二、链表的建立
1、链表的建立
链表的建立本质上就是在链表为空的情况下,插入一个个结点。
下面以不带头结点的单链表为例,说明链表建立的过程。
typedefstructnode/*定义链表中结点的构造*/
{intdata;/*结点的数据域*/
structnode*next;/*结点的指针域*/
}NODE;/*结点类型的别名*/
head=NULL;/*设head是链表的头指针,准备一个空链表*/
rear=NULL;/*准备链表的尾指针rear,其初值也是NULL*/
/*因为准备通过在链表的尾部插入结点(尾插)来建立链表,所以准备了一个尾指针*/
printf(“请输入第一个结点的数据(<0表示输入结束):
”);
scanf(“%d”,&x);/*输入第一个结点数据*/
while(x>=0)/*当输入的数据不是结束标志,反复处理*/
{p=(NODE*)malloc(sizeof(NODE));
/*为新结点申请空间,由指针变量p指示该结点*/
p->data=x;/*在新结点中存入数据*/
/*以下处理把新结点插入到链表的尾部*/
if(head==NULL)/*若当前是空链表*/
{head=p;/*以新结点作为链表第一个结点*/
rear=p;/*新结点也是链表当前最后一个结点*/
}
else/*若新结点不是链表中插入的第一个结点*/
{rear->next=p;
/*新结点的地址由链表最后一个结点来记录*/
rear=p;
/*把新结点作为链表当前新的最后一个结点*/
}
printf(“请输入下一个结点的数据(<0表示输入结束):
”);
scanf(“%d”,&x);/*输入下一个结点数据*/
}
rear->next=NULL;/*链表最后一个结点的指针域置空指针*/
上述操作的示意图如下
head(head=NULL)rear(rear=NULL)
p5headrearp5
p->data=x;head=p;rear=p;
head5p9
rear
head59rear->next=p;
rearp
head59Λrear=p;
rearp
以下是通过在链表的首部插入结点(头插)来建立链表
head=NULL;/*设head是链表的头指针,准备一个空链表*/
printf(“请输入第一个结点的数据(<0表示输入结束):
”);
scanf(“%d”,&x);/*输入第一个结点数据*/
while(x>=0)/*当输入的数据不是结束标志,反复处理*/
{p=(NODE*)malloc(sizeof(NODE));
/*为新结点申请空间,由指针变量p指示该结点*/
p->data=x;/*在新结点中存入数据*/
/*以下处理把新结点插入到链表的首部*/
p->next=head;
/*由新结点来记录由头指针指示的链表第一个结点*/
head=p;/*以新结点作为链表当前新的第一个结点*/
printf(“请输下一个结点的数据(<0表示输入结束):
”);
scanf(“%d”,&x);/*输入下一个结点数据*/
}
上述操作的示意图如下
head(head=NULL)
p5p5Λphead5Λ
p->next=head;head=p;
head5Λp9
p95Λp->next=head;
head
head95Λhead=p;
p
三、链表的基本遍历
1、什么是链表的遍历
所谓链表的遍历是指通过循环操作使指针变量获取链表中各个结点的地址,从而能对链表中各个结点进行所需要的处理。
链表遍历操作的关键是如何从链表当前结点获取下一个结点的地址以及如何控制遍历循环的结束。
例如,假设有以下链表:
headabc…xyΛ
我们准备用指针变量p来指示链表中各个结点(也就是用p来记录链表中各结点的地址),在链表遍历中,通常把起这种作用的指针变量称为“扫描指针变量”(简称扫描指针)。
2、遍历整个链表
/*以下是扫描整个链表的遍历操作*/
p=head;/*扫描指针置初值为链表的第一个结点,即准备从链表的开头进行遍历*/
while(p!
=NULL)/*当扫描指针尚未扫描完链表*/
{……/*通过p处理p当前所指的结点*/
p=p->next;/*使p也指向p->next所指的结点*/
/*因为p->next指示的当前p所指结点的下一个结点*/
/*所以,此操作的含义是由p当前扫描的结点获取即将扫描的下一个结点的地址*/
}
mn…mn
pp=p->next;p
3、定位到链表尾指针的遍历
/*以下是把扫描指针定位到链表尾结点的遍历操作*/
p=head;
while(p->next!
=NULL)
/*当扫描指针所指结点的指针域值不为空,就反复扫描*/
/*因为链表中只有最后一个结点(尾结点)的指针域是空*/
/*所以,此判别条件的含义是若扫描指针尚未指向尾结点*/
/*就循环扫描,而一旦扫描指针指向尾结点,就停止扫描*/
/*所以,循环扫描结束时,扫描指针正好指向尾结点*/
{……
p=p->next;
}
四、链表内结点的插入
1、在链表中已知地址结点的右面插入(右插)
假设有如下由头指针head指示的链表,指针变量p指示链表中某结点b,指针变量r指示即将插入的新结点d。
现要把r所指示的结点插入到p所指示的结点的右面。
head…abc…Λ
rdp
r->next=p->next;/*将p所指结点的后继结点的地址交给新插入结点的指针域记录,也就是使p所指结点的后继结点也成为r所指结点的后继结点,示意图如下*/
head…abc…Λ
rdp
p->next=r;
/*使r所指新插入的结点成为p所指结点的后继结点*/
head…abc…Λ
rdp
2、在链表中已知地址结点的左面插入(左插)
假设有如下由头指针head指示的链表,指针变量p指示链表中某结点b,指针变量r指示即将插入的新结点d。
现要把r所指示的结点插入到p所指示的结点的左面。
head…abc…Λ
rdp
分析:
根据所插入的位置可以看到,链表中p所指示的结点指针域不需要作调整,倒是p所指示结点的前驱结点的指针域需要调整。
在链表中,要操作哪个结点,必须有指针变量获取该结点的地址,通过该指针变量来处理。
所以,左插操作的要点是找到p所指结点的前驱结点,把在p所指结点的左面插入转化为在p所指结点的前驱结点的右面插入。
那么,如何能找到p所指结点的前驱结点呢?
这可以通过遍历链表来实现,只不过要控制扫描指针循环到指向p所指结点的前驱结点时结束。
为此,设一个扫描指针变量q,进行以下遍历操作
q=head;
while(q->next!
=p)/*请自己分析循环判别条件*/
q=q->next;
遍历完成后,问题就变成把r所指的结点插入到q所指结点的右面。
请记住,单链表中的“左插”问题最终要化为“右插”来解决。
如此,插入操作可如下进行:
r->next=q->next;(此时,也可写成r->next=p;)
q->next=r;
q
head…abc…Λ
rdp
3、在链表的尾部插入(尾插)
通过遍历链表,使扫描指针定位在尾结点,插入操作就和普通的“右插”一样。
3、在链表的首部插入(头插)
这在链表建立时已做过。
设链表的头指针是head,新插入结点由p指示,则操作如下:
p->next=head;
head=p;
五、链表内结点的删除
1、删除链表中已知地址结点的后继结点(右删)
设有如下由头指针head指示的链表,现要删除p所指结点的后继结点。
可操作如下:
head…abc…Λ
p
t=p->next;/*使指针变量t指向被删结点*/
p->next=p->next->next;
(此时也可写成p->next=t->next;)
/*把被删结点的后记结点的地址交给p所指的结点来记录,*/
/*这样,被删结点的后继结点就成p所指结点的后继结点*/
free(t);
head…abc…Λ
p
若只是把结点从链表中摘除,并不删除它,则不做free操作。
2、删除链表中已知地址结点本身
设有如下由头指针head指示的链表,现要删除p所指结点本身。
可操作如下:
head…abc…Λ
p
分析:
删除p所指结点后,链表中需要调整指针域的是p所指结点的前驱结点。
所以,本删除操作的关键是如何找到p所指结点的前驱结点。
同样,可以通过如下的遍历链表来实现。
设扫描指针变量是q。
q=head;
while(q->next!
=p)/*请自己分析循环判别条件*/
q=q->next;
如此一来,就变成删除q所指结点的后继结点操作了。
q->next=p->next;(或q->next=q->next->next;)
free(p);
head…abc…Λ
qp
3、删除链表中已知地址结点的前驱结点(左删)
设有如下由头指针head指示的链表,现要删除p所指结点的前驱结点。
可操作如下:
head…abc…Λ
p
分析:
同样,本操作的关键是获取被删结点的前驱结点的地址,从而可以调整该结点的指针域的值。
为此,先进行如下链表遍历。
设扫描指针变量是q。
q=head;
while(q->next->next!
=p)/*请自己分析循环判别条件*/
q=q->next;
t=q->next;
q->next=q->next->next;
(或q->next=p;或q->next=t->next;)
3、删除链表的尾结点(尾删)
先通过链表遍历把扫描指针变量定位在尾结点的前驱结点,接下去的操作就和普通的“尾删”一样了。
遍历链表的循环判别条件可设成while(q->next->next!
=NULL)。
4、删除链表中第一个结点(头删)
此时,操作如下:
t=head;
head=head->next;
free(t);
headabcd…Λ
t
headabcd…Λ
t
六、双链表内结点的插入、删除
1、双链表中结点的定义
设双链表的结点定义如下
typedefstructdlist
{intdata;/*结点的数据域*/
structdlist*front;/*结点的左指针域*/
structdlist*back;/*结点的右指针域*/
}DNODE;
从结点定义可以看到,双链表的结点中设置了两个指针域,双链表的“双”字由此而来。
结点中的左指针域front(相当于prior或left)指向该结点的前驱结点。
结点中的右指针域back(相当于next或right)指向该结点的后继结点。
显然,双链表中第一个结点的左指针域(front域)和最后一个结点的右指针域(back域)都为NULL。
2、双链表中结点的插入
设有如下示意的双链表的一部分,现要在p所指结点的左面(或右面)插入一个由指针变量r所指示的结点,操作如下:
ABC
p
D
r
分析,为叙述方便,下面把结点成为A结点,B结点…,从图上可以清楚地看到,当D结点插入到B结点的左面后,A结点的后继结点和B结点的前驱结点发生了变化,也就是说,A结点的右指针域和B结点的左指针域都要重新调整,当然,对D结点来说,插入到链表中后,D结点的左、右指针域也要填上相应的前驱结点和后继结点的地址。
所以,总的来说,在双链表内部插入一个结点,需要调整4个指针域。
操作如下,
(1)r->back=p;
(2)r->front=p->front;
(3)p->front->back=r;(4)p->front=r;
ABC
(1)p
D
(2)
r
ABC
(1)(3)p
D
(2)
r
ABC
(1)(3)(4)p
D
(2)
r
思考,在B结点的右面插入怎么做?
(3)、(4)步的顺序能互换吗?
假如语句(4)就在第(3)步写了,该怎么办?
3、双链表中结点的删除
(1)删除指针变量所指结点本身
ABC
p
分析,从示意图可以看出,当p所指的B结点被删除后,对于B结点的前驱结点A结点来说,它的后继结点发生了变化,而对于B结点的后继结点C结点来说,它的前驱结点也发生了变化。
所以,当B结点被删除后,链表中A结点的后继指针域(back)和C结点的前驱指针域(front)都要重新调整。
所以,总的来说,在双链表内部删除一个结点,需要调整2个指针域。
删除操作如下。
(1)p->front->back=p->back;
/*被删结点的后继结点地址交给它的前驱结点来记录*/
(2)p->back->front=p->front;
/*被删结点的前驱结点地址交给它的后继结点来记录*/
若不是仅从链表摘除,而是要删除结点,则再写free(p);
(1)
ABC
(2)p
(2)删除指针变量所指结点的前驱结点
ABC
qp
分析,可以设一个指针变量q使其指向被删结点。
操作如下,q=p->front;接下去的操作就是删除q所指示的结点本身,和前面所叙述过的操作一样。
(3)删除指针变量所指结点的后继结点
ABC
pq
分析,设一个指针变量q,使其指向被删结点,q=p->back;接下去就是删除q所指结点本身的操作了。
七、循环链表
1、循环链表的概念
所谓循环链表是指链表最后一个结点的指针域不设为NULL,而是记录链表中第一个结点的地址,也就是使链表的最后一个结点指向链表的第一个结点。
当然,对于双链表来说,它的第一个结点的左指针域也不是NULL,而是指向链表的最后一个结点。
Head…
head…
请记住,在循环链表中,没有指针域为NULL的结点。
2、循环链表中结点的插入和删除
对于链表内部结点(不是第一个也不是最后一个)的插入、删除操作来说,和非循环链表是一样的。
(1)单循环链表中结点的插入
对于单循环链表来说,插入第一个结点时,结点指针域的值是结点本身的地址。
headp->next=p;
p
而在单循环链表中最后一个结点后面插入时,也要通过遍历使扫描指针定位在最后一个结点上。
但由于循环链表中结点的指针域不可能为空,所以,在遍历时不能以NULL为判别标志。
此时,可操作如下。
head…
q=head;qr
while(q->next!
=head)
q=q->next;
r->next=q->next;q->next=r;
(2)单循环链表中结点的删除
删除单循环链表第一个结点时,要注意,此时还要调整链表最后一个结点的指针域,使其指向链表中新的第一个结点。
操作如下。
head…
tq
q=head;
while(q->next!
=head)q=q->next;/*使q指向尾结点*/
t=head;/*t指向被删结点*/
head=head->next;
q->next=head;/*使尾结点指向链表中新的第一个结点*/
free(t);
(3)双循环链表中结点的插入
对于双循环链表来说,插入第一个结点时,结点指针域的值是结点本身的地址。
headp->back=p;p->front=p;
p
在双循环链表的最后一个结点的右面插入操作可以化为在第一个结点的左面插入操作。
head…
<1>r->front=head->front;
r<2>r->back=head;
<3>head->front->back=r;
<4>head->front=r;
head…
<4><1><3>
<2>
r
<1>r->front=head->front;
<2>r->back=head;
<3>head->front->back=r;
<4>head->front=r;
(4)双循环链表中结点的删除
和非循环的双链表内部结点的删除操作一样。
八、带头结点的链表
1、头结点的概念
所谓头结点是指专门用来占据链表中第一个结点位置的结点,它的结构和链表中其它结点一样,但不用它放正式的结点数据,也不能在链表操作中把它删除,有时也用它来存放链表有关的属性数据,例如链表中结点的个数。
由于头结点始终处在链表第一个结点的位置,所以链表中数据元素结点是链表中第二个及以后的结点。
请区分以下几个术语:
头结点–专门放置在链表第一个结点位置的非链表元素结点。
头指针–链表中第一个结点的地址(链表的首地址)。
头指针变量–记录(指示)链表首地址的指针变量,有时也简称为头指针。
首元结点–链表中第一个元素结点。
显然,对于不带头结点的链表来说,头指针变量指示首元结点。
而对于带头结点的链表来说,头指针变量永远指示头结点。
2、带头结点的单链表
(1)空链表的表示headΛ
(2)在链表首部插入(头插)
实际上就是在头指针变量head所指的头结点的右面插入。
设r指示要插入的结点
headABYΛrK
r->next=head->next;head->next=r;
(3)删除链表首元结点(头删)
headABYΛrK
t
t=head->next;
head->next=head->next->next;(或head->next=t->next)
free(t);
3、带头结点的单循环链表
空表的表示headhead->next=head;
带头结点的单循环链表的整个遍历
head…
q=head->next;
while(q!
=head)
{处理q所指的结点
q=q->next;
}
而不带头结点的单循环链表的整个遍历操作如下
head…
q=head;
处理q所指的首元结点
q=head->next;
while(q!
=head)
{处理q所指的结点
q=q->next;
}
4、带头结点的双循环链表
空表的表示head
其它操作和非循环的双链表或单循环链表一样。
九、链表的分类
我们现在学习的线性链表可以如下分类
按结点中指针域的个数–单链表和双链表
按链表是否循环构造–非循环链表和循环链表
按链表是否带头结点–不带头结点或带头结点的链表
三个方面组合起来可以有8种不同形式的链表。
十、链表的连接
1、两个不带头结点的单链表的连接
设有如下两个不带头结点的单链表A和B,现在要把B链表接在A链表的后面,形成一个链表。
Aabc…hΛ
Bijk…xΛ
分析,此操作的要点是使B链表的第一个结点成为A链表最后一个结点的后继结点。
为此,要遍历A链表,使扫描指针定位在A链表的尾结点。
操作如下。
p=A;
while(p->next!
=NULL)p=p->next;
p->next=B;
2、两个不带头结点的单循环链表的连接
设有如下两个不带头结点的单循环链表A和B,现在要把B链表接在A链表的后面,形成一个链表。
Aabc…h
Bijk…x
分析,此操作的要点同样要使B链表的第一个结点成为A链表最后一个结点的后继结点。
但是,因为是循环链表,所以还要使B链表的最后一个结点指向A链表的第一个结点。
为此,要遍历A链表,使扫描指针定位在A链表的尾结点。
同样,也要遍历B链表,使扫描指针定位在B链表的尾结点。
操作如下。
p=A;
while(p->next!
=A)p=p->next;
q=B;
while(q->next!
=B)q=q->next;
p->next=B;q->next=A;
3、两个带头结点的单链表的连接
设有如下两个不带头结点的单链表A和B,现在要把B链表接在A链表的后面,形成一个链表。
Aab…hΛ
Bij…xΛ
分析,此操作的要点是使B链表的第一个数据元素结点成为A链表最后一个结点的后继结点。
为此,要遍历A链表,使扫描指针定位在A链表的尾结点。
操作如下。
p=A;
while(p->next!
=NULL)p=p->next;
p->next=B->next;free(B);
4、两个带头结点的单循环链表的连接
设有如下两个不带头结点的单循环链表A和B,现在要把B链表接在A链表的后面,形成一个链表。
Aab…h
Bij…x
分析,此操作的要点同样要使B链表的第一个数据元素结点成为A链表最后一个结点的后继结点。
但是,因为是循环链表,所以还要使B链表的最后一个结点指向A链表的第一个结点。
为此,要遍历A链表,使扫描指针定位在A链表的尾结点。
同样,也要遍历B链表,使扫描指针定位在B链表的尾结点。
操作如下。
p=A;
while(p->next!
=A)p=p->next;
q=B;
while(q->next!
=B)q=q->next;
p->next=B->next;q->next=A;
十、链表和数组的比较
数组与链表的主要区别在于,数组是一片预先定义好了大小的连续存储区,所以,存储空间的大小不能改变,使用前要预先估计所需存储空间的大小;而动态链表的诸结点空间之间不必连续,并且,可以根据需要随时申请新的结
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据结构 复习 纲要