第2章 线性表
一.选择题
1.A | 2.B | 3.C | 4.A | 5.D | 6.D | 7.D | 8.C | 9.B | 10.B,C | 11.1I | 11.2I | 11.3E |
11.4B | 11.5C | 12.B | 13.C | 14.C | 15.C |
| 16.A | 17.A | 18.A | 19.D | 20.C | 21.B |
22.D | 23.C | 24.B | 25.B | 26.A | 27.D |
|
|
|
|
|
|
|
二.判断题
1. × | 2.√ | 3. √ | 4.× | 5.× | 6. × | 7. × | 8.× | 9.× | 10.× | 11.× | 12.× |
13. × | 14. √ | 15.× | 16. √ |
|
|
|
|
|
|
|
|
部分答案解释如下。
1、 头结点并不“仅起”标识作用,并且使操作统一。另外,头结点数据域可写入链表长度,或作监视哨。
4.两种存储结构各有优缺点,应根据实际情况选用,不能笼统说哪一个好。
7.集合中元素无逻辑关系。
9.非空线性表第一个元素无前驱,最后一个元素无后继。
13.线性表是逻辑结构,可以顺序存储,也可链式存储。
三.填空题
1.顺序 2.(n-1)/2 3.py->next=px->next; px->next=py
4 .n-i+1
5.主要是使插入和删除等操作统一,在第一个元素之前插入元素和删除第一个结点不必另作判断。另外,不论链表是否为空,链表指针不变。
6.O(1),O(n) 7.单链表,多重链表,(动态)链表,静态链表
8.f->next=p->next; f->prior=p; p->next->prior=f; p->next=f;
9.p^.prior s^.prior^.next
10. 指针 11.物理上相邻 指针 12.4 2
13.从任一结点出发都可访问到链表中每一个元素。
14.u=p->next; p->next=u->next; free(u); 15.L->next->next==L 16.p->next!=null
17.L->next==L && L->prior==L 18.s->next=p->next;p->next=s;
19.(1) IF pa=NIL THEN return(true);
(2) pb<>NIL AND pa^.data>=pb^.data
(3) return(inclusion(pa,pb));
(4) pb:=pb^.next;
(5) return(false);
非递归算法:
(1)pre:=pb; (2) pa<>NIL AND pb<>NIL AND pb^.data>=pa^.data (3)pa:=pa^.next; pb:=pb->next;
(4)pb:=pre^.next;pre:=pb;pa:=pa^.next;(5)IF pa=NIL THEN return(true) ELSE return(false);
[注]:本题是在链表上求模式匹配问题。非递归算法中用指针pre指向主串中开始结点(初始时为第一元素结点)。若主串与子串对应数据相等,两串工作指针pa和pb后移;否则,主串工作指针从pre的下一结点开始(这时pre又指向新的开始结点),子串工作指针从子串第一元素开始,比较一直继续到循环条件失败。若pa为空,则匹配成功,返回true,否则,返回false。
20.A.VAR head:ptr B. new(p) C. p^.data:=k D. q^.next:=p E. q:=p(带头结点)
21.(1)new(h);∥生成头结点,以便于操作。
(2)r^.next:=p; (3) r^.next:=q; (4) IF (q=NIL) THEN r^.next:=p;
22.A: r^.link^.data<>max AND q^.link^.data<>max
B: r:=r^.link C: q^.link D: q^.link E: r^.link F: r^.link
G: r:=s(或r:= r^.link) H: r:=r^.link I: q^.link:=s^.link
23.(1)la (2)0 (3)j<i-1 (4)p↑.next (5)i<1
24.(1)head^.left:=s ∥head的前驱指针指向插入结点
(2)j:=1;
(3)p:=p^.right ∥工作指针后移
(4)s^.left:=p
(5)p^.right^.left:=s; ∥p后继的前驱是s
(6)s^.left:=p;
25.(1)i<=L.last ∥L.last 为元素个数
(2)j:=j+1 ∥有值不相等的元素
(3)L.elem[j]:=L.elem[i] ∥元素前移
(4)L.last:=j ∥元素个数
26.(A)p^.link:=q;∥拉上链,前驱指向后继
(B)p:=q;∥新的前驱
(C)p^.link:=head;∥形成循环链表
(D)j:=0; ∥计数器,记被删结点
(E)q:=p^.link∥记下被删结点
(F)p^.link=q^.link ∥删除结点
27. (1)p:=r;∥r指向工作指针s的前驱,p指向最小值的前驱。
(2)q:=s;∥q指向最小值结点,s是工作指针
(3)s:=s^.link∥工作指针后移
(4)head:=head^.next;∥第一个结点值最小;
(5)p^link:=q^.link;∥跨过被删结点(即删除一结点)
28.(1) l^.key:=x;∥头结点l这时起监视哨作用
(2) l^.freq:=p^.freq ∥头结点起监视哨作用
(3) q->pre->next=q->next; q->next->pre=q->pre; ∥先将q结点从链表上摘下
q^.next:=p; q^.pre:=p^.pre; p^.pre->next:=q; p^.pre:=q; ∥结点q插入结点p前
(4) q^.freq=0 ∥链表中无值为x的结点,将新建结点插入到链表最后(头结点前)。
29.(1)a^.key:=’@’∥a的头结点用作监视哨,取不同于a链表中其它数据域的值
(2)b^.key:=p^.key∥b的头结点起监视哨作用
(3)p:=p^.next∥找到a,b表中共同字母,a表指针后移
(4)0(m*n)
30. C 部分:(1)p!=null ∥链表未到尾就一直作
(2)q ∥将当前结点作为头结点后的第一元素结点插入
31. (1)L=L->next; ∥暂存后继
(2)q=L; ∥待逆置结点
(3)L=p; ∥头指针仍为L
32. (1) p^.next<>p0 (2)r:= p^.next (3) p^.next:= q0;
(4) q0:= p; (5) p:=r
33. (1)r (2)NIL (3)x<head^.data (4)p^.data<x
(5)p:=p^.next (6)p^.data>=x; (7)r (8)p
(9)r (10)NIL (11)NIL
34.(1)pa!=ha ∥或pa->exp!=-1
(2)pa->exp==0 ∥若指数为0,即本项为常数项
(3)q->next=pa->next ∥删常数项
(4)q->next ∥取下一元素
(5)=pa->coef*pa->exp
(6)-- ∥指数项减1
(7)pa ∥前驱后移,或q->next
(8)pa->next ∥取下一元素
35.(1)q:=p; ∥q是工作指针p的前驱
(2)p^.data>m ∥p是工作指针
(3)r:=q; ∥r 记最大值的前驱,
(4)q:=p; ∥或q:=q^.next;
(5)r^.next:=q^.next; ∥或r^.next:=r^.next^.next 删最大值结点
36.(1)L->next=null ∥置空链表,然后将原链表结点逐个插入到有序表中
(2)p!=null ∥当链表尚未到尾,p为工作指针
(3)q!=null ∥查p结点在链表中的插入位置,这时q是工作指针。
(4)p->next=r->next ∥将p结点链入链表中
(5)r->next=p ∥r是q的前驱,u是下个待插入结点的指针。
37.程序(a) PASCAL部分(编者略)
程序(b) C部分
(1)(A!=null && B!=null) ∥两均未空时循环
(2)A->element==B->element ∥两表中相等元素不作结果元素
(3)B=B->link ∥向后移动B表指针
(4)A!=null ∥将A 表剩余部分放入结果表中
(5)last->link=null ∥置链表尾
四、 应用题
1.(1)选链式存储结构。它可动态申请内存空间,不受表长度(即表中元素个数)的影响,插入、删
除时间复杂度为O(1)。
(2)选顺序存储结构。顺序表可以随机存取,时间复杂度为O(1)。
2.链式存储结构一般说克服了顺序存储结构的三个弱点。首先,插入、删除不需移动元素,只修改指针,时间复杂度为O(1);其次,不需要预先分配空间,可根据需要动态申请空间;其三,表容量只受可用内存空间的限制。其缺点是因为指针增加了空间开销,当空间不允许时,就不能克服顺序存储的缺点。
3.采用链式存储结构,它根据实际需要申请内存空间,而当不需要时又可将不用结点空间返还给系统。在链式存储结构中插入和删除操作不需要移动元素。
4.线性表 栈 队列 串 顺序存储结构和链式存储结构。
顺序存储结构的定义是:
CONST maxlen=线性表可能达到的最大长度;
TYPE sqlisttp=RECORD
elem:ARRAY[1..maxlen] OF ElemType;
last:0..maxlen;
END;
链式存储结构的定义是:
TYPE pointer=↑nodetype;
nodetype=RECORD
data:ElemType;
next:pointer;
END;
linklisttp=pointer;
5.顺序映射时,ai与ai+1的物理位置相邻;链表表示时ai与ai+1的物理位置不要求相邻。
6.在线性表的链式存储结构中,头指针指链表的指针,若链表有头结点则是链表的头结点的指针,头指针具有标识作用,故常用头指针冠以链表的名字。头结点是为了操作的统一、方便而设立的,放在第一元素结点之前,其数据域一般无意义(当然有些情况下也可存放链表的长度、用做监视哨等等),有头结点后,对在第一元素结点前插入结点和删除第一结点,其操作与对其它结点的操作统一了。而且无论链表是否为空,头指针均不为空。首元结点也就是第一元素结点,它是头结点后边的第一个结点。
7.见上题6。
8.(1)将next域变为两个域: pre和next,其值域均为0..maxsize。初始化时,头结点(下标为0的元素)其next域值为1,其pre域值为n(设n是元素个数,且n<maxsize)
(2) stalist[stalist[p].pre].pre;
(3) stalist[p].next;
9. 在单链表中不能从当前结点(若当前结点不是第一结点)出发访问到任何一个结点,链表只能从头指针开始,访问到链表中每个结点。在双链表中求前驱和后继都容易,从当前结点向前到第一结点,向后到最后结点,可以访问到任何一个结点。
10.本题是链表的逆置问题。设该链表带头结点,将头结点摘下,并将其指针域置空。然后从第一元素结点开始,直到最后一个结点为止,依次前插入头结点的后面,则实现了链表的逆置。
11.该算法的功能是判断链表L是否是非递减有序,若是则返回“true”;否则返回“false“。pre指向当前结点,p指向pre的后继。
12.q=p->next; p->next=q->next; free(q);
13. 设单链表的头结点的头指针为head,且pre=head;
while(pre->next!=p) pre=pre->next;
s->next=p; pre->next=s;
14.设单链表带头结点,工作指针p初始化为p=H->next;
(1) while(p!=null && p->data!=X) p=p->next;
if(p= =null) return(null);∥查找失败
else return(p);∥查找成功
(2) while(p!=null && p->data<X ) p=p->next;
if(p==null || p->data>X) return(null);∥查找失败
else return(p);
(3) while(p!=null && p->data>X) p=p->next;
if(p==null || p->data<X) return(null); ∥查找失败
else return(p); ∥查找成功
15.本程序段功能是将pa和pb链表中的值相同的结点保留在pa链表中(pa中与pb中不同结点删除),pa是结果链表的头指针。链表中结点值与从前逆序。S1记结果链表中结点个数(即pa与pb中相等的元素个数)。S2记原pa链表中删除的结点个数。
16.设 q:=p^.llink; 则
q^.rlink:=p^.rlink; p^.rlink^.llink:=q; p^.llink:=q^.llink;
q^.llink^.rlink:=p; p^.rlink:=q; q^.llink:=p
17.(1)前两个语句改为:
p.llink^.rlink<- p^.rlink;
p^.rlink^.llink<- p^.llink;
(2)后三个语句序列应改为:
q^.rlink<- p^.rlink;∥以下三句的顺序不能变
p^.rlink^.llink<- q;
p^.rlink<- q;
18.mp是一个过程,其内嵌套有过程subp。
subp(s,q)的作用是构造从s到q的循环链表。
subp(pa,pb)调用结果是将pa到pb的前驱构造为循环链表。
subp(pb,pa)调用结果是将pb到pa的前驱(指在L链表中,并非刚构造的pa循环链表中)构造为循环链表。
总之,两次调用将L循环链表分解为两个。第一个循环链表包含从pa到pb的前驱,L中除刚构造的pa到pb前驱外的结点形成第二个循环链表。
19.在指针p所指结点前插入结点s的语句如下:
s->pre=p->pre; s->next=p; p->pre->next=s; p->pre=s;
20.(A) f1<>NIL并且f2<>NIL
(B) f1↑.data < f2↑.data
(C) f2↑.data<f1↑.data
(D) f3↑.data<f1↑.data
(E) f1<- f1↑.link 或f2=f2↑.link;
21. 1)本算法功能是将双向循环链表结点的数据域按值自小到大排序,成为非递减(可能包括数据域值相等的结点)有序双向循环链表。
2)(1)r->prior=q->prior;∥将q结点摘下,以便插入到适当位置。
(2)p->next->prior=q;∥(2)(3)将q结点插入
(3)p->next=q;
(4)r=r->next;或r=q->next;∥后移指针,再将新结点插入到适当位置。
五、 算法设计题
1.[题目分析]因为两链表已按元素值递增次序排列,将其合并时,均从第一个结点起进行比较,将小的链入链表中,同时后移链表工作指针。该问题要求结果链表按元素值递减次序排列。故在合并的同时,将链表结点逆置。
LinkedList Union(LinkedList la,lb)
∥la,lb分别是带头结点的两个单链表的头指针,链表中的元素值按递增序排列,本算法将两链表合并成一个按元素值递减次序排列的单链表。
{ pa=la->next; pb=lb->next;∥pa,pb分别是链表la和lb的工作指针
la->next=null; ∥la作结果链表的头指针,先将结果链表初始化为空。
while(pa!=null && pb!=null) ∥当两链表均不为空时作
if(pa->data<=pb->data)
{ r=pa->next; ∥将pa 的后继结点暂存于r。
pa->next=la->next; ∥将pa结点链于结果表中,同时逆置。
la->next=pa;
pa=r; ∥恢复pa为当前待比较结点。
}
else
{r=pb->next;∥ 将pb 的后继结点暂存于r。
pb->next=la->next; ∥将pb结点链于结果表中,同时逆置。
la->next=pb;
pb=r; ∥恢复pb为当前待比较结点。
}
while(pa!=null) ∥将la表的剩余部分链入结果表,并逆置。
{r=pa->next; pa->next=la->next; la->next=pa; pa=r; }
while(pb!=null)
{r=pb->next; pb->next=la->next; la->next=pb; pb=r; }
}∥算法Union结束。
[算法讨论]上面两链表均不为空的表达式也可简写为while(pa&&pb),两递增有序表合并成递减有序表时,上述算法是边合并边逆置。也可先合并完,再作链表逆置。后者不如前者优化。算法中最后两个while语句,不可能执行两个,只能二者取一,即哪个表尚未到尾,就将其逆置到结果表中,即将剩余结点依次前插入到结果表的头结点后面。
与本题类似的其它题解答如下:
(1)[问题分析]与上题类似,不同之处在于:一是链表无头结点,为处理方便,给加上头结点,处理结束再删除之;二是数据相同的结点,不合并到结果链表中;三是hb链表不能被破坏,即将hb的结点合并到结果链表时,要生成新结点。
LinkedList Union(LinkedList ha, hb)
∥ha和hb是两个无头结点的数据域值递增有序的单链表,本算法将hb中并不出现在ha中的数据合并到ha中,合并中不能破坏hb链表。
{LinkedList la;
la=(LinkedList)malloc(sizeof(LNode));
la->next=ha;∥申请头结点,以便操作。
pa=ha; ∥pa是ha链表的工作指针
pb=hb; ∥pb是hb链表的工作指针
pre=la; ∥pre指向当前待合并结点的前驱。
while(pa&&pb)
if(pa->data<pb->data)∥处理ha中数据
{pre->next=pa;pre=pa;pa=pa->next;}
else if(pa->data>pb->data)∥处理hb中数据。
{r=(LinkedList)malloc(sizeof(LNode));∥申请空间
r->data=pb->data; pre->next=r;
pre=r;∥将新结点链入结果链表。
pb=pb->next;∥hb链表中工作指针后移。
}
else∥处理pa->data=pb->data;
{pre->next=pa; pre=pa;
pa=pa->next;∥两结点数据相等时,只将ha的数据链入。
pb=pb->next;∥不要hb的相等数据
}
if(pa!=null)pre->next=pa;∥将两链表中剩余部分链入结果链表。
else pre->next=pb;
free(la);∥释放头结点.ha,hb指针未被破坏。
}∥算法nion结束。
(2)本题与上面两题类似,要求结果指针为lc,其核心语句段如下:
pa=la->next;pb=hb->next;
lc=(LinkedList )malloc(sizeof(LNode));
pc=lc;∥pc是结果链表中当前结点的前驱
while(pa&&pb)
if(pa->data<pb->data)
{pc->next=pa;pc=pa;pa=pa->next;}
else {pc->next=pb;pc=pb;pb=pb->next;}
if(pa)pc->next=pa; else pc->next=pb;
free(la);free(lb);∥释放原来两链表的头结点。
算法时间复杂度为O(m+n),其中m和n分别为链表la和lb的长度。
2.[题目分析]本组题有6个,本质上都是链表的合并操作,合并中有各种条件。与前组题不同的是,叙述上是用线性表代表集合,而操作则是求集合的并、交、差(A∪B,A∩B,A-B)等。
本题与上面1.(2)基本相同,不同之处1.(2)中链表是“非递减有序”,(可能包含相等元素),本题是元素“递增有序”(不准有相同元素)。因此两表中合并时,如有元素值相等元素,则应删掉一个。
LinkedList Union(LinkedList ha,hb)
∥线性表A和B代表两个集合,以链式存储结构存储,元素递增有序。ha和hb分别是其链表的头指针。本算法求A和B的并集A∪B,仍用线性表表示,结果链表元素也是递增有序。
{ pa=ha->next;pb=hb->next;∥设工作指针pa和pb。
pc=ha;∥pc为结果链表当前结点的前驱指针。
while(pa&&pb)
if(pa->data<pb->data)
{pc->next=pa;pc=pa;pa=pa->next;}
else if(pa->data>pb->data)
{pc->next=pb;pc=pb;pb=pb->next;}
else∥处理pa->data=pb->data.
{pc->next=pa;pc=pa;pa=pa->next;
u=pb;pb=pb->next;free(u);}
if(pa) pc->next=pa;∥ 若ha表未空,则链入结果表。
else pc->next=pb;∥若hb表未空,则链入结果表。
free(hb); ∥释放hb头结点
return(ha);
}∥算法Union结束。
与本题类似的其它几个题解答如下:
(1) 解答完全同上2。
(2) 本题是求交集,即只有同时出现在两集合中的元素才出现在结果表中。其核心语句段如下:
pa=la->next;pb=lb->next;∥设工作指针pa和pb;
pc=la;∥结果表中当前合并结点的前驱的指针。
while(pa&&pb)
if(pa->data==pb->data)∥交集并入结果表中。
{ pc->next=pa;pc=pa;pa=pa->next;
u=pb;pb=pb->next;free(u);}
else if(pa->data<pb->data) {u=pa;pa=pa->next;free(u);}
else {u=pb; pb=pb->next; free(u);}
while(pa){ u=pa; pa=pa->next; free(u);}∥ 释放结点空间
while(pb) {u=pb; pb=pb->next; free(u);}∥释放结点空间
pc->next=null;∥置链表尾标记。
free(lb); ∥注: 本算法中也可对B表不作释放空间的处理
(3)本题基本与(2)相同,但要求无重复元素,故在算法中,待合并结点数据要与其前驱比较,只有在与前驱数据不同时才并入链表。其核心语句段如下。
pa=L1->next;pb=L2->next;∥pa、pb是两链表的工作指针。
pc=L1;∥L1作结果链表的头指针。
while(pa&&pb)
if(pa->data<pb->data) {u=pa;pa=pa->next;free(u);}∥删除L1表多余元素
else if (pa->data>pb->data) pb=pb->next; ∥pb指针后移
else ∥处理交集元素
{if(pc==L1) {pc->next=pa;pc=pa;pa=pa->next;} ∥处理第一个相等的元素。
else if(pc->data==pa->data){ u=pa;pa=pa->next;free(u);} ∥重复元素不进入L1表。
else{ pc->next=pa;pc=pa;pa=pa->next;} ∥交集元素并入结果表。
} ∥while
while(pa) {u=pa;pa=pa->next;free(u);} ∥ 删L1表剩余元素
pc->next=null; ∥置结果链表尾。
注: 本算法中对L2表未作释放空间的处理。
(4) 本题与上面(3)算法相同,只是结果表要另辟空间。
(5) [题目分析]本题首先求B和C的交集,即求B和C中共有元素,再与A求并集,同时删除重复元素,以保持结果A递增。
LinkedList union(LinkedList A,B,C)
∥A,B和C均是带头结点的递增有序的单链表,本算法实现A= A∪(B∩C),使求解结构保持递增有序。
{pa=A->next;pb=B->next;pc=C->next;∥设置三个工作指针。
pre=A; ∥pre指向结果链表中当前待合并结点的前驱。
if(pa->data<pb->data||pa->data<pc->data)∥A中第一个元素为结果表的第一元素。
{pre->next=pa;pre=pa;pa=pa->next;}
else{while(pb&&pc) ∥找B表和C表中第一个公共元素。
if(pb->data<pc->data)pb=pb->next;
else if(pb->data>pc->data)pc=pc->next;
else break; ∥找到B表和C表的共同元素就退出while循环。
if(pb&&pc)∥ 因共同元素而非B表或C表空而退出上面while循环。
if(pa->data>pb->data)∥A表当前元素值大于B表和C表的公共元素,先将B表元素链入。
{pre->next=pb;pre=pb;pb=pb->next;pc=pc->next;}∥ B,C公共元素为结果表第一元素。
}∥结束了结果表中第一元素的确定
while(pa&&pb&&pc)
{while(pb&&pc)
if(pb->data<pc->data) pb=pb->next;
else if(pb->data>pc->data) pc=pc->next;
else break; ∥B表和C表有公共元素。
if(pb&&pc)
{while(pa&&pa->data<pb->data) ∥先将A中小于B,C公共元素部分链入。
{pre->next=pa;pre=pa;pa=pa->next;}
if(pre->data!=pb->data){pre->next=pb;pre=pb;pb=pb->next;pc=pc->next;}
else{pb=pb->next;pc=pc->next;}∥ 若A中已有B,C公共元素,则不再存入结果表。
}
}∥ while(pa&&pb&&pc)
if(pa) pre->next=pa; ∥当B,C无公共元素(即一个表已空),将A中剩余链入。
}∥算法Union结束
[算法讨论]本算法先找结果链表的第一个元素,这是因为题目要求结果表要递增有序(即删除重复元素)。这就要求当前待合并到结果表的元素要与其前驱比较。由于初始pre=A(头结点的头指针),这时的data域无意义,不能与后继比较元素大小,因此就需要确定第一个元素。当然,不要这样作,而直接进入下面循环也可以,但在链入结点时,必须先判断pre是否等于A,这占用了过多的时间。因此先将第一结点链入是可取的。
算法中的第二个问题是要求时间复杂度为O(|A|+|B|+|C|)。这就要求各个表的工作指针只能后移(即不能每次都从头指针开始查找)。本算法满足这一要求。
最后一个问题是,当B,C有一表为空(即B和C已无公共元素时),要将A的剩余部分链入结果表。
3.[题目分析]循环单链表L1和L2数据结点个数分别为m和n ,将二者合成一个循环单链表时,需要将一个循环链表的结点(从第一元素结点到最后一个结点)插入到另一循环链表的第一元素结点前即可。题目要求“用最快速度将两表合并“,因此应找结点个数少的链表查其尾结点。
LinkedList Union(LinkedList L1,L2;int m,n)
∥L1和L2分别是两循环单链表的头结点的指针,m和n分别是L1和L2的长度。
∥本算法用最快速度将L1和L2合并成一个循环单链表。
{if(m<0||n<0) {printf(“表长输入错误/n”);exit(0);}
if(m<n) ∥若m<n,则查L1循环单链表的最后一个结点。
{if(m==0)return(L2);∥L1为空表。
else{p=L1;
while(p->next!=L1) p=p->next;∥查最后一个元素结点。
p->next=L2->next;∥将L1循环单链表的元素结点插入到L2的第一元素结点前。
L2->next=L1->next;
free(L1);∥释放无用头结点。
}
}∥处理完m<n情况
else∥ 下面处理L2长度小于等于L1的情况
{if(n==0)return(L1);∥L2为空表。
else{p=L2;
while(p->next!=L2) p=p->next;∥查最后元素结点。
p->next=L1->next;∥将L2的元素结点插入到L1循环单链表的第一元素结点前。
L1->next=L2->next;
free(L2);∥释放无用头结点。
}
}∥算法结束。
类似本题叙述的其它题解答如下:
(1)[题目分析]本题将线性表la和lb连接,要求时间复杂度为O(1),且占用辅助空间尽量小。应该使用只设尾指针的单循环链表。
LinkedList Union(LinkedList la,lb)
∥la和lb是两个无头结点的循环单链表的尾指针,本算法将lb接在la后,成为一个单循环链表。
{ q=la->next; ∥q指向la的第一个元素结点。
la->next=lb->next; ∥将lb的最后元素结点接到lb的第一元素。
lb->next=q; ∥将lb指向la的第一元素结点,实现了lb接在la后。
return(lb); ∥返回结果单循环链表的尾指针lb。
}∥算法结束。
[算法讨论]若循环单链表带有头结点,则相应算法片段如下:
q=lb->next; ∥q指向lb的头结点;
lb->next=la->next; ∥lb的后继结点为la的头结点。
la->next=q->next; ∥la的后继结点为lb的第一元素结点。
free(q); ∥释放lb的头结点
return(lb); ∥返回结果单循环链表的尾指针lb。
(2)[题目分析]本题要求将单向链表ha和单向循环链表hb合并成一个单向链表,要求算法所需时间与链表长度无关,只有使用带尾指针的循环单链表,这样最容易找到链表的首、尾结点,将该结点序列插入到单向链表第一元素之前即可。
其核心算法片段如下(设两链表均有头结点)
q=hb->next; ∥单向循环链表的表头指针
hb->next=ha->next; ∥将循环单链表最后元素结点接在ha第一元素前。
ha->next=q->next; ∥将指向原单链表第一元素的指针指向循环单链表第一结点
free(q); ∥释放循环链表头结点。
若两链表均不带头结点,则算法片段如下:
q=hb->next; ∥q指向hb首元结点。
hb->next=ha; ∥hb尾结点的后继是ha第一元素结点。
ha=q; ∥头指针指向hb的首元结点。
4.[题目分析]顺序存储结构的线性表的插入,其时间复杂度为O(n),平均移动近一半的元素。线性表LA和LB合并时,若从第一个元素开始,一定会造成元素后移,这不符合本题“高效算法”的要求。另外,题中叙述“线性表空间足够大”也暗示出另外合并方式,即应从线性表的最后一个元素开始比较,大者放到最终位置上。设两线性表的长度各为m和n ,则结果表的最后一个元素应在m+n位置上。这样从后向前,直到第一个元素为止。
PROC Union (VAR LA:SeqList;LB:SeqList)
∥LA和LB是顺序存储的非递减有序线性表,本算法将LB合并到LA中,元素仍非递减有序。
m:=LA.last;n:=LB.last;∥m,n分别为线性表LA和LB的长度。
k:=m+n; ∥k为结果线性表的工作指针(下标)。
i:=m;j:=n; ∥i,j分别为线性表LA和LB的工作指针(下标)。
WHILE(i>0)AND(j>0)DO
IF LA.elem[i]>=LB.elem[j]
THEN[LA.elem[k]:=LA.elem[i];k:=k-1;i:=i-1;]
ELSE[LA.elem[k]:=LB.elem[j];k:=k-1;j:=j-1;]
WHILE(j>0) DO [LA.elem[k]:=LB.elem[j];k:=k-1;j:=j-1;]
LA.last:=m+n;
ENDP;
[算法讨论]算法中数据移动是主要操作。在最佳情况下(LB的最小元素大于LA的最大元素),仅将LB的n个元素移(拷贝)到LA中,时间复杂度为O(n),最差情况,LA的所有元素都要移动,时间复杂度为O(m+n)。因数据合并到LA中,所以在退出第一个WHILE循环后,只需要一个WHILE循环,处理LB中剩余元素。第二个循环只有在LB有剩余元素时才执行,而在LA有剩余元素时不执行。本算法利用了题目中“线性表空间足够大”的条件,“最大限度的避免移动元素”,是“一种高效算法”。
5.[题目分析]本题实质上是一个排序问题,要求“不得使用除该链表结点以外的任何链结点空间”。链表上的排序采用直接插入排序比较方便,即首先假定第一个结点有序,然后,从第二个结点开始,依次插入到前面有序链表中,最终达到整个链表有序。
LinkedList LinkListSort(LinkedList list)
∥list是不带头结点的线性链表,链表结点构造为data和link两个域,data是数据域,link是指针域。本算法将该链表按结点数据域的值的大小,从小到大重新链接。
{p=list->link; ∥p是工作指针,指向待排序的当前元素。
list->link=null;∥假定第一个元素有序,即链表中现只有一个结点。
while(p!=null)
{r=p->link; ∥r是p的后继。
q=list;
if(q->data>p->data)∥处理待排序结点p比第一个元素结点小的情况。
{p->link=list;
list=p;∥链表指针指向最小元素。
}
else∥查找元素值最小的结点。
{while(q->link!=null&&q->link->data<p->data)q=q->link;
p->link=q->link;∥将当前排序结点链入有序链表中。
q->link=p; }
p=r;∥p指向下个待排序结点。
}
}
[算法讨论]算法时间复杂度的分析与用顺序存储结构时的情况相同。但顺序存储结构将第i(i>1)个元素插入到前面第1至第i-1个元素的有序表时,是将第i个元素先与第i-1个元素比较。而在链表最佳情况均是和第一元素比较。两种存储结构下最佳和最差情况的比较次数相同,在链表情况下,不移动元素,而是修改结点指针。
另一说明是,本题中线性链表list不带头结点,而且要求“不得使用除该链表以外的任何链结点空间“,所以处理复杂,需要考虑当前结点元素值比有序链表第一结点的元素值还小的情况,这时要修改链表指针list。如果list是头结点的指针,则相应处理要简单些,其算法片段如下:
p=list->link;∥p指向第一元素结点。
list->link=null;∥有序链表初始化为空
while(p!=null)
{r=p->link;∥保存后继
q=list;
while(q->link!=null && q->link->data<p->data)q=q->link;
p->link=q->link;
q->link=p;
q=r;
}
6.[题目分析]本题明确指出单链表带头结点,其结点数据是正整数且不相同,要求利用直接插入原则把链表整理成递增有序链表。这就要求从第二结点开释,将各结点依次插入到有序链表中。
LinkedList LinkListInsertSort(LinkedList la)
∥la是带头结点的单链表,其数据域是正整数。本算法利用直接插入原则将链表整理成递增的有序链表。
{if(la->next!=null)∥链表不为空表。
{p=la->next->next;∥p指向第一结点的后继。
la->next->next=null;∥直接插入原则认为第一元素有序,然后从第二元素起依次插入。
while(p!=null)
{r=p->next;∥暂存p的后继。
q=la;
while(q->next!=null&&q->next->data<p->data)q=q->next;∥查找插入位置。
p->next=q->next;∥将p结点链入链表。
q->next=p;
p=r;
}
与本题有类似叙述的题的解答:
(1)本题也是链表排序问题,虽没象上题那样明确要求“利用直接插入的原则”来排序,仍可用上述算法求解,这里不再赘述。
7.[题目分析]本题要求将一个链表分解成两个链表,两个链表都要有序,两链表建立过程中不得使用NEW过程申请空间,这就是要利用原链表空间,随着原链表的分解,新建链表随之排序。
PROC discreat(VAR listhead,P,Q:linkedlist)
∥listhead是单链表的头指针,链表中每个结点由一个整数域DATA和指针域NEXT组成。本算法将链表listhead分解成奇数链表和偶数链表,分解由P和Q指向,且P和Q链表是有序的。
P:=NIL;Q:=NIL;∥P和Q链表初始化为空表。
s:=listhead;
WHILE(s<>NIL)DO
[r:=s^.NEXT; ∥暂存s的后继。
IF s^.DATA DIV 2=0 ∥处理偶数。
THEN IF P=NIL THEN[P:=s;P^.NEXT:=NIL;] ∥第一个偶数链结点。
ELSE[pre:=P;
IF pre^.DATA>s^.DATA THEN[s^.NEXT:=pre;P:=s;∥插入当前最小值结点修改头指针]
ELSE[WHILE pre^.NEXT<>NIL DO
IF pre^.NEXT^.DATA<s^.DATA THEN pre:=pre^.NEXT;∥查找插入位置。
s^.NEXT:=pre^.NEXT; ∥链入此结点。
pre^.NEXT:=s;
]
]
ELSE∥处理奇数链。
IF Q=NIL THEN[Q:=s;Q^.NEXT:=NIL;] ∥第一奇数链结点。
ELSE[pre:=Q;
IF pre^.DATA>s^.DATA THEN[s^.NEXT:=pre; Q:=s; ]∥修改头指针。
ELSE[WHILE pre^.NEXT<>NIL DO ∥查找插入位置。
IF pre^.NEXT^.DATA<s^.DATA THEN pre:=pre^.NEXT;
s^.NEXT:=pre^.NEXT;∥链入此结点。
pre^.NEXT:=s;
]
]∥结束奇数链结点
s:=r;∥s指向新的待排序结点。
]∥结束“WHILE(s<>NIL)DO”
ENDP;∥结束整个算法。
[算法讨论]由于算法要求“不得使用NEW过程申请空间,也没明确指出链表具有头结点,所以上述算法复杂些,它可能需要在第一个结点前插入新结点,即链表的头指针会发生变化。如有头结点,算法不必单独处理在第一个结点前插入结点情况,算法会规范统一,下面的(1)是处理带头结点的例子。算法中偶数链上结点是靠数据整除2等于0(DATA DIV 2=0)判断的。
类似本题的其它题解答如下:
(1)[题目分析]本题基本类似于上面第7题,不同之处有二。一是带头结点,二是分解后的两个链表,一个是数据值小于0,另一个是数据值大于0。由于没明确要求用类PASCAL书写算法,故用C书写如下。
void DisCreat1(LinkedList A)
∥A是带头结点的单链表,链表中结点的数据类型为整型。本算法将A分解成两个单链表B和C,B中结点的数据小于零,C中结点的数据大于零。
{B=A;
C=(LinkedList )malloc(sizeof(LNode));∥为C申请结点空间。
C->next=null ∥C初始化为空表。
p=A->next; ∥p为工作指针。
B->next=null; ∥B表初始化。
while(p!=null)
{r=p->next; ∥暂存p的后继。
if (p->data<0)∥小于0的放入B表。
{p->next=B->next; B->next=p; }∥将小于0的结点链入B表。
else {p->next=C->next; C->next=p; }
p=r;∥p指向新的待处理结点。
}
}∥算法结束。
[算法讨论]因为本题并未要求链表中结点的数据值有序,所以算法中采取最简单方式:将新结点前插到头结点后面(即第一元素之前)。
(2) 本题同上面第7题,除个别叙述不同外,本质上完全相同,故不再另作解答。
(3)[题目分析]本题中的链表有头结点,分解成表A和表B,均带头结点。分解后的A表含有原表中序号为奇数的元素,B表含有原A表中序号为偶数的元素。由于要求分解后两表中元素结点的相对顺序不变,故采用在链表尾插入比较方便,这使用一指向表尾的指针即可方便实现。
void DisCreat3(LinkedList A)
∥A是带头结点的单链表,本算法将其分解成两个带头结点的单链表,A表中含原表中序号为奇数
∥的结点,B表中含原表中序号为偶数的结点。链表中结点的相对顺序同原链表。
{i=0;∥i记链表中结点的序号。
B=(LinkedList)malloc(sizeof(LNode);∥创建B表表头。
B->next=null; ∥B表的初始化。
LinkedList ra,rb;∥ra和rb将分别指向将创建的A表和B表的尾结点。
ra=A;rb=B;
p=A->next; ∥p为链表工作指针,指向待分解的结点。
A->next=null; ∥置空新的A表
while(p!=null)
{r=p->next; ∥暂存p的后继。
i++;
if(i%2==0) ∥处理原序号为偶数的链表结点。
{p->next=rb->next;∥在B表尾插入新结点;
rb->next=p; rb=p;∥rb指向新的尾结点;
}
else∥处理原序号为奇数的结点。
{p->next=ra->next; ra->next=p; ra=p; }
p=r; ∥将p恢复为指向新的待处理结点。
}∥算法结束
8.[题目分析]题目要求重排n个元素且以顺序存储结构存储的线性表,使得所有值为负数的元素移到正数元素的前面。这可采用快速排序的思想来实现,只是提出暂存的第一个元素(枢轴)并不作为以后的比较标准,比较的标准是元素是否为负数。
int Rearrange(SeqList a; int n)
∥a是具有n个元素的线性表,以顺序存储结构存储,线性表的元素是整数。本算法重排线性表a,
∥使所有值为负数的元素移到所有值为正数的数的前面。
{i=0; j=n-1; ∥ i,j为工作指针(下标),初始指向线性表a的第1个和第n个元素。
t=a[0]; ∥暂存枢轴元素。
while(i<j)
{while(i<j && a[j]>=0) j--; ∥ 若当前元素为大于等于零,则指针前移。
if(i<j){a[i]=a[j];i++;} ∥ 将负数前移。
while(i<j &&a[i]<0)i++; ∥ 当前元素为负数时指针后移。
if(i<j) a[j--]=a[i]; ∥ 正数后移。
}
a[i]=t; ∥将原第一元素放到最终位置。
}
[算法讨论] 本算法时间复杂度为O(n)。算法只是按题目要求把正负数分开,如要求统计负数和大于等于零的个数,则最后以t来定。如t为负数,则0至i共i+1个负数,n-1-i个正数(包括零)。另外,题目并未提及零的问题,笔者将零放到正数一边。对此问题的扩充是若元素包含正数、负数和零,并要求按负数、零、正数的顺序重排线性表,统计负数、零、正数的个数。请读者利用上面解题思想自行解答。
类似本题的选了5 个题,其解答如下:
(1)与上面第8题不同的是,这里要求以an为参考元素,将线性表分成左右两部分。左半部分的元素都小于等于an,右半部分的元素都大于an,an位于分界位置上。其算法主要片段语句如下:
i=1;j=n;
t=a[n]; ∥暂存参考元素。
while(i<j)
{while(i<j && a[i]<=t) i++; ∥当前元素不大于参考元素时,指针i后移。
if(i<j) a[j--]=a[i]; ∥将大于参考元素的元素后移。
while(i<j && a[j]>t) j--; ∥当前元素大于参考元素时指针前移。
if(i<j) a[i++]=a[j]; ∥将小于参考元素的当前元素前移。
}
a[i]=t; ∥参考元素置于分界位置。
(2) [题目分析]本题要求将线性表A分成B和C两个表,表B和表C不另占空间,而是利用表A的空间,其算法与第8题相同。这里仅把表B和表C另设空间的算法解答如下:
void Rearrange2(int A[],B[],C[])
∥线性表A有n个整型元素,顺序存储。本算法将A拆成B和C 两个表,B中存放大于
∥等于零的元素,C中存放小于零的元素。
{i=0; ∥i,j,k是工作指针,分别指向A、B和C表的当前元素。
j=k=-1; ∥j,k初始化为-1。
while(i<n)
{if(A[i]<0) C[++k]=A[i++]; ∥将小于零的元素放入C表。
else B[++j]=A[i++]; ∥将大于零的元素放入B表。
[算法讨论]本题用一维数组存储线性表,结果线性表B和C中分别有j+1和k+1个元素。若采用教材中的线性表,则元素的表示作相应改变,例如A.elem[i],而最后B和C表应置上表的长度,如B.length=j和C.length=k。
(3) 本题与第8题本质上相同,第8题要求分开正数和负数,这里要求分开奇数和偶数,判别方式是a[i]%2==0,满足时为偶数,反之为奇数。
(4) 本题与第8题相同,只是叙述不同。
(5) 本题与第8题基本相同,不同之处在于这里的分界元素是整数19(链表中并不要求一定有19)。本题要求用标准pascal描述算法,如下所示。
TYPE arr=ARRAY[1..1000] OF integer;
VAR a:arr;
PROCEDURE Rearrange5(VAR a:arr);
∥a是n(设n=1000)个整数组成的线性表,用一维数组存储。本算法将n个元素中所有大于等于19的整数放在所有小于19的整数之后。
VAR i,j,t:integer;
BEGIN
i:=1;j:=n;t:=a[1] ;∥i,j指示顺序表的首尾元素的下标,t暂存分界元素
WHILE(i<j)DO
BEGIN
WHILE (i<j)AND(a[j]>=19) DO j:=j-1;
IF(i<j)THEN BEGIN A[i]:=A[j];i:=i+1 END;
WHILE (i<j)AND(a[i] <19) DO i:=i+1;
IF(i<j)THEN BEGIN A[j]:=A[i];j:=j-1 END;
END;
a[i]:=t;
END;
[算法讨论] 分界元素t放入a[i],而不论它的值如何。算法中只用了一个t中间变量,符合空间复杂度O(1)的要求。算法也满足时间复杂度O(n)的要求。
9.[题目分析] 本题要求在单链表中删除最小值结点。单链表中删除结点,为使结点删除后不出现“断链”,应知道被删结点的前驱。而“最小值结点”是在遍历整个链表后才能知道。所以算法应首先遍历链表,求得最小值结点及其前驱。遍历结束后再执行删除操作。
LinkedList Delete(LinkedList L)
∥L是带头结点的单链表,本算法删除其最小值结点。
{p=L->next; ∥p为工作指针。指向待处理的结点。假定链表非空。
pre=L; ∥pre指向最小值结点的前驱。
q=p; ∥q指向最小值结点,初始假定第一元素结点是最小值结点。
while(p->next!=null)
{if(p->next->data<q->data){pre=p;q=p->next;} ∥查最小值结点
p=p->next; ∥指针后移。
}
pre->next=q->next;∥从链表上删除最小值结点
free(q); ∥释放最小值结点空间
}∥结束算法delete。
[算法讨论] 算法中函数头是按本教材类C描述语言书写的。原题中void delete(linklist &L),是按C++的“引用”来写的,目的是实现变量的“传址”,克服了C语言函数传递只是“值传递”的缺点。
10.[题目分析] 本题要求将链表中数据域值最小的结点移到链表的最前面。首先要查找最小值结点。将其移到链表最前面,实质上是将该结点从链表上摘下(不是删除并回收空间),再插入到链表的最前面。
LinkedList delinsert(LinkedList list)
∥list是非空线性链表,链结点结构是(data,link),data是数据域,link是链域。
∥本算法将链表中数据域值最小的那个结点移到链表的最前面。
{p=list->link;∥p是链表的工作指针
pre=list; ∥pre指向链表中数据域最小值结点的前驱。
q=p; ∥q指向数据域最小值结点,初始假定是第一结点
while (p->link!=null)
{if(p->link->data<q->data){pre=p;q=p->link;} ∥找到新的最小值结点;
p=p->link;
}
if (q!=list->link) ∥若最小值是第一元素结点,则不需再操作
{pre->link=q->link; ∥将最小值结点从链表上摘下;
q->link= list->link;∥将q结点插到链表最前面。
list->link=q;
}
}∥算法结束
[算法讨论] 算法中假定list带有头结点,否则,插入操作变为q->link=list;list=q。
11.[题目分析] 知道双向循环链表中的一个结点,与前驱交换涉及到四个结点(p结点,前驱结点,前驱的前驱结点,后继结点)六条链。
void Exchange(LinkedList p)
∥p是双向循环链表中的一个结点,本算法将p所指结点与其前驱结点交换。
{q=p->llink;
q->llink->rlink=p; ∥p的前驱的前驱之后继为p
p->llink=q->llink; ∥p的前驱指向其前驱的前驱。
q->rlink=p->rlink; ∥p的前驱的后继为p的后继。
q->llink=p; ∥p与其前驱交换
p->rlink->llink=q; ∥p的后继的前驱指向原p的前驱
p->rlink=q; ∥p的后继指向其原来的前驱
}∥算法exchange结束。
12.[题目分析] 顺序存储的线性表递增有序,可以顺序查找,也可折半查找。题目要求“用最少的时间在表中查找数值为x的元素”,这里应使用折半查找方法。
void SearchExchangeInsert(ElemType a[];ElemType x)
∥a是具有n个元素的递增有序线性表,顺序存储。本算法在表中查找数值为x的元素,如查到则与其后继交换位置;如查不到,则插入表中,且使表仍递增有序。
{ low=0;high=n-1; ∥low和high指向线性表下界和上界的下标
while(low<=high)
{mid=(low+high)/2; ∥找中间位置
if(a[mid]==x) break; ∥找到x,退出while循环。
else if (a[mid] <x) low=mid+1;∥到中点mid的右半去查。
else high=mid-1; ∥到中点mid的左部去查。
}
if(a[mid]==x && mid!=n)∥ 若最后一个元素与x相等,则不存在与其后继交换的操作。
{t=a[mid];a[mid]=a[mid+1];a[mid+1]=t;} ∥ 数值x与其后继元素位置交换。
if(low>high) ∥查找失败,插入数据元素x
{for(i=n-1;i>high;i--) a[i+1]=a[i];∥后移元素。
a[i+1]=x;∥插入x。
} ∥结束插入
}∥结束本算法。
[算法讨论] 首先是线性表的描述。算法中使用一维数组a表示线性表,未使用包含数据元素的一维数组和指示线性表长度的结构体。若使用结构体,对元素的引用应使用a.elem[i]。另外元素类型就假定是ElemType,未指明具体类型。其次,C中一维数组下标从0开始,若说有n个元素的一维数组,其最后一个元素的下标应是n-1。第三,本算法可以写成三个函数,查找函数,交换后继函数与插入函数。写成三个函数显得逻辑清晰,易读。
13.[题目分析] 判断链表中数据是否中心对称,通常使用栈。将链表的前一半元素依次进栈。在处理链表的后一半元素时,当访问到链表的一个元素后,就从栈中弹出一个元素,两元素比较,若相等,则将链表中下一元素与栈中再弹出元素比较,直至链表到尾。这时若栈是空栈,则得出链表中心对称的结论;否则,当链表中一元素与栈中弹出元素不等时,结论为链表非中心对称,结束算法的执行。
int dc(LinkedList h,int n)
∥ h是带头结点的n个元素单链表,链表中结点的数据域是字符。本算法判断链表是否是中心对称。
{char s[]; int i=1;∥i记结点个数, s字符栈
p=h->next;∥p是链表的工作指针,指向待处理的当前元素。
for(i=1;i<=n/2;i++)∥ 链表前一半元素进栈。
{s[i]=p->data;p=p->next;}
i--; ∥恢复最后的i值
if(n%2==1)p=p->next;} ∥若n是奇数,后移过中心结点。
while(p!=null && s[i]==p->data){i--;p=p->next;} ∥测试是否中心对称。
if(p==null)return(1);∥链表中心对称
else return(0); ∥链表不中心对称
}∥算法结束。
[算法讨论] 算法中先将“链表的前一半”元素(字符)进栈。当n为偶数时,前一半和后一半的个数相同;当n为奇数时,链表中心结点字符不必比较,移动链表指针到下一字符开始比较。比较过程中遇到不相等时,立即退出while循环,不再进行比较。
14.[题目分析] 在单链表中删除自第i个元素起的共len个元素,应从第1个元素起开始计数,记到第i个时开始数len个,然后将第i-1个元素的后继指针指向第i+len个结点,实现了在A链表中删除自第i个起的len个结点。这时应继续查到A的尾结点,得到删除元素后的A链表。再查B链表的第j个元素,将A链表插入之。插入和删除中应注意前驱后继关系,不能使链表“断链”。另外,算法中应判断i,len和j的合法性。
LinkedList DelInsert(LinkedList heada,headb,int i,j,len)
∥heada和headb均是带头结点的单链表。本算法删除heada链表中自第i个元素起的共len个元素,然后将单链表heada插入到headb的第j个元素之前。
{if(i<1 || len<1 || j<1){printf(“参数错误/n”);exit(0);}∥参数错,退出算法。
p=heada;∥p为链表A的工作指针,初始化为A的头指针,查到第i个元素时,p指向第i-1个元素
k=0;∥计数
while(p!=null && k<i-1)∥查找第i个结点。
{k++;p=p->next;}
if(p==null){printf(“给的%d太大/n”,i);exit(0);} ∥i太大,退出算法
q=p->next;∥q为工作指针,初始指向A链表第一个被删结点。
k=0;
while(q!=null && k<len){k++;u=q,q=q->next;free(u);} ∥删除结点,后移指针。
if(k<len){printf(“给的%d太大/n”,len);exit(0);}
p->next=q;∥A链表删除了len个元素。
if (heada->next!=null) ∥heada->next=null说明链表中结点均已删除,无需往B表插入
{while(p->next!=null)p= p->next;∥找A的尾结点。
q=headb;∥q为链表B的工作指针。
k=0;∥计数
while(q!=null && k<j-1) ∥查找第j个结点。
{k++;q= q->next;} ∥查找成功时,q指向第j-1个结点
if(q==null){printf(“给的%d太大/n”,j);exit(0);}
p->next=q->next; ∥将A链表链入
q->next=heada->next; ∥A的第一元素结点链在B的第j-1个结点之后
}//if
free(heada);∥释放A表头结点。
}∥算法结束。
与本题类似的题的解答如下:
(1)本题与第14题基本相同,不同之处仅在于插入B链表第j个元素之前的,不是删除了len个元素的A链表,而是被删除的len个元素。按照上题,这len个元素结点中第一个结点的指针p->next,查找从第i个结点开始的第len个结点的算法修改为:
k=1;q=p->next; //q指向第一个被删除结点
while(q!=null && k<len) ∥查找成功时,q指向自i起的第len个结点。
{k++;q= q->next;}
if(k<len) {printf(“给的%d太大/n”,len);exit(0);}
15.[题目分析] 在递增有序的顺序表中插入一个元素x,首先应查找待插入元素的位置。因顺序表元素递增有序,采用折半查找法比顺序查找效率要高。查到插入位置后,从此位置直到线性表尾依次向后移动一个元素位置,之后将元素x插入即可。
void Insert(ElemType A[],int size, ElemType x)
∥ A是有size个元素空间目前仅有num(num<size)个元素的线性表。本算法将元素x插入到线性表中,并保持线性表的有序性。
{low=1;high=num; //题目要求下标从1开始
while(low<=high)∥对分查找元素x的插入位置。
{mid=(low+high)/2;
if(A[mid]==x){low=mid+1;break;}
else if(A[mid]>x)high=mid-1 ;else low=mid+1 ;
}
for(i=num;i>=low;i--) A[i+1]=A[i];∥元素后移。
A[i+1]=x; ∥将元素x插入。
}算法结束。
[算法讨论] 算法中当查找失败(即线性表中无元素x)时,变量low在变量high的右面(low=high+1)。移动元素从low开始,直到num为止。特别注意不能写成for(i=low;i<=num;i++)A[i+1]=A[i],这是一些学生容易犯的错误。另外,题中未说明若表中已有值为x的元素时不再插入,故安排在A[mid]= =x时,用low(=mid+1)记住位置,以便后面统一处理。查找算法时间复杂度为O(logn),而插入时的移动操作时间复杂度为O(n),若用顺序查找,则查找的时间复杂度亦为O(n)。
类似本题的其它题的解答:
(1)[题目分析] 本题与上面15题类似,不同之处是给出具体元素值,且让编写turbo pascal程序,程序如下:
PROGRAM example(input,output);
TYPE pointer=^node;
node=RECORD
data:integer;
next:pointer;
END;
VAR head,q:pointer;
PROCEDURE create(VAR la:pointer);
VAR x:integer;
p,q:pointer;
BEGIN
new(la);la^.next:=NIL;{建立头结点。}
read(x);q:=la;{q用以指向表尾。}
WHILE NOT EOF DO {建立链表}
BEGIN
new(p);p^.data:=x;p^.next:=q^.next;q^.next:=p;q:=p; read(x);
END;
END;
PROCEDURE insert(VAR la:pointer;s:pointer);
VAR p,q:pointer;found:boolean;
BEGIN
p:= la^.next;{p为工作指针。}
q:=la;{q为p的前驱指针。}
found:=false;
WHILE(p<>NIL)AND NOT found
IF(p^.data<x)THEN BEGIN q:=p;p:= p^.next; END
ELSE found:=true;
s^.next:=p;{将s结点插入链表}
q^.next:=s;
END;
BEGIN {main}
writeln(“请按顺序输入数据,建立链表”)
create(head);
writeln(“请输入插入数据”)
new(q);
readln(q^.data);
insert(head,q);
END.{程序结束}
[程序讨论] 在建立链表时,输入数据依次为12,13,21,24,28,30,42,键入CTRL-Z,输入结束。“插入数据”输26即可。本题编写的是完整的pascal程序。
16.[题目分析] 将具有两个链域的单循环链表,改造成双向循环链表,关键是控制给每个结点均置上指向前驱的指针,而且每个结点的前驱指针置且仅置一次。
void StoDouble(LinkedList la)
∥la是结点含有pre,data,link三个域的单循环链表。其中data为数据域,pre为空指针域,link是指向后继的指针域。本算法将其改造成双向循环链表。
{while(la->link->pre==null)
{la->link->pre=la; ∥将结点la后继的pre指针指向la。
la=la->link; ∥la指针后移。
}
} ∥算法结束。
[算法讨论] 算法中没有设置变量记住单循环链表的起始结点,至少省去了一个指针变量。当算法结束时,la恢复到指向刚开始操作的结点,这是本算法的优点所在。
17.[题目分析] 求两个集合A和B的差集A-B,即在A中删除A和B中共有的元素。由于集合用单链表存储,问题变成删除链表中的结点问题。因此,要记住被删除结点的前驱,以便顺利删除被删结点。两链表均从第一元素结点开始,直到其中一个链表到尾为止。
void Difference(LinkedList A,B,*n)
∥A和B均是带头结点的递增有序的单链表,分别存储了一个集合,本算法求两集合的差集,存储于单链表A中,*n是结果集合中元素个数,调用时为0
{p=A->next; ∥p和q分别是链表A和B的工作指针。
q=B->next; pre=A; ∥pre为A中p所指结点的前驱结点的指针。
while(p!=null && q!=null)
if(p->data<q->data){pre=p;p=p->next;*n++;} ∥ A链表中当前结点指针后移。
else if(p->data>q->data)q=q->next; ∥B链表中当前结点指针后移。
else {pre->next=p->next; ∥处理A,B中元素值相同的结点,应删除。
u=p; p=p->next; free(u);} ∥删除结点
18.[题目分析] 本题要求对单链表结点的元素值进行运算,判断元素值是否等于其序号的平方减去其前驱的值。这里主要技术问题是结点的序号和前驱及后继指针的正确指向。
int Judge(LinkedList la)
∥la是结点的元素为整数的单链表。本算法判断从第二结点开始,每个元素值是否等于其序号的平方减去其前驱的值,如是返回true;否则,返回false。
{p=la->next->next;∥p是工作指针,初始指向链表的第二项。
pre=la->next; ∥pre是p所指结点的前驱指针。
i=2; ∥i是la链表中结点的序号,初始值为2。
while(p!=null)
if(p->data==i*i-pre->data){i++;pre=p;p=p->next;} ∥结点值间的关系符合题目要求
else break; ∥当前结点的值不等于其序号的平方减去前驱的值。
if(p!=null)return(false); ∥未查到表尾就结束了。
else return(true); ∥成功返回。
}∥算法结束。
[算法讨论]本题不设头结点也无影响。另外,算法中还可节省前驱指针pre,其算法片段如下:
p=la;∥假设无头结点,初始p指向第一元素结点。
i=2;
while(p->next!=null) ∥初始p->next指向第二项。
if(p->next->data= =i*i-p->data)
{i++;p=p->next;}
if(p->next!=null)return(false);∥失败
else return(true); ∥成功
19.[题目分析] 本题实质上是一个模式匹配问题,这里匹配的元素是整数而不是字符。因两整数序列已存入两个链表中,操作从两链表的第一个结点开始,若对应数据相等,则后移指针;若对应数据不等,则A链表从上次开始比较结点的后继开始,B链表仍从第一结点开始比较,直到B链表到尾表示匹配成功。A链表到尾B链表未到尾表示失败。操作中应记住A链表每次的开始结点,以便下趟匹配时好从其后继开始。
int Pattern(LinkedList A,B)
∥A和B分别是数据域为整数的单链表,本算法判断B是否是A的子序列。如是,返回1;否则,返回0表示失败。
{p=A; ∥p为A链表的工作指针,本题假定A和B均无头结点。
pre=p; ∥pre记住每趟比较中A链表的开始结点。
q=B; ∥q是B链表的工作指针。
while(p && q)
if(p->data==q->data) {p=p->next;q=q->next;}
else{pre=pre->next;p=pre; ∥A链表新的开始比较结点。
q=B;} ∥q从B链表第一结点开始。
if(q==null)return(1); ∥B是A的子序列。
else return(0); ∥B不是A的子序列。
}∥算法结束。
20.[题目分析] 本题也是模式匹配问题,应先找出链表L2在链表L1中的出现,然后将L1中的L2倒置过来。设L2在L1中出现时第一个字母结点的前驱的指针为p,最后一个字母结点在L1中为q所指结点的前驱,则在保存p后继结点指针(s)的情况下,执行p->next=q。之后将s到q结点的前驱依次插入到p结点之后,实现了L2在L1中的倒置。
LinkedList PatternInvert(LinkedList L1,L2)
∥L1和L2均是带头结点的单链表,数据结点的数据域均为一个字符。本算法将L1中与L2中数据域相同的连续结点的顺序完全倒置过来。
{p=L1; ∥p是每趟匹配时L1中的起始结点前驱的指针。
q=L1->next; ∥q是L1中的工作指针。
s=L2->next; ∥s是L2中的工作指针。
while(p!=null && s!=null)
if(q->data==s->data){q=q->next;s=s->next;} ∥对应字母相等,指针后移。
else {p=p->next;q=p->next;s=L2->next;} ∥失配时,L1起始结点后移,L2从首结点开始。
if(s==null)∥匹配成功,这时p为L1中与L2中首字母结点相同数据域结点的前驱,q为L1中与L2最后一个结点相同数据域结点的后继。
{r=p->next; ∥r为L1的工作指针,初始指向匹配的首字母结点。
p->next=q; ∥将p与q结点的链接。
while(r!=q); ∥逐结点倒置。
{s=r->next; ∥暂存r的后继。
r->next=p->next;∥将r所指结点倒置。
p->next=r;
r=s; ∥恢复r为当前结点。
}
}
else printf(“L2并未在L1中出现”);
} ∥算法结束。
[算法讨论] 本算法只讨论了L2在L1至多出现一次(可能没出现),没考虑在L1中多次出现的情况。若考虑多次出现,可在上面算法找到第一次出现后的q结点作L1中下次比较的第一字母结点,读者可自行完善之。
类似本题的另外叙述题的解答:
(1)[题目分析] 本题应先查找第i个结点,记下第i个结点的指针。然后从第i+1个结点起,直至第m(1<i<m)个结点止,依次插入到第i-1个结点之后。最后将暂存的第i个结点的指针指向第m结点,形成新的循环链表,结束了倒置算法。
LinkedList PatternInvert1(LinkedList L,int i,m)
∥L是有m个结点的链表的头结点的指针。表中从第i(1<i<m)个结点到第m个结点构成循环部分链表,本算法将这部分循环链表倒置。
{if(i<1|| i>=m || m<4){printf(“%d,%d参数错误/n”,i,m);exit(0);}
p=L->next->next; ∥p是工作指针,初始指向第二结点(已假定i>1)。
pre=L->next; ∥pre是前驱结点指针,最终指向第i-1个结点。
j=1; ∥计数器
while(j<i-1) ∥查找第i个结点。
{j++;pre=p;p=p->next;}∥查找结束,p指向第i个结点。
q=p; ∥暂存第i个结点的指针。
p=p->next; ∥p指向第i+1个结点,准备逆置。
j+=2; ∥上面while循环结束时,j=i-1,现从第i+1结点开始逆置。
while(j<=m)
{r=p->next; ∥暂存p的后继结点。
p->next=pre->next;∥逆置p结点。
pre->next=p;
p=r; ∥p恢复为当前待逆置结点。
j++; ∥计数器增1。
}
q->next=pre->next;∥将原第i个结点的后继指针指向原第m个结点。
[算法讨论] 算法中未深入讨论i,m,j的合法性,因题目的条件是m>3且1<i<m。因此控制循环并未用指针判断(如一般情况下的p!=null),结束循环也未用指针判断。注意最后一句q->next=pre->next,实现了从原第i个结点到原第m个结点的循环。最后pre->next正是指向原第m个结点,不可用p->next代替pre->next。
21.[题目分析] 顺序存储结构的线性表的逆置,只需一个变量辅助空间。算法核心是选择循环控制变量的初值和终值。
void SeqInvert(ElemType a[ ],int n)
∥a是具有n个元素用一维数组存储的线性表,本算法将其逆置。
{for(i=0;i<=(n-1)/2;i++)
{t=a[i];a[i]= a[n-1-i];a[n-1-i]=t;}
}∥算法结束
[算法讨论] 算法中循环控制变量的初值和终值是关键。C中数组从下标0开始,第n个元素的下标是n-1。因为首尾对称交换,所以控制变量的终值是线性表长度的一半。当n为偶数,“一半”恰好是线性表长度的二分之一;若n是奇数,“一半”是小于n/2的最大整数,这时取大于1/2的最小整数的位置上的元素,恰是线性表中间位置的元素,不需要逆置。另外,由于pascal数组通常从下标1开始,所以,上下界处理上略有不同。这点请读者注意。
类似本题的其它题的解答:
这一组又选了6个题,都是单链表(包括单循环链表)的逆置。链表逆置的通常作法是:将工作指针指向第一个元素结点,将头结点的指针域置空。然后将链表各结点从第一结点开始直至最后一个结点,依次前插至头结点后,使最后插入的结点成为链表的第一结点,第一个插入的结点成为链表的最后结点。
(1)要求编程实现带头结点的单链表的逆置。首先建立一单链表,然后逆置。
typedef struct node
{int data;∥假定结点数据域为整型。
struct node *next;
}node,*LinkedList;
LinkedList creat( )
{LinkedList head,p
int x;
head=(LinkedList)malloc(sizeof(node));
head->next=null; /*设置头结点*/
scanf(“%d”,&x);
while(x!=9999) /*约定输入9999时退出本函数*/
{p=(LinkedList)malloc(sizeof(node));
p->data=x;
p->next=head->next;/* 将新结点链入链表*/
head->next=p;
scanf(“%d”,&x);
}
return(head);
}∥结束creat函数。
LinkedList invert1(LinkedList head)
/*逆置单链表*/
{LinkedList p=head->next; /*p为工作指针*/
head->next=null;
while(p!=null)
{r=p->next; /*暂存p的后继*/
p->next=head->next;
head->next=p;
p=r;
}
return(head);
}/*结束invert1函数*/
main()
{LinkedList la;
la=creat( ); /*生成单链表*/
la=invert1(la);/*逆置单链表*/
}
(2)本题要求将数据项递减有序的单链表重新排序,使数据项递增有序,要求算法复杂度为O(n)。虽没说要求将链表逆置,这只是叙述不同,本质上是将单链表逆置,现编写如下:
LinkedList invert2(LinkedList la)
∥la是带头结点且数据项递减有序的单链表,本算法将其排列成数据项递增有序的单链表。
{p=la->next; /*p为工作指针*/
la->next=null;
while(p!=null)
{r=p->next; /*暂存p的后继。*/
p->next=la->next;/*将p结点前插入头结点后。*/
la->next=p;p=r;
}
}∥结束算法
(3)本题要求倒排循环链表,与上面倒排单链表处理不同之处有二:一是初始化成循环链表而不是空链表;二是判断链表尾不用空指针而用是否是链表头指针。算法中语句片段如下:
p=la->next; ∥p为工作指针。
la->next=la; ∥初始化成空循环链表。
while(p!=la) ∥当p=la时循环结束。
{r=p->next; ∥暂存p的后继结点
p->next=la->next;∥逆置
la->next=p; p=r;
}
(4)不带头结点的单链表逆置比较复杂,解决方法可以给加上头结点:
la=(LinkedList)malloc(sizeof(node));
la->next=L;
之后进行如上面(2)那样的逆置,最后再删去头结点:
L=la->next; ∥L是不带头结点的链表的指针。
free(la); ∥释放头结点。
若不增加头结点,可用如下语句片段:
p=L->next; ∥p为工作指针。
L->next=null;∥第一结点成为尾结点。
while(p!=null)
{r=p->next;
p->next=L;∥将p结点插到L结点前面。
L=p; ∥L指向新的链表“第一”元素结点。
p=r;
}
(5)同(4),只是叙述有异。
(6)同(2),差别仅在于叙述不同。
22.[题目分析] 在无序的单链表上,查找最小值结点,要查遍整个链表,初始假定第一结点是最小值结点。当找到最小值结点后,判断数据域的值是否是奇数,若是,则“与其后继结点的值相交换”即仅仅交换数据域的值,用三个赋值语句即可交换。若与后继结点交换位置,则需交换指针,这时应知道最小值结点的前驱。至于删除后继结点,则通过修改最小值结点的指针域即可。
[算法设计]
void MiniValue(LinkedList la)
∥la是数据域为正整数且无序的单链表,本算法查找最小值结点且打印。若最小值结点的数值是奇数,则与后继结点值交换;否则,就删除其直接后继结点。
{p=la->next; ∥设la是头结点的头指针,p为工作指针。
pre=p; ∥pre指向最小值结点,初始假定首元结点值最小。
while(p->next!=null) ∥p->next是待比较的当前结点。
{if(p->next->data<pre->data)pre=p->next;
p=p->next; ∥后移指针
}
printf(“最小值=%d/n”,pre->data);
if(pre->data%2!=0) ∥处理奇数
if(pre->next!=null)∥若该结点没有后继,则不必交换
{t= pre->data;pre->data=pre->next->data;pre->next->data=t;}∥交换完毕
else∥处理偶数情况
if(pre->next!=null)∥若最小值结点是最后一个结点,则无后继
{u=pre->next;pre->next=u->next;free(u);} ∥释放后继结点空间
23.[题目分析] 将一个结点数据域为字符的单链表,分解成含有字母字符、数字字符和其它字符的三个循环链表,首先要构造分别含有这三类字符的表头结点。然后从原链表第一个结点开始,根据结点数据域是字母字符、数字字符和其它字符而分别插入到三个链表之一的链表。注意不要因结点插入新建链表而使原链表断链。另外,题目并未要求链表有序,插入采用“前插法”,每次插入的结点均成为所插入链表的第一元素的结点即可。
void OneToThree(LinkedList L,la,ld,lo)
∥L是无头结点的单链表第一个结点的指针,链表中的数据域存放字符。本算法将链表L分解成含有英文字母字符、数字字符和其它字符的带头结点的三个循环链表。
{la=(LinkedList)malloc(sizeof(LNode)); ∥建立三个链表的头结点
ld=(LinkedList)malloc(sizeof(LNode));
lo=(LinkedList)malloc(sizeof(LNode));
la->next=la;ld->next=ld;lo->next=lo;∥置三个循环链表为空表
while(L!=null)∥分解原链表。
{r=L; L=L->next; ∥L指向待处理结点的后继
if(r->data>=‘a’&& r->data<=‘z’|| r->data>=‘A’&& r->data<=‘Z’)
{r->next=la->next; la->next=r;} ∥处理字母字符。
else if(r->data>=‘0’&& r->data<=‘9’)
{r->next=ld->next;ld->next=r;} ∥处理数字字符
else {r->next=lo->next;lo->next=r;} ∥处理其它符号。
}∥结束while(L!=null)。
}∥算法结束
[算法讨论] 算法中对L链表中每个结点只处理一次,时间复杂度O(n),只增加了必须的三个表头结点,符合题目“用最少的时间和最少的空间”的要求。
24.[题目分析] 在递增有序的线性表中,删除数值相同的元素,要知道被删除元素结点的前驱结点。
LinkedList DelSame(LinkedList la)
∥la是递增有序的单链表,本算法去掉数值相同的元素,使表中不再有重复的元素。
{pre=la->next;∥pre是p所指向的前驱结点的指针。
p=pre->next;∥p是工作指针。设链表中至少有一个结点。
while(p!=null)
if(p->data==pre->data) ∥处理相同元素值的结点
{u=p;p=p->next;free(u);} ∥释放相同元素值的结点
else {pre->next=p;pre=p;p=p->next;} ∥处理前驱,后继元素值不同
pre->next=p;∥置链表尾。
}∥DelSame
[算法讨论] 算法中假设链表至少有一个结点,即初始时pre不为空,否则p->next无意义。算法中最后pre->next=p是必须的,因为可能链表最后有数据域值相同的结点,这些结点均被删除,指针后移使p=null而退出while循环,所以应有pre->next=p使链表有尾。若链表尾部没数据域相同的结点,pre和p为前驱和后继,pre->next=p也是对的。
顺便提及,题目应叙述为非递减有序,因为“递增”是说明各结点数据域不同,一个值比一个值大,不会存在相同值元素。
25.[题目分析] 建立递增有序的顺序表,对每个输入数据,应首先查找该数据在顺序表中的位置,若表中没有该元素则插入之,如已有该元素,则不再插入,为此采用折半查找方法。
FUNC BinSearch(VAR a:sqlisttp;x:integer):integer;
∥在顺序表a中查找值为x的元素,如查找成功,返回0值,如x不在a中,则返回查找失败时的较大下标值。
low:=1;high:=a.last;found:=false;
WHILE(low<=high)AND NOT found DO
[mid:=(low+high)DIV 2;
IF a.elem[mid]=x THEN found:=true
ELSE IF a.elem[mid]>x THEN high:=mid-1 ELSE low:=mid+1;
]
IF found=true THEN return(0)
ELSE return(low);∥当查找失败时,low=high+1。
ENDF;∥结束对分查找函数。
PROC create(VAR L:sqlisttp)
∥本过程生成顺序表L。
L.last:=0; ∥顺序表L初始化。
read(x);
WHILE x<>9999 DO ∥设x=9999时退出输入
[k:=binsearch(L,x);∥去查找x元素。
IF k<>0 ∥不同元素才插入
THEN [FOR i:=L.last DOWNTO k DO L.elem[i+1]:=L.elem[i];
L.elem[k]=x;L.last:= L.last+1;∥插入元素x,线性表长度增1
]
read(x);
]
ENDP;∥结束过程creat
26.[题目分析] 在由正整数序列组成的有序单链表中,数据递增有序,允许相等整数存在。确定比正整数x大的数有几个属于计数问题,相同数只计一次,要求记住前驱,前驱和后继值不同时移动前驱指针,进行计数。将比正整数x小的数按递减排序,属于单链表的逆置问题。比正整数x大的偶数从表中删除,属于单链表中结点的删除,必须记住其前驱,以使链表不断链。算法结束时,链表中结点的排列是:小于x的数按递减排列,接着是x(若有的话),最后是大于x的奇数。
void exam(LinkedList la,int x)
∥la是递增有序单链表,数据域为正整数。本算法确定比x大的数有几个;将比x小的数按递减排序,并将比x大的偶数从链表中删除。)
{p=la->next;q=p;∥p为工作指针 q指向最小值元素,其可能的后继将是>=x的第一个元素。
pre=la; ∥pre为p的前驱结点指针。
k=0; ∥计数(比x大的数)。
la->next=null;∥置空单链表表头结点。
while(p && p->data<x) ∥先解决比x小的数按递减次序排列
{r=p->next; ∥暂存后继
p->next=la->next;∥逆置
la->next=p;
p=r;∥恢复当前指针。退出循环时,r指向值>=x的结点。
}
q->next=p; pre=q; ∥pre指向结点的前驱结点
while(p->data==x){pre=p; p=p->next;} ∥从小于x到大于x可能经过等于x
while(p) ∥以下结点的数据域的值均大于x
{k++; x=p->data; ∥下面仍用x表示数据域的值,计数
if(x % 2==0) ∥删偶数