第2章 线性表

一、基础知识题

2.1 描述以下三个概念的区别:头指针,头结点,首元结点(第一个元素结点)。

首元结点是指链表中存储线性表中第一个数据元素a1的结点。
头结点是为了操作方便,在链表的首元结点之前附设的一个结点,该结点的数据域中不存储线性表的数据元素,其作用是为了对链表进行操作时,可以对空表、非空表的情况以及对首元结点进行统一处理。
头指针是指向链表中第一个结点(或为头结点或为首元结点)的指针。若链表中附设头结点,则不管线性表是否为空表,头指针均不为空,否则表示空表的链表的头指针为空。
这三个概念对单链表、双向链表和循环链表均适用。是否设置头结点,是不同的存储结构表示同一逻辑结构的问题。

2.2 填空题

(1)在顺序表中插入或删除一个元素,需要平均移动 表中一半 元素,具体移动的元素个数与 该元素的位置 有关。
(2)顺序表中逻辑上相邻的元素的物理位置 必定 相邻。单链表中逻辑上相邻的元素在物理位置 不一定 相邻。
(3)在单链表中,除了首元结点外,任一结点的存储位置由 其直接前驱结点的链域的值 指示。
(4)在单链表中设置头结点的作用是 插入和删除首元素时不必进行特殊处理。

2.3 在什么情况下用顺序表比链表好?

当不需频繁在存储的元素间进行插入和删除操作时,用顺序表较好。

2.4 对以下单链表分别执行下列各程序段,并画出结果示意图。

第02章 线性表 - 图3

(1)

  1. Q=P->next;

第02章 线性表 - 图4

(2)

  1. L=P->next;

第02章 线性表 - 图5

(3)

  1. R->data=P->data;

第02章 线性表 - 图6

(4)

  1. R->data=P->next->data;

第02章 线性表 - 图7

(5)

  1. P->next->next->next->data=P->data;

第02章 线性表 - 图8

(6)

  1. T=P;
  2. while(T!=NULL)
  3. {
  4. T->data=T->data*2;
  5. T=T->next;
  6. }

第02章 线性表 - 图9

(7)

  1. T=P;
  2. while(T->next!=NULL)
  3. {
  4. T->data=T->data*2;
  5. T=T->next;
  6. }

第02章 线性表 - 图10

注:虽然原图中最后有省略号,但是在做此题时应将S结点视为链表的最后一个结点。
  因为从出题人的角度出发,该题与2.4.6题要形成对照。

2.5 画出执行下列各行语句后各指针及链表的示意图。

  1. L = (LinkList) malloc (sizeof(LNode));
  2. P = L;
  3. for(i=1; i<=4; i++)
  4. {
  5. P->next= (LinkList) malloc (sizeof(LNode));
  6. P = P->next;
  7. P->data = i*2-1;
  8. }
  9. P->next = NULL;
  10. for(i=4; i>=1; i--)
  11. Ins_LinkList(L, i+1, i*2);
  12. for(i=1; i<=3; i++)
  13. Del_LinkList(L, i);

第02章 线性表 - 图11
第02章 线性表 - 图12
第02章 线性表 - 图13
第02章 线性表 - 图14

2.6 已知L是无表头结点的单链表,且P结点既不是首元结点,也不是尾元结点,试从下列提供的答案中选择合适的语句序列。

a.在P结点后插入S结点的语句序列是 (4)(1)。
b.在P结点前插入S结点的语句序列是 (7)(11)(8)(4)(1)。
c.在表首插入S结点的语句序列是 (5)(12)。
d.在表尾插入S结点的语句序列是 (9)(1)(6)。

(1)P->next=S;
(2)P->next=P->next->next;
(3)P->next=S->next;
(4)S->next=P->next;
(5)S->next=L;
(6)S->next=NULL;
(7)Q=P;
(8)while(P->next!=Q)
P=P->next;
(9)while(P->next!=NULL)
P=P->next;
(10)P=Q;
(11)P=L;
(12)L=S;
(13)L=P;

2.7 已知L是带表头结点的非空单链表,且P结点既不是首元结点,也不是尾元结点,试从下列提供的答案中选择合适的语句序列。

a.删除P结点的直接后继结点的语句序列是 (11)(3)(14)。
b.删除P结点的直接前驱结点的语句序列是 (10)(12)(8)(11)(3)(14)。
c.删除P结点的语句序列是 (10)(12)(7)(3)(14)。
d.删除首元结点的语句序列是 (12)(11)(3)(14)。
e.删除尾元结点的语句序列是 (9)(11)(3)(14)。

