当前位置:首页 > 教学设计 > 单链表.双链表.循环链表和静态链表的习题:
 

单链表.双链表.循环链表和静态链表的习题:

发布时间:2019-08-08 09:49:39 影响了:

单链表、双链表、循环链表和静态链表的习题 一、单项选择题

1. 关于线性表的顺序存储结构和链式存储结构的描述中,正确的是( )。

Ⅰ. 线性表的顺序存储结构优于其链式存储结构

Ⅱ. 链式存储结构比顺序存储结构能更方便地表示各种逻辑结构

Ⅲ. 如频繁使用插入和删除结点操作,顺序存储结构更优于链式存储结构

Ⅳ. 顺序存储结构和链式存储结构都可以进行顺序存取

A. Ⅰ、Ⅱ、Ⅲ B. Ⅱ、Ⅳ C. Ⅱ、Ⅲ D. Ⅲ、Ⅳ

2. 对于一个线性表既要求能够进行较快速地插入和删除,又要求存储结构能反映数据之间的逻辑关系,则应该用( )。

A. 顺序存储方式 B. 链式存储方式 C.散列存储方式 D. 以上均可以

3. 对于顺序存储的线性表,其算法的时间复杂度为O(1)的运算应该是()。

A. 将n 个元素从小到大排序 B. 删除第i 个元素(1

C. 改变第i 个元素的值(1

4. 下列关于线性表说法正确的是( )。

Ⅰ. 顺序存储方式只能用于存储线性结构

Ⅱ. 取线性表的第i 个元素的时间同i 的大小有关

Ⅲ. 静态链表需要分配较大的连续空间,插入和删除不需要移动元素

Ⅳ. 在一个长度为n 的有序单链表中插入一个新结点并仍保持有序的时间复杂度为O(n) Ⅴ. 若用单链表来表示队列,则应该选用带尾指针的循环链表

A. Ⅰ、Ⅱ B.Ⅰ、Ⅲ、Ⅳ、Ⅴ C. Ⅳ、Ⅴ D. Ⅲ、Ⅳ、Ⅴ

5. 设线性表中有2n 个元素,( )在单链表上实现要比在顺序表上实现效率更高。

A. 删除所有值为x 的元素

B. 在最后一个元素的后面插入一个新元素

C. 顺序输出前k 个元素

D. 交换第i 个元素和第2n-i-l 个元素的值(i=0,…, n-1)

6. 在一个单链表中,已知q 所指结点是p 所指结点的前驱结点,若在q 和p 之间插入结点s ,则执行( )。

A .s->next=p->next;p->next=s; B.p->next=s->next; s->next=p;

C. q->next=s;s->next=p; D. p->next=s;s->next=q;

7. 给定有n 个元素的一维数组,建立一个有序单链表的最低时间复杂度是( )。

A. O(1) B. O(n) C. O(n2-------) D. O(nlog2------n)

8. 将长度为n 的单链表链接在长度为m 的单链表后面,其算法的时间复杂度釆用大O 形式表示应该是( )。

A. O(1) B. O(n) C. O(m) D. O(n+m)

9. 单链表中,增加一个头结点的目的是为了( )。

A. 使单链表至少有一个结点 B. 标识表结点中首结点的位置

C. 方便运算的实现 D. 说明单链表是线性表的链式存储

10. 在一个长度为n 的带头结点的单链表h 上,设有尾指计r ,则执行( )操作与链表的表长有关。

A. 删除单链表中的第一个元素

B. 删除单链表中最后一个元素

C. 在单链表第一个元素前插入一个新元素

D. 在单链表最后一个元素后插入一个新元素

11. 对于一个头指针为head 的带头结点的单链表,判定该表为空表的条件是( ); 对于不带头结点的单链表,则判定空表的条件为( )。

A .head==NULL B .head->next=NULL

C. head->next==head D. head!=NULL

12. 下面关于线性表的一些说法中,正确的是( )。

A .对一个设有头指针和尾指针的单链表执行删除最后一个元素的操作与链表长度无关

B. 线性表中每个元素都有一个直接前趋和一个直接后继

C. 为了方便插入和删除数据,可以使用双链表存放数据

D. 取线性表第i 个元素的时间同i 的大小有关

13. 某线性表中最常见的操作是在最后一个元素之后插入一个元素和删除第一个元素,则釆用( )存储方式最省时间。

A. 单链表 B. 仅有头指针的单循环链表

C. 双链表 D. 仅有尾指针的单循环链表

14. 在双链表中向p 所指的结点之前插入一个结点q 的操作为( )。

A. p->prior=q;q->next=p;p->prior->next=q;q->prior=p->prior; B .q->prior=p->prior;p->prior->next=q;q->next=p;p->priop=q->next; C. q->next=p;p->next=q;q->prior->next=q;q->next=p;

D .p->prior->next=q; q->next=p; q->prior=p->prior;p->prior=q;

15. 在双向链表存储结构中,删除p 所指的结点时必须修改指针( )。 A .p->llink->rlink=p->rlink; p->rlink->llink=p->llink; B.p->llink=p->llink->llink;p->llink->rlink=p; C.p->rlink->llink=p; p->rlink = p->rlink->rlink;

D .p->rlink=p->llink->llink;p->llink=p->rlink->rlink;

16. 在长度为n 的有序单链表中插入一个新结点,并仍然保持有序的时间复杂度是( )。

A. O(1) B. O(n) C.O(n2---------) D. O(nlog2--------n)

17. 与单链表相比,双链表的优点之一是( )。

A. 插入、删除操作更方便 B. 可以进行随机访问

C. 可以省略表头指针或表尾指针 D. 访问前后相邻结点更灵活

18. 带头结点的双循环链表L 为空的条件是( )。 A. L->prior==L&&L->next==NULL B. L->prior==NULL&&L->next==NULL C. L->prior==NULL&&L->next==L

D. L->prior==L&&L->next==L

19. 一个链表最常用的操作是在末尾插入结点和删除结点,则选用( )最节省时间。

A. 带头结点的双循环链表 B. 单循环链表

C. 带尾指针的单循环链表 D. 单链表

20. 设对n(n>1)个元素的线性表的运算只有4种:删除第一个元素;删除最后一个元素; 在第一个元素之前插入新元素;在最后一个元素之后插入新元素,则最好使用( )。

A. 只有尾结点指计没有头结点指针的循环单链表

B. 只有尾结点指针没有头结点指针的非循环双链表

C. 只有头结点指针没有尾结点指针的循环双链表

D. 既有头结点指针也有尾结点指针的循环单链表

21. —个链表最常用的操作是在最后一个元素之后插入一个元素和删除第一个元素,则选

用( )最节省时间。

A. 不带头结点的单循环链表 B.双链表

C. 不带头结点且有尾指针的单循环链表 D.单链表

22. 静态链表中指针表示的是( )。

A. 下一元素的地址 B. 内存储器地址

C. 下一个元素在数组中的位置 D.左链或右链指向的元素的地址

23. 需要分配较大的空间,插入和删除不需要移动元素的线性表,其存储结构为( )。

A. 单链表 B. 静态链表 C. 顺序表 D. 双链表

二、综合应用题

1. 设计一个递归算法,删除不带头结点的单链表L 中所有值为x 的结点。

2. 在带头结点的单链表L 中,删除所有值为x 的结点,并释放其空间,假设值为x 的结点不唯一,试编写算法以实现上述操作。

3. 设L 为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值。

4. 试编写在带头结点的单链表L 中删除一个最小值结点的高效算法(假设最小值结点是唯一的)。

5. 试编写算法将带头结点的单链表就地逆置,所谓“就地”是指辅助空间为O(1)。

6. 有一个带头结点的单链表L ,设计一个算法使其元素递增有序。

7. 设在一个带表头结点的单链表中所有元素结点的数据值无序,试编写一个函数,删除表中所有大于最小值小于最大值的元素(若存在) 。

8. 给定两个单链表,编写算法找出两个链表的公共结点。

9. 给定一个带表头结点的单链表,设head 为头指针,结点的结构为(data, next),data 为整型元素,next 为指针,试写出算法:按递增次序输出单链表中各结点的数据元素,并释放结点所占的存储空间(要求:不允许使用数组作为辅助空间)。

10. 将一个带头结点的单链表A 分解为两个带头结点的单链表A 和B ,使得A 表中含有原表中序号为奇数的元素,而B 表中含有原表中序号为偶数的元素,且保持其相对顺序不变。

11. 设C={a1, b1, a2, b2, …, an, bn}为线性表,釆用带头结点的hc 单链表存放,设计一个就地算法,将其拆分为两个线性表,使得A={a1, a2,…, an}, B={bn, …, b2, b1}

12. 在一个递增有序的线性表中,有数值相同的元素存在。若存储方式为单链表,设计算法去掉数值相同的元素,使表中不再有重复的元素。例如(7, 10, 10, 21, 30, 42, 42, 42, 51, 70)将变作(7, 10, 21, 30, 42, 51, 70)。

13. 假设有两个按元素值递增次序排列的线性表,均以单链表形式存储。请编写算法将这两个单链表归并为一个按元素值递减次序排列的单链表,并要求利用原来两个单链表的结点存放归并后的单链表。

14. 设A 和B 是两个单链表(带头结点),其中元素递增有序。设计一个算法从A 和B 中公共元素产生单链表C ,要求不破坏A 、B 的结点。

15. 已知两个链表A 和B 分别表示两个集合,其元素递增排列。编制函数,求A 与B 的交集,并存放于A 链表中。

16. 两个整数序列A=a1, a2, a3,…, am和B=b1, b2, b3,…,bn 已经存入两个单链表中,设计一个算法,判断序列B 是否是序列A 的连续子序列。

17. 设计一个算法用于判断带头结点的循环双链表是否对称。

18. 有两个循环单链表,链表头指针分别为h1和h2,编写一个函数将链表h2链接到链表h1之后,要求链接后的链表仍保持循环链表形式。

19. 设有一个带头结点的循环单链表,其结点值均为正整数。设计一个算法,反复找出单链表中结点值最小的结点并输出,然后将该结点从中删除,直到单链表空为止,再删除表头结点。

20. 设头指针为L 的带有表头结点的非循环双向链表,其每个结点中除有pred(前驱指针) 、data(数据) 和next(后继指针)域外,还有一个访问频度域freq

。在链表被启用前,其值均初始化为零。每当在链表中进行一次Locate(L, x)运算时,令元素值为x 的结点中freq 域的值增1,并使此链表中结点保持按访问频度非增(递减)的顺序排列,同时最近访问的结点排在频度相同的结点的前面,以便使频繁访问的结点总是靠近表头。试编写符合上述要求的Locate(L, x)运算的算法,该运算为函数过程,返回找到结点的地址,类型为指针型。

21. 【2009年计算机联考真题】

已知一个带有表头结点的单链表,结点结构为

假设该链表只给出了头指针list 。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k 个位置上的结点(k 为正整数)。若查找成功,算法输出该结点的 data 域的值,并返回1;否则,只返回0。要求:

1) 描述算法的基本设计思想。

2) 描述算法的详细实现步骤。

3) 根据设计思想和实现步骤,釆用程序设计语言描述算法(使用C 、C++或Java 语言实现),关键之处请给出简要注释。

22. 【2012年计算机联考真题】

假定釆用带头结点的单链表保存单词,当两个单词有相同的后缀时,则可共享相同的后缀存储空间,例如,“loading ”和“being ”的存储映像如下图所示。

设strl 和str2分别指向两个单词所在单链表的头结点,链表结点结构为,请设计一个时间上尽可能高效的算法,找出由str1和str2所指向两个链表共同后缀的起始位置 (如图中字符i 所在结点的位置p) 。要求:

1) 给出算法的基本设计思想。

2) 根据设计思想,釆用C 或C++或Java 语言描述算法,关键之处给出注释。

3) 说明你所设计算法的时间复杂度。

答案与解析

一、单项选择题

1. B

两种存储结构有不同的适用场合,不能简单地说谁好谁坏,Ⅰ错误。链式存储用指针表示逻辑结构,而指针的设置是任意的,故可以很方便地表示各种逻辑结构;顺序存储则只能用物理上的邻接关系来表示逻辑结构,Ⅱ正确。在顺序存储中,插入和删除结点需要大量的移动元素,效率较低,Ⅲ的描述刚好相反。顺序存储结构既可以随机存取也能顺序存取,而链式结构只能进行顺序存取,Ⅳ正确。

2. B