(1)P=P->next;
(2)P->next=P;
(3)P->next=P->next->next;
(4)P=P->next->next;
(5)while(P!=NULL)
P=P->next;
(6)while(Q->next!=NULL)
{
P=Q;
Q=Q->next;
}
(7)while(P->next!=Q)
P=P->next;
(8)while(P->next->next!=Q)
P=P->next;
(9)while(P->next->next!=NULL)
P=P->next;
(10)Q=P;
(11)Q=P->next;
(12)P=L;
(13)L=L->next;
(14)free(Q);

2.8 已知P结点是某双向链表的中间结点,试从下列提供的答案中选择合适的语句序列。

a.在P结点后插入S结点的语句序列是 (7)(12)(3)(6)。
b.在P结点前插入S结点的语句序列是 (8)(4)(5)(13)。
c.删除P结点的直接后继结点的语句序列是 (15)(1)(11)(18)。
d.删除P结点的直接前驱结点的语句序列是 (16)(2)(10)(18)。
e.删除P结点的语句序列是 (14)(9)(17)。

(1)P->next=P->next->next;
(2)P->priou=P->priou->priou;
(3)P->next=S;
(4)P->priou=S;
(5)S->next=P;
(6)S->priou=P;
(7)S->next=P->next;
(8)S->priou=P-priou;
(9)P->priou->next=P->next;
(10)P->priou->next=P;
(11)P->next->priou=P;
(12)P->next->priou=S;
(13)P->priou->next=S;
(14)P->next->priou=P->priou;
(15)Q=P->next;
(16)Q=P->priou;
(17)free(P);
(18)free(Q);

2.9 简述下列算法的功能。

(1)

  1. Status A(LinkedList L) //L是无表头结点的单链表
  2. {
  3. if(L&&L->next)
  4. {
  5. Q=L;
  6. L=L->next;
  7. P=L;
  8. while(P->next)
  9. P=P->next;
  10. P->next=Q;
  11. Q->next=NULL;
  12. }
  13. return OK;
  14. }//A

(1)如果L的长度不小于2,则将首元结点删去并插入表尾。

(2)

  1. void BB(LNode *s, LNode *q)
  2. {
  3. p=s;
  4. while(p->next!=q)
  5. p=p->next;
  6. p->next=s;
  7. }//BB
  8. void AA(LNode *pa, LNode *pb)
  9. {//pa和pb分别指向单循环链表中的两个结点
  10. BB(pa, pb);
  11. BB(pb, pa);
  12. }//AA

(2)将单循环链表拆成两个单循环链表。

二、算法设计题

本章算法题目涉及的顺序表和线性链表的类型定义如下:
  1. #define LIST_INIT_SIZE 100
  2. #define LISTINCREMENT 10
  3. typedef struct
  4. {
  5. ElemType *elem; //存储空间基址
  6. int length; //当前长度
  7. int listsize; //当前分配的存储容量
  8. }SqList; //顺序表类型
  9. // 注:此文档中,ElemType被定义为int类型。
  10. typedef struct LNode
  11. {
  12. ElemType data;
  13. Struct Lnode *next;
  14. }LNode, *LinkList; //线性链表类型

2.10 指出以下算法的错误和低效(即费时)之处,并将它改写为一个既正确又高效的算法。

  1. Status DeleteK(SqList &a, int i, int k)
  2. { //本过程从顺序存储结构的线性表a中删除第i个元素起的k个元素
  3. if(i<1 || k<0 || i+k>a.length)
  4. return INFEASIBLE; //参数不合法
  5. else
  6. for(count=1; count<k; count++)
  7. { //删除一个元素
  8. for(j=a.length; j>=i+1; j--)
  9. a.elem[j-1] = a.elem[j];
  10. a.length--;
  11. }
  12. return OK;
  13. } //DeleteK