要求较快速地插入和删除,排除A 、D 。散列存储通过散列函数映射到物理空间,不能反映数据之间的逻辑关系,排除C 。链式存储中的静态链表满足这一要求。

3. C

对n 个元素进行排序的时间复杂度最快也要O(n)(初始有序) ,通常是O(nlog2---------n)或O(n2------)。在顺序表中删除第i 个元素,或在第i 个元素之后插入一个新元素,如想保持其它元素原来的顺序,时间复杂度为O(n),因此A 、B 、D 均错误。在顺序存储的线性表中更改第i 个元素的值,直接用下标访问并重新赋值即可,时间复杂度为O(1)。

4. D

顺序存储方式同样适合图和树的存储,如满二叉树的顺序存储,Ⅰ错误。若线性表釆用顺序存储方式,则取其第i 个元素的时间与i 的大小无关,Ⅱ错误。Ⅲ是静态链表的特有性质。单链表只能顺序查找插入位置,时间复杂度为O(n),若为顺序表,可釆用折半查找,时间复杂度可降至O(log2-------n), Ⅳ正确。队列需要在表头删除元素,表尾插入元素,故釆用带尾指针的循环单链表较为方便,插入和删除的时间复杂度都是O(1),Ⅴ正确。

5. A

A 中对于单链表和顺序表上实现的时间复杂度都为O(n),但后者要移动很多元素,所以在单链表上实现效率更高。B 和D 效率刚好相反,C 无区别。

6. C

s 插入后,q 成为s 的前驱,而p 成为s 的后继,选项C 满足此条件。

7. D

若先建立链表,然后依次直接插入建立有序表,则每插入一个元素就需遍历链表寻找插入位置,此即链表的插入排序,时间复杂度为O(n2-----)。若先将数组排好序,然后建立链表,建立链表的时间复杂度为O(n),而数组排序的最少时间复杂度为O(nlog2-----------n),故时间复杂度为O(nlog2----------n)。本题问最少时间复杂度,故选D 。

8. C

先遍历长度为m 的单链表,找到这个长度为m 的单链表的尾结点,然后将其next 域置为另一个单链表的首结点,其时间复杂度为O(m)。

9. C

单链表设置头结点的目的是为了方便运算的实现,主要好处体现在:第一,有头结点后,插入和删除数据元素的算法统一了,不再需要判断是否在第一个元素之前插入或删除第一个元素;第二,不论链表是否为空,链表指针不变。

10. B

删除单链表的最后一个结点需要置其前驱结点的指针域为NULL ,故需要的时间为O(n),与表长有关。其他操作均与表长无关,读者可以自行模拟。

11. B, A

在带头结点的单链表中,头指针head 指向头结点,头结点的next 域指向第1个元素结点,head->next==NULL表示该单链表为空。在不带头结点的单链表中,head 直接指向第1 个元素结点,head==NULL表示该单链表为空。

12. C

双链表能很方便地访问前驱和后继,故删除和插入数据较为方便。A 显然错误。B 表中第一个元素和最后一个元素不满足题设要求。D 未考虑顺序存储的情况。B 、C 、D 在删除尾结点时,都需要先查找其前驱结点,时间复杂度为O(n)。

13. D

在最后一个元素之后插入元素,需要先找到最后一个元素,故A 、B 和C 的时间复杂度均为O(n)。B 因为没有特殊的指针指示头结点或尾结点,故需从某一结点向固定的方向顺序依次搜索插入和删除的位置,时间复杂度也为O(n)。D 的两种算法的时间复杂度都是O(1),如下图所示。

14. D

为了在p 之前插入结点q ,可以将p 的前一个结点的next 域指向q ,将q 的next 域指向 p ,将q 的prior 域指向p 的前一个结点,将p 的prior 域指向q 。仅D 满足条件。

15. A

与上一题的分析基本类似,只不过这里是删除一个结点,注意将p 的前、后两结点链接起来。

注,请读者仔细对比上述两题,弄清双链表的插入和删除的方法。

16. B

假设单链表递增有序(递减的情况同理),在插入数据为x 的结点之前,先要在单链表中找到第一个大于x 的结点的直接前驱p ,在p 之后插入该结点。查找过程的时间复杂度为O(n),插入过程的时间复杂度为O(1),因此时间复杂度为O(n)。

17. D

在双链表中可以快速访问任何一个结点的前后相邻结点,而在单链表中只能快速访问任何一个结点的后继结点。

18. D

循环单链表L 判空的条件是头结点(头指针)的prior 和next 域都指向它自身。

19. A

在链表的末尾插入和删除一个结点时,需要修改其相邻结点的指针域。而寻找尾结点以及尾结点的前驱结点,只有带头结点的双循环链表所需要的时间最少。

20. C

对于A ,删除尾结点*p时,需要找到*p的前一个结点,时间复杂度为O(n)。对于B ,删除首结点*p时,需要找到*p结点,这里没有直接给出头结点指针,而通过尾结点的prior 指

针找到*p结点的时间复杂度为O(n)。对于D ,删除尾结点*p时,需要找到*p的前一个结点,时间复杂度为O(n)。对于C ,执行这4种算法的时间复杂度均为O(1)。

21. C

对于A ,在最后一个元素之后插入一个元素的情况与普通单链表相同,时间复杂度为 O(n);而删除表中第一个元素时,为保持单循环链表的性质(尾结点的指计指向第一个结点) ,需要首先遍历整个链表找到尾结点,然后再执行删除操作,时间复杂度也为O(n)。对于B ,双链表的情况与单链表的相同,一个是O(n),一个是O(1)。对于C ,与A 的分析对比,因为已经知道了尾结点的位置,省去了遍历链表的过程,因此插入和删除的时间复杂度均为 O(1)。对于D ,要在最后一个元素之后插入一个元素,需要遍历整个链表才能找到插入位置,时间复杂度为O(n);删除第一个元素的时间复杂度为O(1)。

22. C

静态链表中的指针又称游标,指示下一个元素在数组中的下标。

23. B

静态链表用数组表示,因此需要预先分配较大的连续空间,静态链表同时还具有一般链表的特点,即插入和删除不需要移动元素。

二、综合应用题

1. 解答:

设f(L,x) 的功能是删除以L 为首结点指针的单链表中所有值等于x 的结点,则显然有 f(L->next, x)的功能是删除以L->next为首结点指针的单链表中所有值等于x 的结点。由此,可以推出递归模型如下:

终止条件:

f(L, x) ≡ 不做任何事情; 若L 为空表或只有一个结点

f(L, x)≡删除*L 结点;f(L->next, x); 若 L->data==x

递归主体:

f(L, x)≡f(L->next, x); 其他情况

本题代码如下: 1. void Del_X_3 (Linklist &L , ElemType x) {

2. //递归实现在单链表L 中删除值为x 的结点

3. LNode *p ; //p指向待删除结点

4. if (L ==NULL ) //递归出口

5. return ;

6.

7. if (L ->data ==x ) { //若L 所指结点的值为x

8. p=L ; //删除*L,并让L 指向下一结点

9. L=L ->next ;

10. free (p );

11. Del_X_3(L , x ) ; //递归调用

12. }else //若L 所指结点的值不为x

13. Del_X_3 (L ->next , x) ; //递归调用

14. }

算法需要借助一个递归工作栈,深度为O(n),时间复杂度为O(n)。有读者认为直接free 掉p 结点会造成断链,实际上因为L 为引用,是直接对原链表进行操作,因此不会断链。

2. 解答:

解法一:用p 从头至尾扫描单链表,pre 指向*p结点的前驱。若p 所指结点的值为x ,则删除,并让p 移向下一个结点,否则让pre 、p 指针同步后移一个结点。

本题代码如F : 1. void Del_X_1(Linklist &L , ElemType x){ 2. //L为带头的单链表,本算法删除L 中所有值为x 的结点 3. LNode *p =L ->next , *pre =L , *q ; //置 p 和 pre 的初始值

4.

5. while (p !=NULL ){

6. if (p ->data ==x ){

7. q=p ; //q指向该结点

8. p=p ->next ;

9. pre->next =p ; //删除*q 结点

10. free (q ); //释放 *q结点的空间

11. }else { //否则,pre 和p 同步后移

12. pre=p ;

13. p=p ->next ;

14. } //else

15. } //while

16. }