错误有两处:
(1)参数不合法的判别条件不完整。合法的入口参数条件为:(0(2)第二个for语句中,元素前移的次序错误。
低效之处是每次删除一个元素的策略。


2.11 设顺序表va中的数据元素递增有序。试写一算法,将x插入到顺序表的适当位置上,以保持该表的有序性。


2.12 设A=(a1,…,an)和B=(b1,…,bn)均为顺序表,A’和B’分别为A和B中除去最大共同前缀后的子表(例如,A=(x,y,y,z,x,z),B=(x,y,y,z,y,x,x,z),则两者中最大的共同前缀为(x,y,y,z),在两表中除去最大共同前缀后的子表分别为A’=(x,z)和B’=(y,x,x,z))。若A’=B’=空表,则A=B;若A’=空表,而B’≠空表,或者两者均不为空表,且A’的首元小于B’的首元,则AB。试写一个比较A,B大小的算法(请注意:在算法中,不要破坏原表A和B,并且,也不一定先求得A’和B’才进行比较)。


2.13 试写一算法在带头结点的单链表结构上实现线性表操作LOCATE(L,X)。


2.14 试写一算法在带头结点的单链表结构上实现线性表操作LENGTH(L)。


2.15 已知指针ha和hb分别指向两个单链表的头结点,并且已知两个链表的长度分别为m和n。试写一算法将这两个链表连接在一起(即令其中一个表的首元结点连在另一个表的最后一个结点之后),假设指针hc指向连接后的链表的头结点,并要求算法以尽可能短的时间完成连接运算。请分析你的算法和时间复杂度。


2.16 已知指针la和lb分别指向两个无头结点单链表中的首元结点。下列算法是从表la中删除自第i个元素起共len个元素后,将它们插入到表lb中的第j个元素之前。试问此算法是否正确?如有错,则请改正之。

  1. Status DeleteAndInsertSub (LinkedList la, LinkedList lb, int i, int j, int len)
  2. {
  3. if(i<0 || j<0 || len<0)
  4. return INFEASIBLE;
  5. p=la; k=1;
  6. while(k<i)
  7. {
  8. p=p->next;
  9. k++;
  10. }
  11. q=p;
  12. while(k<=len)
  13. {
  14. q=q->next;
  15. k++;
  16. }
  17. s=lb;
  18. k=1;
  19. while(k<j)
  20. {
  21. s=s->next;
  22. k++;
  23. }
  24. s->next=p;
  25. q->next=s->next;
  26. return OK;
  27. } //DeleteAndInsertSub

2.17 试写一算法,在无头结点的动态单链表上实现线性表操作INSERT(L, i, b),并和在带头结点的动态单链表上实现相同操作的算法进行比较。

2.18 同2.17题要求。试写一算法,实现线性表操作DELETE(L, i)。


2.19 已知线性表中的元素以值递增有序排列,并以单链表作存储结构。试写一高效的算法,删除表中所有值大于mink且小于maxk的元素(若表中存在这样的元素),同时释放被删结点空间,并分析你的算法的时间复杂度(注意:mink和maxk是给定的两个参变量,它们的值可以和表中的元素相同,也可以不同)。

时间复杂度分析:最坏的情况是全部扫描完也没找到适合的元素,故时间复杂度与链表长度有关,为O(Length(L))。

2.20 同2.19题条件(递增有序排列),试写一高效的算法,删除表中所有值相同的多余元素(使得操作后的线性表中所有元素的值均不相同),同时释放被删结点空间,并分析你的算法的时间复杂度。


2.21 试写一算法,实现顺序表的就地逆置,即利用原表的存储空间将线性表(a1, a2, …, an)逆置为(an, an-1, …, a1)。


2.22 试写一算法,对单链表实现就地逆置。


2.23 设线性表A=(a1, a2, …, am),B=(b1, b2, …, bn),试写一个按下列规则合并A,B为线性表C的算法,即使得

C=(a1, b1, …, am, bm, bm+1, …, bn) 当m<=n时;

或者 C=(a1, b1, …, an, bn, an+1, …, am) 当m>n时。

线性表A,B和C均以单链表作存储结构,且C表利用A表和B表中的结点空间构成。注意:单链表的长度值m和n均未显式存储。

关键词:单链表


2.24 假设有两个按元素值递增有序排列的线性表A和B,均以单链表作存储结构,请编写算法将A表和B表归并成一个按元素值递减有序(即非递增有序,允许表中含有值相同的元素)排列的线性表C,并要求利用原表(即A表和B表)的结点空间构造C表。

关键词:递增单链表


2.25 假设以两个元素依值递增有序排列的线性表A和B分别表示两个集合(即同一表中的元素值各不相同),现要求另辟空间构成一个线性表C,其元素为A和B中元素的交集,且表C中的元素也依值递增有序排列。试对顺序表编写求C的算法。

关键词:递增无重复顺序表


2.26 要求同2.25题。试对单链表编写求C的算法。

关键词:递增无重复单链表


2.27 对2.25题的条件作以下修改,对顺序表重新编写求得表C的算法。

(1)假设在同一表(A或B)中可能存在值相同的元素,但要求新生成的表C中的元素值各不相同;

(2)利用A表空间存放表C。

关键词:递增有重复顺序表


2.28 对2.25题的条件作以下两点修改,对单链表重新编写求得表C的算法。

(1)假设在同一表(A或B)中可能存在值相同的元素,但要求新生成的表C中的元素值各不相同。

(2)利用原表(A表或B表)中的结点构造表C,并释放A表中的无用结点空间。

关键词:递增有重复单链表,释放无效结点


2.29 已知A,B和C为三个递增有序的线性表,现要求对A表作如下操作:删去那些既在B表中出现,又在C表中出现的元素。试对顺序表编写实现上述操作的算法,并分析你的算法的时间复杂度(注意:同一表中各元素值可能相同)。


2.30 要求同2.29题。试对单链表编写算法,请释放A表中的无用结点空间。


2.31 假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点的指针,试编写算法在链表中删除指针s所指结点的前驱结点。


2.32 已知有一个单向循环链表,其每个结点中含三个域:pre,data和next,其中data为数据域,next为指向后继结点的指针域,pre也为指针域,但它的值为空(NULL),试编写算法将此单向循环链表改为双向循环链表,即使pre称为指向前驱结点的指针域。


2.33 已知由一个线性链表表示的线性表中含有三类字符的数据元素(如:字母字符、数字字符和其他字符),试编写算法将该线性链表分割为三个循环链表,其中每个循环链表表示的线性表中均只含一类字符。


在2.34至2.36题中,“异或指针双向链表”类型XorLinkedList和指针异或函数XorP定义为:
  1. typedef struct XorNode
  2. {
  3. char data;
  4. struct XorNode LRPtr;
  5. } XorNode, *XorPointer;
  6. //无头结点的异或指针双向链表
  7. typedef struct
  8. {
  9. XorPointer Left, Right; //分别指向链表的左端和右端
  10. } XorLinkedList;
  11. XorPointer XorP(XorPointer p, XorPointer q); //指针异或函数XorP返回指针p和q的异或(XOR)值

异或表

第02章 线性表 - 图15

异或指针链表的动态创建过程

第02章 线性表 - 图16
第02章 线性表 - 图17
第02章 线性表 - 图18
第02章 线性表 - 图19
第02章 线性表 - 图20
第02章 线性表 - 图21

2.34 假设在算法描述语言中引入指针的二元运算“异或”(用“⊕”表示),若a和b为指针,则a⊕b的运算结果仍为原指针类型,且:

a⊕(a⊕b)=(a⊕a)⊕b=b

(a⊕b)⊕b=a⊕(b⊕b)=a

则可利用一个指针域来实现双向链表L。链表L中的每个结点只含两个域:data域和LRPtr域,其中LRPtr域存放该结点的左邻与右邻结点指针(不存在时为NULL)的异或。若设指针L.Left指向链表中的最左结点,L.Right指向链表中的最右结点,则可实现从左向右或从右向左遍历此双向链表的操作。试写一算法按任一方向依次输出链表中各元素的值。

2.35 采用2.34题所述的存储结构,写出在第i个结点之前插入一个结点的算法。

2.36 采用2.34题所述的存储结构,写出删除第i个结点的算法。


2.37 设以带头结点的双向循环链表表示的线性表L=(a1, a2, …, an),试写一时间复杂度为O(n)的算法,将L改造为L=(a1, a3, …, an, …, a4, a2)。


2.38 设有一个双向循环链表,每个结点中除有pre,data和next三个域外,还增设了一个访问频度域freq。在链表被起用之前,频度域freq的值均初始化为零,而每当对链表进行一次LOCATE(L, x)的操作后,被访问的结点(即元素值等于x的结点)中的频度域freq的值便增1,同时调整链表中结点之间的次序,使其按访问频度非递增的次序顺序排列,以便始终保持被频繁访问的结点总是靠近表头结点,试编写符合上述要求的LOCATE操作的算法。


在2.39至2.40题中,稀疏多项式采用的顺序存储结构SqPoly定义为
  1. typedef struct
  2. {
  3. int coef;
  4. int exp;
  5. } PolyTerm;
  6. typedef struct
  7. { //多项式的顺序存储结构
  8. PolyTerm *data;
  9. int last;
  10. } SqPoly;

2.39 已知稀疏多项式:第02章 线性表 - 图22,其中n=em>em-1>…>e1≥0,ci≠0(i=1,2,…,m),m≥1。试采用存储量同多项式项数m成正比的顺序存储结构,编写求Pn(x0)的算法(x0为给定值),并分析你的算法的时间复杂度。

时间复杂度为O(n),只与顺序表长度有关。

2.40 采用2.39题给定的条件和存储结构,编写求第02章 线性表 - 图23的算法,将结果多项式存放在新辟的空间中,并分析你的算法的时间复杂度。


在2.41至2.42题中,稀疏多项式采用的循环链表存储结构LinkedPoly定义为
  1. typedef struct PolyNode
  2. {
  3. PolyTerm data;
  4. struct PolyNode *next;
  5. } PolyNode, *PolyLink;
  6. typedef PolyLink LinkedPoly;

2.41 试以循环链表作稀疏多项式的存储结构,编写求其导函数的算法,要求利用原多项式中的结点空间存放其导函数(多项式),同时释放所有无用(被删)结点。

2.42 试编写算法,将一个用循环链表表示的稀疏多项式分解成两个多项式,使这两个多项式中各自仅含奇次项或偶次项,并要求利用原链表中的结点空间构成这两个链表。