本算法是在无序单链表中删除满足某种条件的所有结点,这里的条件是结点的值为x 。实际上,这个条件是可以任意指定的,只要修改if 条件即可,比如,我们要求删除值介于 mink 和maxk 之间的所有结点,则只需将if 语句修改为if(p->data>mink &&p->data

解法二:釆用尾插法建立单链表。用p 指针扫描L 的所有结点,当其值不为x 时将其链接

到L 之后,否则将其释放。

本题代码如下: 1. void Del_X_2(Linklist &L , ElemType x){

2. //L为带头的单链表,本算法删除L 中所有值为x 的结点

3. LNode *p =3->next , *r =L , *q ; //r指向尾结点,其切值为头结点 4.

5. while (p !=NULL ){

6. if (p ->data !=x ) { //*p结点值不为x 时将其链接到L 尾部

7. r->next =p ;

8. r=p ;

9. p=p ->next ; //继续扫描

10. }else { //*p结点值为x 时将其释放

11. q=p ;

12. p=p ->next ; //继续扫描

13. free (q ); //释放空间

14. }

15. }//while

16. r->next =NULL ; //插入结束后置尾结点指针为NULL

17. }

上述两个算法扫描一遍链表,时间复杂度为O(n),空间复杂度为O(1)。

3. 解答:

考虑到从头到尾输出比较简单,本题的思路很自然地联系到借助上题链表逆置的方法来实现,改变链表的方向,然后就可以从头到尾实现反向输出了。

此外,本题还可以借助一个栈来实现,每经过一个结点时,将该结点放入栈中。在遍历完整个链表后,再从栈顶开始输出结点值即可。

既然能用栈的思想解决,也就很自然地联想到了用递归来实现,每当访问一个结点时,先递归输出它后面的结点,再输出该结点自身,这样链表就反向输出了。如下图所示:

本题代码如下:

1. void R_Print(LinkList L){

2. //从尾至头输出单链表L 中每个结点的值

3. if (L ->next !=NULL ){

4. R_Print (L ->next ) ; //递归

5. } //if

6. print (L ->data ) ; //输出函数

7. }

4. 解答:

算法思想:用p 从头至尾扫描单链表,pre 指向*p结点的前驱,用minp 保存值最小的结点指针(初值为p ),minpre 指向*minp结点的前驱(初值为pre )。一边扫描,一边比较,若p->dafa小于minp->dara,则将p 、pre 分别赋值给minp 、minpre, 如下图所示。当p 扫描完毕,minp 指向最小值结点,minpre 指向最小值结点的前驱结点,再将minp

所指结点删除即可。

本题代码如下:

1. LinkList Delete_Min(LinkList &L ){

2. //L是带头结点的单链表,本算法删除其最小值结点

3. LNode *pre = L, *p =pre ->next ; //p 为工作指针,pre 指向其前驱

4. LNode *minpre =pre , *minp =p ; //保存最小值结点及其前驱

5. while (p !=NULL ){

6. if (p ->data data ){

7. minp=p ; //找到比之前找到的最小值结点更小的结点

8. minpre=pre ;

9. }

10. pre=p ; //继续扫描下一个结点

11. p=p ->next ;

12. }

13. minpre->next =minp ->next ; //删除最小值结点

14. free (minp );

15. return L;

16. }

算法需要从头至尾扫描链表,时间复杂度为O(n),空间复杂度为O(1)。

若本题改为不带头结点的单链表,则实现上会有所不同,留给读者自行思考。

5. 解答:

解法一:将头结点摘下,然后从第一结点开始,依次前插入到头结点的后面(头插法建立单

链表)

,直到最后一个结点为止,则实现了链表的逆置,如下图所示。

本题代码如下:

1. LinkList Reverse_l(LinkList &L ){

2. //L是带头结点的单链表,本算法将L 就地逆置

3. p=L ->next ; //p为工作指针,从第一个元素结点开始

4. L->next = NULL; //先将头结点L 的next 域置为NULL

5. while (p !=NULL ) { //依次将元素结点摘下

6. r=p ->next ; //暂存p 的后继

7. p->next =L ->next ; //将p 结点插入到头结点之后

8. L->next =p ;

9. p=r ;

10. }

11. return L;

12. }

解法二:大部分辅导书都只介绍解法一,这对读者的理解和思维是不利的。为了将调整指针这个复杂的过程分析清楚,我们借助图形来进行直观的分析。

假设pre 、p 和r 指向3个相邻的结点,如下图所示。假设经过若千操作,*pre之前的结点的指针都已调整完毕,它们的next 都指向其原前驱结点。现在令*p结点的next 域指向*pre 结点,注意到一旦调整指针的指向后,*p的后继结点的链就断开了,为此需要用r 来指向原 *p的后继结点。处理时需要注意两点:一是在处理第一个结点时,应将其next 域置为

NULL ,而不是指向头结点

(因为它将作为新表的尾结点);二是在处理完最后一个结点后,需要将头结点的指针指向它。

本题代妈如下:

1. LinkList Reverse_2(LinkList &L ){

2. //依次遍历线性表L, 并将结点指针反转

3. LNode *pre ,*p =L ->next ,*r =p ->next ;

4. p->next =NULL ; //处理第一个结点

5. while (r !=NULL ) { //r为空,则说明p 为最后一个结点

6. pre=p ; //依次继续遍历

7. p=r ;

8. r=r ->next ;

9. p->next =pre ; //指针反转

10. }

11. L->next =p ; //处理最后一个结点

12. return L;

13. }

上述两个算法的时间复杂度为O(n),空间复杂度为O(1)。

6. 解答:

算法思想:釆用直接插入排序算法的思想,先构成只含一个数据结点的有序单链表,然后依次扫描单链表中剩下的结点*p (直至p==NULL为止) ,在有序表中通过比较查找插入 *p的前驱结点*pre,然后将*p插入到*pre之后,如下图所示。

本题代码如下:

1. void Sort (LinkList &L ){

2. //本算法实现将单链表L 的结点重排,使其递增有序

3. LNode *p =L ->next , *pre ;

4. LNode *r =p ->next ; //r保持*p后继结点指针,以保证不断链

5. p->next =NULL ; //构造只含一个数据结点的有序表

6. p=r ;

7. while (p !=NULL ){

8. r=p ->next ; //保存*p的后继结点指针

9. pre=L ;

10. while (pre ->next !=NULL && pre->next ->data

data )

11. pre=pre ->next ; //在有序表中查找插入的前驱结点*pre

12. p->next =pre ->next ; //将*p 插入到*pre 之后

13. pre->next =p ;

14. p=r ; //扫描原单链表中剩下的结点

15. }

16. }

7. 解答:

因为链表是无序的,所以只能逐个结点进行检查,执行删除。

本题代码如下:

1. void RangeDelete (LinkList &L , int min, int max){

2. LNode *pr = L, *p =L ->link ; //p 是检测指针,pr 是其前驱

3. while (p !=NULL )

4. if (p ->data >min &&p ->data

5. pr->link =p ->link ;

6. free (p );

7. p=pr ->link ;

8. }else { //否则继续寻找被删结点

9. pr=p ;

10. p=p ->link ;

11. }

12. }

8. 解答:

两个单链表有公共结点,也就是说两个链表从某一结点开始,它们的next 都指向同一个结点。由于每个单链表结点只有一个next 域,因此从第一个公共结点开始,之后它们所有的结点都是重合的,不可能再出现分叉。所以,两个有公共结点而部分重合的单链表,拓扑形状看起来像Y ,而不可能像X 。

本题极容易联想到“蛮”方法:在第一个链表上顺序遍历每个结点,每遍历一个结点,在第二个链表上顺序遍历所有结点,如果找到两个相同的结点,于是就找到了它们的公共结点。显然,该算法的时间复杂度为O(len1*len2)。

接下来我们试着去寻找一个线性时间复杂度的算法。先把问题简化:如何判断两个单向链表有没有公共结点?应注意到这样一个事实:如果两个链表有一个公共结点,那么该公共结点之后的所有结点都是重合的,即它们的最后一个结点必然是重合的。因此,我们判断两个链表是不是有重合的部分,只要分别遍历两个链表到最后一个结点。如果两个尾结点是一样的,说明它们有公共结点,否则两个链表没有公共的结点。

然而,在上面的思路中,顺序遍历两个链表到尾结点的时候,并不能保证在两个链表上同时到达尾结点。这是因为两个链表长度不一定一样。但假设一个链表比另一个长k 个结点,我们先在长的链表上遍历k 个结点,之后再同步遍历,此时我们就能保证同时到达最后一个结点了。由于两个链表从第一个公共结点开始到链表的尾结点,这一部分是重合的。因此,它们肯定也是同时到达第一公共结点的。于是在遍历中,第一个相同的结点就是第一个公共的结点。

在这个思路中,我们先要分别遍历两个链表得到它们的长度,并求出两个长度之差。在长的链表上先遍历长度之差个结点之后,再同步遍历两个链表,直到找到相同的结点,或者一直到链表结束。此时,该方法的时间复杂度为O(len1+len2)。

本题代码如下: 1. LinkList Search_1st_Common(LinkList Ll, LinkList L2){

2. //本算法实现在线性的时间内找到两个单链表的第一个公共结点

3. int len1 = Length (L1), len2 = Length (L2) ; //计算两个链表的表长

4. LinkList longList, shortList; //分别指向表长较长和较短的链表

5. if (len1>len2) { //L1 表长较长

6. longList = Ll->next ; shortList=L2->next ;

7. dist=len1-len2; //表长之差

8. }else { //L2表长较长

9. longList=L2->next ; shortList=L1->next ;

10. dist=len2 - lenl; //表长之差

11. }

12. while (dist --) //表长的链表先遍历到第dist 个结点,然后同步

13. longList=longList ->next ;

14. while (longList !=NULL ) { //同步寻找共同结点

15. if (longList ==shortList ) //找到第一个公共结点

16. return longList;

17. else { //继续同步寻找

18. longList=longList ->next ;

19. shortList=shortList ->next ;

20. }

21. } //while

22. return NULL;

23. }

9. 解答:

算法思想:对链表进行遍历,在每趟遍历中查找出整个链表的最小值元素,输出并释放结点所占空间;再查找次小值元素,输出并释放空间,如此下去,直至链表为空,最后释放头结点所占存储空间,该算法的时间复杂度为O(n2) 。 1. void Min_Delete(LinkList &head ){

2. //head是带头结点的单键表的头指针,本算法按递增顺序输出单链表

3. while (head ->next !=NULL ) { //循环到仅剩头结点

4. pre=head ; //pre为元素最小值结点的前驱结点的指针

5. p=pre ->next ; //p为工作指针

6. while (p ->next !=NULL ) {

7. if (p ->next ->data

next ->data )8.                  pre=p ;   //记住当前最小值结点的前驱9.              p=p ->next ;10.        }11.        print (pre ->next ->data );   //输出元素最小值结点的数据12.        u=pre ->next ;   //删除元素值最小的结点,释放结点空间13.        pre->next =u ->next ;14.        free (u );15.    }//while16.    free  (head );   //释放头结点17. }若题设不限制数组辅助空间的使用,则可先将链表的数据复制在数组里,再釆用时间复杂度为O(nlog2n) 的排序算法进行排序,然后将数组元素输出,时间复杂度为O(nlog2n) 。10. 解答:算法思想:设置一个访问序号变量(初值为0),每访问一个结点序号自动加1,然后根据序号的奇偶性将结点插入到A 表或B 表中。重复以上操作直到表尾。 1.  LinkList DisCreat_1(LinkList &A ){2.      //将表A 中结点按序号的奇偶性分解到表A 或表B 中3.      i=0;   //i记录表A 中结点的序号4.      B= (LinkList )  malloc  (sizeof  (LNode )  );   //创建 B 表表头5.      B->next =NULL ;   //B 表的初始化6.      LNode *ra = A,  *rb =B ;   //ra和rb 将分别指向将创建的A 表和B 表的尾结点7.      p=A ->next ;   //p为链表工作指针,指向待分解的结点8.      A->next =NULL ;   //置空新的 A 表9.      while  (p !=NULL )  {10.        i++;  //序号加 111.        if  (i %2==0)  {  //处理序号为偶数的链表结点12.            rb->next =p ;   // 若B 表尾描入新结点13.            rb=p ;   //rb指向新的尾结点14.        }else {    //处理原序号为奇数的结点15.            ra->next =p ;   //在A 表尾插入新结点16.            ra=p ;17.        }18.        p=p ->next ;   //将p 恢复为指向新的待处理结点19.    }  //while 结束20.    ra->next =NULL ;21.    rb->next =NULL ;22.    return  B;23. }为了保持原来结点中的顺序,本题釆用尾插法建立单链表。此外,本算法完全可以不用设置序号变量。while 循环中的代码改为将结点插入到表A 中和将下一结点插入到表B 中,这样while 中第一处理的结点就是奇数号结点,第二处理的结点就是偶数号结点。11. 解答:算法思想:釆用上题的思路,不设序号变量。二者的差别仅在于对B ,表的建立不采用尾插法,而是釆用头插法。 1.  LinkList DisCreat_2(LinkList &A ){2.      LinkList B= (LinkList ) malloc (sizeof (LNode ))  ;   //创建B 表表头3.      B->next =NULL ;   //B 表的初始化4.      LNode *p =A ->next ,  *q ;   //p 为工作指针5.      LNode *ra =A ;   //ra始终指向A 的尾结点6.      while (p !=NULL ){7.          ra->next =p ;   ra=p ;   //将*p 链到 A 的表尾8.          p=p ->next ;9.          q=p ->next ;   //头插后,将断链,因此用q 记忆*p的后继10.        p->next =B ->next ;   //将插入到 B 的前端11.        B->next =p ;12.        p=q13.    }14.    ra->next =NULL ;   //A尾结点的next 域置空15.    return  B;16. }该算法特别需要注意的是,釆用头插法插入结点后,*p的指针域已改变,如果不设变量保存其后继结点会引起断链,从而导致算法出错。12. 解答:算法思想:由于是有序表,所有相同值域的结点都是相邻的。用p 扫描递增单链表L ,若*p结点的值域等于其后继结点的值域,则删除后者,否则p 移向下一个结点。1.  void  Del_Same(LinkList &L )  {2.      //L是递增有序的单链表,本算法删除表中数值相同的元素3.      LNode *p =L ->next ,  *q ;     //p 为扫描工作指针4.      while  (p ->next !=NULL )  {5.          q=p ->next ;   //q指向 *p 的后继结点6.          if  (p ->data ==q ->data )  {  //找到重复值的结点7.              p->next =q ->next ;  //释放*q 结点8.              free (q );   //释放相同元素值的结点9.          }else10.            p = p->next ;11.    }12. }本算法的时间复杂度为O(n),空间复杂度为O(1)。13. 解答:算法思想:两个链表已经按元素值递增次序排序,将其合并时,均从第一个结点起进行比较,将小的结点链入链表中,同时后移工作指针。该问题要求结果链表按元素值递减次序排列,故新链表的建立应该釆用头插法。比较结束后,可能会有一个链表非空,此时用头插法将剩下的结点依次插入新链表中即可。 1.  void  MergeList (LinkList &La , LinkList &Lb )  {2.      //合并两个递增有序链表(带头结点) ,并使合并后的链表递减排列3.      LNode *r ,  *pa =La ->next ,  *pb =Lb ->next ;   //分别是表 La 和 Lb 的工作指针4.      La->next = NULL;   //La作为结果链表的头指针,先将结果链表初始化为空5.      while (pa &&pb )   //当两链表均不为空时,循环6.          if (pa ->data data ){7.              r=pa ->next ;   //r暂存pa 的后继结点指针8.              pa->next =La ->next ;9.              La->next =pa ;   //将pa 结点链于结果表中,同时逆置(头插法)10.            pa=r ;   //恢复pa 为当前待比较结点11.        }else {12.            r=pb ->next ;   //r暂存pb 的后继结点指针13.            pb->next =La —>next ;14.            La->next =pb ;   //将pb 结点链于结果表中,同时逆置(头插法)15.            pb=r ;   //恢复pb 为当前待比较结点16.        }17.        if (pa )18.            pb=pa ;   //通常情况下会剩一个链表非空,处理剩下的部分19.        while (pb ){  //处理剩下的一个非空链表20.            r=pb ->next ;   //依次插入到La 中(头插法)21.            pb->next = La->next ;22.            La->next = pb;23.            pb=r ;24.        }25.    free (Lb );26. }14. 解答:算法思想:尾插法建立单链表C, 由于算法要求不破坏A 、B 的结点,所以,需要通过比较复制的方式产生单链表C 。 1.  void  Get_Common (LinkList A,  linkList B)  {2.      //本算法产生单链表A 和B 的公共元素的单链表C3.      LNode *p =A ->next ,*q =B ->next ,*r ,*s ;4.      LinkList C=(LinkList ) malloc (sizeof (LNode ))  ;  //建立表 C5.      r=C ;   //r始终指向C 的尾结点6.      while  (p !=NULL && q!=NULL )  {    //循环跳出条件7.          if (p ->data data )8.              p=p ->next ;   //若A 的当前元素较小,后移指针9.          else  if (p ->data >q ->data )10.            q=q ->next ;   //若B 的当前元素较小,后移指针11.        else {  //找到公共元素结点12.            s=(LNode *)malloc (sizeof (LNode ));13.            s->data =p ->data ;   // 复制产生结点 * s14.            r->next =s ;  r=s ;   //将*s链接到C 上(尾插法)15.            p=p ->next ;   //表A 和B 继续向后扫描16.            q=q ->next ;17.        }18.    }19.    r->next =NULL ;   //置C 尾结点指针为空20. }15. 解答:算法思想:釆用归并的思想,设置两个工作指针pa 和pb ,对两个链表进行归并扫描,只有同时出现在两集合中的元素才链接到结果表中且仅保留一个,其他的结点全部释放。当一个链表遍历完毕后,释放另一个表中剩下的全部结点。 1.  LinkList Union (LinkList &la , LinkList &lb )  {2.      pa=la ->next ;   //设工作指针分别为pa 和pb3.      pb=lb ->next ;4.      pc=la ;   //结果表中当前合并结点的前驱指针5.      while (pa &&pb ){6.          if (pa ->data ==pb ->data )  {  //交集并入结果表中7.              pc->next =pa ;     //A中结点链接到结果表8.              pc=pa ;9.              pa=pa ->next ;10.            u=pb ;   //B中结点释放11.            pb=pb ->next ;12.            free (u );13.        }14.        else  if  (pa ->data data )  { //若A 中当前结点值小于B 中当前结点值15.            u=pa ;16.            pa=pa ->next ;   //后移指针17.            free (u )  ;  //释放A 中当前结点18.        }else {  //若B 中当前结点值小于A 中当前结点值19.            u=pb ;20.            pb=pb ->next ;   //后移指针21.            free  (u )  ;   //释放B 中当前结点22.        }23.    }  //while 结束24.    while (pa ){    //B已遍历完,A 未完25.        u=pa ;26.        pa=pa ->next ;27.        free  (u )  ;   //释放A 中剩余结点28.    }29.    while (pb ){  //A已遍历完,B 未完30.        u=pb ;31.        pb=pb ->next ;32.        free (u )  ;  //释放B 中剩余结点33.    } 34.    pc->next =NULL ;     //置结果链表尾指针为NULL35.    free  (lb )  ;     //释放B 表的头结点36.    return  la;37. }链表归并类型的题在各学校历年真题中出现的频率很高,故应扎实掌握此类问题的思想。该算法的时间复杂度为O(len1+len2),空间复杂度为O(1)。16. 解答:算法思想:因为两个整数序列已存入两个链表中,操作从两个链表的第一个结点开始,若对应数据相等,则后移指针;若对应数据不等,则A 链表从上次开始比较结点的后继开始,B 链表仍从第一个结点开始比较,直到B 链表到尾表示匹配成功。A 链表到尾而B 链表未到尾表示失败。操作中应记住A 链表每次的开始结点,以便下趟匹配时好从其后继开始。 1.  int  Pattern (LinkList A,  LinkList B){2.      //A和B 分别是数据域为整数的单链表,本算法判断B 是否是A 的子序列3.      LNode *p =A ;  //p为A 链表的工作指针,本题假定A 和B 均无头结点4.      LNode *pre = p;   //pre记住每趟比较中A 链表的开始结点5.      LNode *q =B ;   //q是B 链表的工作指针6.      while  (p &&q )7.          if  (p ->data ==q ->data )  { //结点值相同8.              p=p ->next ;9.              q=q ->next ;10.        }else {11.            pre=pre ->next ;12.            p=pre ;   //A链表新的开始比较结点13.            q=B ;   //q从B 链表第一个结点开始14.        }15.16.    if  (q ==NULL )   //B已经比较结束17.        return  1;   //说明B 是A 的子序列18.    else19.        return  0;   //B不是A 的子序列20. }17. 解答:算法思想:让p 从左向右扫描,q 从右向左扫描,直到它们指向同一结点(p==q,当循环双链表中结点个数为奇数时)或相邻(p->next=q或q->prior=p,当循环双链表中结点个数为偶数时)为止,若它们所指结点值相同,则继续进行下去,否则返回0。若比较全部相等,则返回1。 1.  int  Symmetry (DLinkList L){2.      //本算法从两头扫描循环双链表,以判断链表是否对称3.      DNode *p =L ->next ,  *q =L ->prior ;     //两头工作指针4.      while  (p !  =q &&q ->next !  =p )     //循环跳出条件 5.          if  (p ->data ==q ->data )  {    //所指结点值相同则继续比较6.              p=p ->next ;  q=q ->prior ;7.          }else   //否则,返回08.              return  0;9.      return  1;   //比较结束后返回110. }18. 解答:算法思想:先找到两个链表的尾指针,将第一个链表的尾指针与第二个链表的头结点链接起来,再使之成为循环的。1.  LinkList Link (LinkList &h1, LinkList &h2){2.      //将循环链表h2链接到循环链表h1之后,使之仍保持循环链表的形式3.      LNode *p ,  *q ;   //分别指向两个链表的尾结点4.      p=h1;5.      while  (p ->next !=h1)   //寻找 h1 的尾结点6.          p=p ->next ;7.      q=h2;8.      while  (q ->next !=h2)   //寻找 h2 的尾结点9.          q=q ->next ;10.    p->next =h2;     //将h2链接到h1之后11.    q->next =h1;     //令h2的尾结点指向h112.    return  h1;13. }19. 解答:对于循环单链表L ,在不空时循环:每循环一次查找一个最小结点(由minp 指向最小值结点,minpre 指向其前驱结点)并删除它。最后释放头结点。 1.  void  Del_All(LinkList &L ){2.      // 本算法实现每次删除循环单链表中的最小元素,直到链表空为止3.      LNode *p ,  *pre ,  *minp ,  *minpre ;4.      while (L ->next !=L ){  //表不空,循环5.          p=L ->next ;  pre=L ;   //p为工作指针,pre 指向其前驱6.          minp=p ;  minpre=pre ;   //minp 指向最小值结点7.          while (p !=L ){  //循环一趟,查找最小值结点8.              if (p ->data data ){9.                  minp=p ;   //找到值更小的结点10.                minpre=pre ;11.            }12.            pre=p ;   //查找下一个结点13.            p=p ->next ;14.        }15.        printf  ("%d",  minp->data );   //输出最小值结点元素16.        minpre->next =minp ->next ;   //最小值结点从表中“断”开17.        free (minp )  ;  //释放空间18.    }19.    free  (L )  ;   //释放头结点20. }20. 解答:此题主要考查双链表的查找、删除和插入算法。算法思想:首先在双向链表中查找数据值为X 的结点,查到后,将结点从链表上摘下,然后再顺着结点的前驱链查找该结点的插入位置。(频度递减,且排在同频度的第一个),并插入到该位置。 1.  DLinkList Locate (DLinkList &L , ElemType x){2.      //本算法先查找数据x, 查找成功时结点的访问频度域增13.      //最后将该结点按频度递减插入链表中适当位置(同频度最近访问的在前面)4.      DNode *p =L ->next ,*q ;   //p为工作指针,q 为p 的前驱,用于查找插入位置5.      while  (p &&p ->data !=x )6.          p = p->>next ;  //查找值为x 的结点7.      if (!p ){8.          printf  (" 不存在值为x 的结点\n");9.          exit (0);10.    }else  {11.        p->freq ++;    //令元素值为x 的结点的freq 域加112.        p->next ->pred =p ->pred ;13.        p->pred ->next =p ->next ;     //将p 结点从链表上摘下14.        q=p ->pred ;     //以下查找p 结点的插入位置15.        while  (q !=L && q->freq freq )16.            q=q ->pred ;17.        p->next =q ->next ;18.        q->next ->pred =p ;   //将p 结点插入,一定是排在同频率的第一个19.        p->pred =q ;20.        q->next =p ;21.    }22.    return  p ;   //返回值为x 的结点的指针23. }21. 解答:1) 算法的基本设计思想如下:问题的关键是设计一个尽可能高效的算法,通过链表的一趟遍历,找到倒数第k 个结点的位置。算法的基本设计思想是:定义两个指针变量p 和q, 初始时均指向头结点的下一个结点(链表的第一个结点),p 指针沿链表移动;当p 指针移动到第k 个结点时,q 指针开始与P 指针同步移动;当p 指针移动到最后一个结点时,q 指针所指示结点为倒数第k 个结点。以上过程对链表仅进行一遍扫描。2) 算法的详细实现步骤如下:①count=0,p 和q 指向链表表头结点的下一个结点。②若p 为空,转⑤。③若count 等于k, 则q 指向下一个结点;否则,count=count+1。④p 指向下一个结点,转②。⑤若count 等于k ,则查找成功,输出该结点的data 域的值,返回1; 否则,说明k 值超过了线性表的长度,查找失败,返回0。⑥算法结束。3) 算法实现 1.  typedef  int  ElemType;     //链表数据的类型定义2.  typedef  struct  LNode{    //链表结点的结构定义3.      ElemType data;     //结点数据4.      struct  Lnode *link ;     //结点链接指针5.  }LNode ,  *LinkList ;6.  int  Search_k(LinkList list, int  k)  {7.      //查找链表list 倒数第k 个结点,并输出该结点data 域的值8.      LNode *p = list->link ,  *q =list ->link ;   //指计;p 、q 指示第一个结点9.      int  count=0;10.    while  (p !=NULL )  {  //遍历链表直到最后一个结点11.        if  (count 12.        else  q=q ->link ;13.        p-p ->link ;   //之后让p 、q 同步移动14.    } //while 15.    if (count 16.        return  0;   //查找失败返回017.    else {    //否则打印并返回118.    printf ("%d",  q->data );19.    return  1;20.    } 21. } //Search_k注意:若所给出的算法采用一遍扫描方式就能得到正确结果,可给满分15分;若采用两遍或多遍扫描才能得到正确结果的,最高分为10分。若采用递归算法得到正确结果的,最高给10分;若实现算法的空间复杂度过高(使用了大小与k 有关的辅助数组),但结果正确,最高给10分。22. 解答:本题的结构体是单链表,釆用双指针法。用指针p 、q 分别扫描str1和str2,当p 、q 指向同一个地址时,即找到共同后缀的起始位置。1) 算法的基本设计思想如下:①分别求出str1和str2所指的两个链表的长度m 和n 。②将两个链表以表尾对齐:令指针p 、q 分别指向strl 和str2的头结点,若m>=n,则指针P 先走,使p 指向链表中的第m-n+1个结点;若m③反复将指针p 和q 同步向后移动,当p 、q 指向同一位置时停止,即为共同后缀的其实位置,算法结束。2) 本题代码如下:复制纯文本新窗口 1.  typedef  struct  Node{2.      char  data;3.      struct  Node *next ;4.  }SNode ;5.6.  /*求链表长度的函数*/7.  int  listlen (SNode *head ){8.      int  len=0;9.      while (head ->next !=NULL ){10.        len++;11.        head=head ->next ;12.    }13.    return  len;14. }15.16. /*找出共同后缀的起始地址*/17.SNode * find_addr(SNode *strl , SNode *str2){18.    int  m, n ;19.    SNode *p ,  *q ;20.    m=listlen  (strl )  ;   //求 strl 的长度21.    n=listlen  (str2)  ;   //求 str2 的长度22.    for  (p =str1; m >n ; m --)  //若m>n,使p 指向链表中的第m-n+1个结点23.        p=p ->next ;24.    for  (q =str2; m 25.        q=q ->next ;26.    while  (p ->next !=NULL && p->next !=q ->next )  {27.        //将指针 p 和 q 同步向后移动28.        p=p ->next ;29.        q=q ->next ;30.    }//while31.    return  p->next ;  //返回共同后缀的起始地址32. }
猜你想看
相关文章

Copyright © 2008 - 2022 版权所有 职场范文网

工业和信息化部 备案号:沪ICP备18009755号-3