数据结构1.docx
《数据结构1.docx》由会员分享,可在线阅读,更多相关《数据结构1.docx(29页珍藏版)》请在冰豆网上搜索。
数据结构1
(0012)《数据结构》复习思考题
一、常用的存储表示方法有哪几种?
二、下列程序段带标号语句的频度和时间复杂度。
(1)I=0;
while(I=k) I++;//语句3 return(I);(2)n为不小于1的整数(设k的初值等于1)voidpp(intk) { if(k==n)//语句1 for(I=0;I printf(a[I]);//语句3 else {for(I=k-1;I a[I]=a[I]+I;//语句5 pp(k+1);//语句6 } }//pp 三、算法的时间复杂度仅与问题的规模相关吗? 四、常用的存储表示方法有哪几种? 五、确定下列算法中输出语句的执行次数,并给出算法的时间复杂度。(1)voidcombi(intn){ intI,j,k; for(I=1;I<=n;I++) for(j=I+1;j<=n;j++) for(k=j+1;k<=n;k++) cout<(2)voidbinary(intn){ while(n){ cout< n=n/2; }} 六、为什么在单循环链表中设置尾指针比设置头指针更好? 七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜? 八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。if(I<1||k<0||I+k>a.length)returnERROR;else{for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK 九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。 十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。十五、简述以下算法的功能。(1)voidDemo1(SeqStack*S){intI;arr[64];n=0;while(StackEmpty(S))arr[n++]=Pop(S);for(I=0,I}//Demo1(2)SeqStackS1,S2,tmp;DataTypex;…//假设栈tmp和S2已做过初始化while(!StackEmpty(&S1)){x=Pop(&S1);Push(&tmp,x);}while(!StackEmpty(&tmp)){x=Pop(&tmp);Push(&S1,x);Push(&S2,x);(3)voidDemo2(SeqStack*S,intm){//设DataType为int型SeqStackT;intI;InitStack(&T);while(!StackEmpty(S))if((I=Pop(S))!=m)Push(&T,I);while(!StackEmpty(&T)){I=Pop(&T);Push(S,I);}}(4)voidDemo3(CirQueue*Q){//设DataType为int型intx;SeqStackS;InitStack(&S);while(!QueueEmpty(Q)){x=DeQueue(Q);Push(&S,x);}while(!StackEmpty(&s)){x=Pop(&S);EnQueue(Q,x);}}//Demo3(5)CirQueueQ1,Q2;//设DataType为int型intx,I,m=0;…//设Q1已有内容,Q2已初始化过while(!QueueEmpty(&Q1)){x=DeQueue(&Q1);EnQueue(&Q2,x);m++;}for(I=0;I{x=DeQueue(&Q2);EnQueue(&Q1,x);EnQueue(&Q2,x);}十六、写一算法voidStrReplace(char*T,char*P,char*S),将T中首次出现的子串P替换为串S。注意:S和P的长度不一定相等。可以使用已有的串操作。 十七、设有三对角矩阵An*n,将其三条对角线上的元素逐行地存储到向量B[0…3n-3]中,使得B[k]=aij,求:(1)用I,j表示k的下标变换公式。(2)用k表示I,j的下标变换公式。 十八、假设在二叉链表中增加两个域:双亲域(parent)以指示其双亲结点;标志域(mark取值0..2)以区分在遍历过程中到达该结点时应继续向左或向右或访问该结点。试以此存储结构编写不用栈进行后序遍历的递推形式的算法。 十九、分别写出下图所示各二叉树的前序、中序和后序序列。 二十、一个深度为h的满k叉树有如下性质:第h层上的结点都是叶子结点,其余各层上每个结点都有k棵非空子树。如果按层次顺序(同层自左至右)从1开始对全部结点编号,问:(1)各层的结点数目是多少?(2)编号为I的结点的双亲结点(若存在)的编号是多少?(3)编号为I的结点的第j个孩子结点(若存在)的编号是多少?(4)编号为I的结点的有右兄弟的条件是什么?其右兄弟的编号是多少? 二十一、编写算法完成下列操作:无重复地输出以孩子兄弟链表存储的树T中所有的边。输出形式为(k1,k2)…(ki,kj)…,其中ki,kj树结点中结点的标识。(提示:修改二叉树遍历的递归算法,使其参数表增加参数father,指向被访问的当前结点的双亲结点。) 二十二、已知以二维数组表示的图的邻接矩阵如下图所示。试分别画出自顶点1出发进行遍历所得的深度优先生成树和广度优先生成树。 二十三、在图所示的各无向图中:(1)找出所有的简单环。(2)哪些图是连通图?对非连通图给出其连通分量。(3)哪些图是自由树(或森林)? 二十四、对下图所示的AOE-网,计算各活动弧的e(ai)和l(aj)函数值、各事件(顶点)的ve(vi)和vl(vj)函数值;列出各条关键路径。 二十五、采用邻接表存储结构,编写一个判别无向图中任意给定的两个顶点之间是否存在一条长度为k的简单路径的算法。二十六、某校学生学号由8位十进制数字组成:c1c2c3c4c5c6c7c8。C1c2为入学时年份的后两砬;c3c4为系别:00~24分别代表该校的25个系:c5为0或1,0表示本科生,1表示研究生;C6c7c8为对某级某系某类学生的顺序编号,对于本科生,它不超过199,对于研究生,它不超过049,共有4个年级,四年级学生1996年入学。(1)当在校生人数达极限情况时,将他们的学号散列到0~24999的地址空间,问装载因子是多少?(2)求一个无冲突的哈希函数H1,它将在校生学号散列到0~24999的地址空间其簇聚性如何?(3)设在校生总数为15000人,散列地址空间为0~19999,你是否能找到一个(2)中要求的H1?若不能,试设计一个哈希函数H2及其解决冲突的方法,使得多数学号可只经一次散列得到(可设各系各年级本科生平均人数为130,研究生平均人数为20)。(4)用算法描述语言表达H2,并写出相应的查找函数。 二十七、在一棵空的二叉查找树中依次插入关键字序列为20、30、8、12、34、5、60、5、1,29,请画出所得到的二叉查找树。 二十八、画出对长度为18的有序的顺序表进行二分查找的判定树,并指出在等概率时查找成功的平均查找长度,以及查找失败时所需的最多的关键字比较次数 二十九、以关键字序列(265,301,751,129,937,863,742,694,076,438)为例,分别写出执行以下排序算法的各趟排序结束时,关键字序列的状态。(1)直接插入排序(2)希尔排序(3)冒泡排序(4)快速排序(5)直接选择排序(6)堆排序(7)归并排序(8)基数排序上述方法中,哪些是稳定的排序?哪些是非稳定的排序?对不稳定的排序试举出一个不稳定的实例。三十、以单链表作为存储结构实现直接插入排序算法。(0012)《数据结构》复习思考题答案一、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。二、下列程序段带标号语句的频度和时间复杂度。(1)I=0; while(I=k) I++;//语句3 return(I);(2)n为不小于1的整数(设k的初值等于1)voidpp(intk) { if(k==n)//语句1 for(I=0;I printf(a[I]);//语句3 else {for(I=k-1;I a[I]=a[I]+I;//语句5 pp(k+1);//语句6 } }//pp(1)这个算法完成在一维数组a[n]中查找给定值k的功能。语句三的频度不仅与问题的规模n有关,还与输入实例中a的各元素取值以及k的取值相关,即与输入实例的初始状态复杂有关。若a中没有与k相等的元素,则语句三的频度为n;若a中的第一个元素a[0]等于k,则语句三的频度是常数0。在这种情况下,可用最坏情况下的时间复杂度作为时间复杂度。在此例中即为O(n)。这样做的原因是:最坏情况下的时间复杂度是在任何输入实例上运行时间的上界。有时,也可能选择将算法的平均(或期望)时间复杂度作为讨论目标。所谓的平均时间复杂度是指所有可能的输入实例以等概率出现的情况下算法的期望运行时间与问题规模的数量级的关系。此例中,以k出现在任何位置的概率相同,都为1/n,则语句三的执行频度为[0+1+2+…+(n-1)]/n=(n-1)/2。它决定了此程序段的平均时间复杂度的数量级为f(n)=n,记作O(n)。(2)在计算包含调用语句的算法的语句频度时,需考虑到调用发生时在被调用算法中各语句的执行情况。本题所示的递归调用较之非递归调用的分析更为复杂。由于k等于n是算法的出口条件,不妨首先分析算法pp(n)的简单情况,这时各语句的执行频度分别为:1,n+1,n,0,0,0;而当k=n-1,n-2,…,1时,语句的执行情况和调度情况,如下表所示。K值不考虑调用时各语句的执行频度调用情况语句1语句2语句3语句4语句5语句6n1n+1n000/n-1100321pp(n)n-2100431pp(n-1)……………………1100n+1n1pp(2)对于k=1即pp(1)而言,各语句的执行次数还须将调用pp(2)时的执行次数累计到内,pp(2)各语句的执行次数又须将调用pp(3)时执行次数累计到内,……由此可的语句频度如下:语句1:1+1+…+1=n语句2:0+0+…+0+(n+1)=n+1语句3:0+0+…+0+n=n语句4:(n+1)+n+…+3=(n-1)(n+4)/2语句5:n+(n-1)+…+2=(n-1)(n+2)/2语句6:1+1+….+1+0=n-1算法的时间复杂度可以基于频度最大的语句,应为O(n2)。三、算法的时间复杂度仅与问题的规模相关吗?不,事实上,算法的时间复杂度不仅与问题的规模相关,还与输入实例中的元素取值等相关,但在最坏的情况下,其时间复杂度就是只与求解问题的规模相关的。我们在讨论时间复杂度时,一般就是以最坏情况下的时间复杂度为准的。 四、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。五、确定下列算法中输出语句的执行次数,并给出算法的时间复杂度。(1)voidcombi(intn){ intI,j,k; for(I=1;I<=n;I++) for(j=I+1;j<=n;j++) for(k=j+1;k<=n;k++) cout<(2)voidbinary(intn){ while(n){ cout< n=n/2; }}(1)I取值范围从1~n,j取值范围从I+1~n,k取值范围从j+1~n,情况如下表所示: I值j值k值输出语句的执行次数123,4,…,nn-2………n-1n1n//234,5,…,nn-3………n-1n1n//…………n-2n-1n1n//n-1n//n///所以,输出语句共执行次数为((n-2)+(n-3)+…+1)+((n-3)+(n-4)+…+1)+…+1=(n-1)(n-2)/2+(n-2)(n-3)/2+…+1=(((n-1)2+(n-2)2+(n-3)2+…+12)-((n-1)+(n-2)+(n-3)+….+1))/2=((n-1)n(2n-1)/6-n(n-1)/2)/2=(n(n-1)(2n-4))/12=n(n-1)(n-2)/6(2)ceil(log2n);六、为什么在单循环链表中设置尾指针比设置头指针更好?答:尾指针是指向终端结点的指针,用它来表示单循环链表可以使得查找链表的开始结点和终端结点都很方便,设一带头结点的单循环链表,其尾指针为rear,则开始结点和终端结点的位置分别是rear->next->next和rear,查找时间都是O(1)。若用头指针来表示该链表,则查找终端结点的时间为O(n)。七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜?答:在实际应用中,应根据具体问题的要求和性质来选择顺序表或链表作为线性表的存储结构,通常有以下几方面的考虑:1.基于空间的考虑。当要求存储的线性表长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序表;反之,当线性表长度变化大,难以估计其存储规模时,采用动态链表作为存储结构为好。2.基于时间的考虑。若线性表的操作主要是进行查找,很少做插入和删除操作时,采用顺序表做存储结构为宜;反之,若需要对线性表进行频繁地插入或删除等的操作时,宜采用链表做存储结构。并且,若链表的插入和删除主要发生在表的首尾两端,则采用尾指针表示的单循环链表为宜。八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。if(I<1||k<0||I+k>a.length)returnERROR;else{for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK更正:for(j=I+k;j<=a.Length;j++)a.elem[j-k]=a.elem[j];a.Length=a.Length–k;九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度算法如下:LinkListLink(LinkListL1,LinkListL2){//将两个单链表连接在一起ListNode*p,*q;p=L1;q=L2;while(p->next)p=p->next;//查找终端结点p->next=q->next;//将L2的开始结点链接在L1之后returnL1;}本算法的主要操作时间花费在查找L1的终端结点上,与L2的长度无关,所以本算的法时间复杂度为:m+1=O(m)十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。要解这样的问题,我们首先想到的是拿链表中的元素一个个地与max和min比较,然后删除这个结点,其实因为已知其是有序链表,所以我们只要找到大于min的结点的直接前趋结点,再找到小于max的结点,然后一并把中间的全部摘掉就可以了。算法如下:voidDeleteList(LinkListL,DataTypemin,DataTypemax){ListNode*p,*q,*r;p=L->next;while(p&&p->data<=min){//找比min大的前一个元素位置q=p;p=p->next;}while(p&&p->data{r=p;p=p->next;free?;//释放这个结点空间}q->next=p;//把断点链上} 十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。答:(1)4132;(2)4213;(3)4231。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。答案:设用变量I表示栈的编号。类型定义:typedefstruct{ElemTypev[m];//栈空间向量inttop[2];//栈顶指针向量}DuStack;栈空条件:s0栈:s->top[0]=-1s1栈:s->top[1]=m栈满条件:s->top[0]+1=s->top[1](此时向量空间全占满)。(1)插入voidpush(DuStack*s,ElemTypex,intI)//当两个栈共享空间时,再由I指定的栈中插入新元素x{if(s->top[0]+1==s->top[1]){printf(“OVERFLOW”);return;}switch(I){case0:s->top[0]++
=k)
I++;//语句3
return(I);
(2)n为不小于1的整数(设k的初值等于1)
voidpp(intk)
{
if(k==n)//语句1
for(I=0;I printf(a[I]);//语句3 else {for(I=k-1;I a[I]=a[I]+I;//语句5 pp(k+1);//语句6 } }//pp 三、算法的时间复杂度仅与问题的规模相关吗? 四、常用的存储表示方法有哪几种? 五、确定下列算法中输出语句的执行次数,并给出算法的时间复杂度。(1)voidcombi(intn){ intI,j,k; for(I=1;I<=n;I++) for(j=I+1;j<=n;j++) for(k=j+1;k<=n;k++) cout<(2)voidbinary(intn){ while(n){ cout< n=n/2; }} 六、为什么在单循环链表中设置尾指针比设置头指针更好? 七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜? 八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。if(I<1||k<0||I+k>a.length)returnERROR;else{for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK 九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。 十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。十五、简述以下算法的功能。(1)voidDemo1(SeqStack*S){intI;arr[64];n=0;while(StackEmpty(S))arr[n++]=Pop(S);for(I=0,I}//Demo1(2)SeqStackS1,S2,tmp;DataTypex;…//假设栈tmp和S2已做过初始化while(!StackEmpty(&S1)){x=Pop(&S1);Push(&tmp,x);}while(!StackEmpty(&tmp)){x=Pop(&tmp);Push(&S1,x);Push(&S2,x);(3)voidDemo2(SeqStack*S,intm){//设DataType为int型SeqStackT;intI;InitStack(&T);while(!StackEmpty(S))if((I=Pop(S))!=m)Push(&T,I);while(!StackEmpty(&T)){I=Pop(&T);Push(S,I);}}(4)voidDemo3(CirQueue*Q){//设DataType为int型intx;SeqStackS;InitStack(&S);while(!QueueEmpty(Q)){x=DeQueue(Q);Push(&S,x);}while(!StackEmpty(&s)){x=Pop(&S);EnQueue(Q,x);}}//Demo3(5)CirQueueQ1,Q2;//设DataType为int型intx,I,m=0;…//设Q1已有内容,Q2已初始化过while(!QueueEmpty(&Q1)){x=DeQueue(&Q1);EnQueue(&Q2,x);m++;}for(I=0;I{x=DeQueue(&Q2);EnQueue(&Q1,x);EnQueue(&Q2,x);}十六、写一算法voidStrReplace(char*T,char*P,char*S),将T中首次出现的子串P替换为串S。注意:S和P的长度不一定相等。可以使用已有的串操作。 十七、设有三对角矩阵An*n,将其三条对角线上的元素逐行地存储到向量B[0…3n-3]中,使得B[k]=aij,求:(1)用I,j表示k的下标变换公式。(2)用k表示I,j的下标变换公式。 十八、假设在二叉链表中增加两个域:双亲域(parent)以指示其双亲结点;标志域(mark取值0..2)以区分在遍历过程中到达该结点时应继续向左或向右或访问该结点。试以此存储结构编写不用栈进行后序遍历的递推形式的算法。 十九、分别写出下图所示各二叉树的前序、中序和后序序列。 二十、一个深度为h的满k叉树有如下性质:第h层上的结点都是叶子结点,其余各层上每个结点都有k棵非空子树。如果按层次顺序(同层自左至右)从1开始对全部结点编号,问:(1)各层的结点数目是多少?(2)编号为I的结点的双亲结点(若存在)的编号是多少?(3)编号为I的结点的第j个孩子结点(若存在)的编号是多少?(4)编号为I的结点的有右兄弟的条件是什么?其右兄弟的编号是多少? 二十一、编写算法完成下列操作:无重复地输出以孩子兄弟链表存储的树T中所有的边。输出形式为(k1,k2)…(ki,kj)…,其中ki,kj树结点中结点的标识。(提示:修改二叉树遍历的递归算法,使其参数表增加参数father,指向被访问的当前结点的双亲结点。) 二十二、已知以二维数组表示的图的邻接矩阵如下图所示。试分别画出自顶点1出发进行遍历所得的深度优先生成树和广度优先生成树。 二十三、在图所示的各无向图中:(1)找出所有的简单环。(2)哪些图是连通图?对非连通图给出其连通分量。(3)哪些图是自由树(或森林)? 二十四、对下图所示的AOE-网,计算各活动弧的e(ai)和l(aj)函数值、各事件(顶点)的ve(vi)和vl(vj)函数值;列出各条关键路径。 二十五、采用邻接表存储结构,编写一个判别无向图中任意给定的两个顶点之间是否存在一条长度为k的简单路径的算法。二十六、某校学生学号由8位十进制数字组成:c1c2c3c4c5c6c7c8。C1c2为入学时年份的后两砬;c3c4为系别:00~24分别代表该校的25个系:c5为0或1,0表示本科生,1表示研究生;C6c7c8为对某级某系某类学生的顺序编号,对于本科生,它不超过199,对于研究生,它不超过049,共有4个年级,四年级学生1996年入学。(1)当在校生人数达极限情况时,将他们的学号散列到0~24999的地址空间,问装载因子是多少?(2)求一个无冲突的哈希函数H1,它将在校生学号散列到0~24999的地址空间其簇聚性如何?(3)设在校生总数为15000人,散列地址空间为0~19999,你是否能找到一个(2)中要求的H1?若不能,试设计一个哈希函数H2及其解决冲突的方法,使得多数学号可只经一次散列得到(可设各系各年级本科生平均人数为130,研究生平均人数为20)。(4)用算法描述语言表达H2,并写出相应的查找函数。 二十七、在一棵空的二叉查找树中依次插入关键字序列为20、30、8、12、34、5、60、5、1,29,请画出所得到的二叉查找树。 二十八、画出对长度为18的有序的顺序表进行二分查找的判定树,并指出在等概率时查找成功的平均查找长度,以及查找失败时所需的最多的关键字比较次数 二十九、以关键字序列(265,301,751,129,937,863,742,694,076,438)为例,分别写出执行以下排序算法的各趟排序结束时,关键字序列的状态。(1)直接插入排序(2)希尔排序(3)冒泡排序(4)快速排序(5)直接选择排序(6)堆排序(7)归并排序(8)基数排序上述方法中,哪些是稳定的排序?哪些是非稳定的排序?对不稳定的排序试举出一个不稳定的实例。三十、以单链表作为存储结构实现直接插入排序算法。(0012)《数据结构》复习思考题答案一、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。二、下列程序段带标号语句的频度和时间复杂度。(1)I=0; while(I=k) I++;//语句3 return(I);(2)n为不小于1的整数(设k的初值等于1)voidpp(intk) { if(k==n)//语句1 for(I=0;I printf(a[I]);//语句3 else {for(I=k-1;I a[I]=a[I]+I;//语句5 pp(k+1);//语句6 } }//pp(1)这个算法完成在一维数组a[n]中查找给定值k的功能。语句三的频度不仅与问题的规模n有关,还与输入实例中a的各元素取值以及k的取值相关,即与输入实例的初始状态复杂有关。若a中没有与k相等的元素,则语句三的频度为n;若a中的第一个元素a[0]等于k,则语句三的频度是常数0。在这种情况下,可用最坏情况下的时间复杂度作为时间复杂度。在此例中即为O(n)。这样做的原因是:最坏情况下的时间复杂度是在任何输入实例上运行时间的上界。有时,也可能选择将算法的平均(或期望)时间复杂度作为讨论目标。所谓的平均时间复杂度是指所有可能的输入实例以等概率出现的情况下算法的期望运行时间与问题规模的数量级的关系。此例中,以k出现在任何位置的概率相同,都为1/n,则语句三的执行频度为[0+1+2+…+(n-1)]/n=(n-1)/2。它决定了此程序段的平均时间复杂度的数量级为f(n)=n,记作O(n)。(2)在计算包含调用语句的算法的语句频度时,需考虑到调用发生时在被调用算法中各语句的执行情况。本题所示的递归调用较之非递归调用的分析更为复杂。由于k等于n是算法的出口条件,不妨首先分析算法pp(n)的简单情况,这时各语句的执行频度分别为:1,n+1,n,0,0,0;而当k=n-1,n-2,…,1时,语句的执行情况和调度情况,如下表所示。K值不考虑调用时各语句的执行频度调用情况语句1语句2语句3语句4语句5语句6n1n+1n000/n-1100321pp(n)n-2100431pp(n-1)……………………1100n+1n1pp(2)对于k=1即pp(1)而言,各语句的执行次数还须将调用pp(2)时的执行次数累计到内,pp(2)各语句的执行次数又须将调用pp(3)时执行次数累计到内,……由此可的语句频度如下:语句1:1+1+…+1=n语句2:0+0+…+0+(n+1)=n+1语句3:0+0+…+0+n=n语句4:(n+1)+n+…+3=(n-1)(n+4)/2语句5:n+(n-1)+…+2=(n-1)(n+2)/2语句6:1+1+….+1+0=n-1算法的时间复杂度可以基于频度最大的语句,应为O(n2)。三、算法的时间复杂度仅与问题的规模相关吗?不,事实上,算法的时间复杂度不仅与问题的规模相关,还与输入实例中的元素取值等相关,但在最坏的情况下,其时间复杂度就是只与求解问题的规模相关的。我们在讨论时间复杂度时,一般就是以最坏情况下的时间复杂度为准的。 四、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。五、确定下列算法中输出语句的执行次数,并给出算法的时间复杂度。(1)voidcombi(intn){ intI,j,k; for(I=1;I<=n;I++) for(j=I+1;j<=n;j++) for(k=j+1;k<=n;k++) cout<(2)voidbinary(intn){ while(n){ cout< n=n/2; }}(1)I取值范围从1~n,j取值范围从I+1~n,k取值范围从j+1~n,情况如下表所示: I值j值k值输出语句的执行次数123,4,…,nn-2………n-1n1n//234,5,…,nn-3………n-1n1n//…………n-2n-1n1n//n-1n//n///所以,输出语句共执行次数为((n-2)+(n-3)+…+1)+((n-3)+(n-4)+…+1)+…+1=(n-1)(n-2)/2+(n-2)(n-3)/2+…+1=(((n-1)2+(n-2)2+(n-3)2+…+12)-((n-1)+(n-2)+(n-3)+….+1))/2=((n-1)n(2n-1)/6-n(n-1)/2)/2=(n(n-1)(2n-4))/12=n(n-1)(n-2)/6(2)ceil(log2n);六、为什么在单循环链表中设置尾指针比设置头指针更好?答:尾指针是指向终端结点的指针,用它来表示单循环链表可以使得查找链表的开始结点和终端结点都很方便,设一带头结点的单循环链表,其尾指针为rear,则开始结点和终端结点的位置分别是rear->next->next和rear,查找时间都是O(1)。若用头指针来表示该链表,则查找终端结点的时间为O(n)。七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜?答:在实际应用中,应根据具体问题的要求和性质来选择顺序表或链表作为线性表的存储结构,通常有以下几方面的考虑:1.基于空间的考虑。当要求存储的线性表长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序表;反之,当线性表长度变化大,难以估计其存储规模时,采用动态链表作为存储结构为好。2.基于时间的考虑。若线性表的操作主要是进行查找,很少做插入和删除操作时,采用顺序表做存储结构为宜;反之,若需要对线性表进行频繁地插入或删除等的操作时,宜采用链表做存储结构。并且,若链表的插入和删除主要发生在表的首尾两端,则采用尾指针表示的单循环链表为宜。八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。if(I<1||k<0||I+k>a.length)returnERROR;else{for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK更正:for(j=I+k;j<=a.Length;j++)a.elem[j-k]=a.elem[j];a.Length=a.Length–k;九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度算法如下:LinkListLink(LinkListL1,LinkListL2){//将两个单链表连接在一起ListNode*p,*q;p=L1;q=L2;while(p->next)p=p->next;//查找终端结点p->next=q->next;//将L2的开始结点链接在L1之后returnL1;}本算法的主要操作时间花费在查找L1的终端结点上,与L2的长度无关,所以本算的法时间复杂度为:m+1=O(m)十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。要解这样的问题,我们首先想到的是拿链表中的元素一个个地与max和min比较,然后删除这个结点,其实因为已知其是有序链表,所以我们只要找到大于min的结点的直接前趋结点,再找到小于max的结点,然后一并把中间的全部摘掉就可以了。算法如下:voidDeleteList(LinkListL,DataTypemin,DataTypemax){ListNode*p,*q,*r;p=L->next;while(p&&p->data<=min){//找比min大的前一个元素位置q=p;p=p->next;}while(p&&p->data{r=p;p=p->next;free?;//释放这个结点空间}q->next=p;//把断点链上} 十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。答:(1)4132;(2)4213;(3)4231。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。答案:设用变量I表示栈的编号。类型定义:typedefstruct{ElemTypev[m];//栈空间向量inttop[2];//栈顶指针向量}DuStack;栈空条件:s0栈:s->top[0]=-1s1栈:s->top[1]=m栈满条件:s->top[0]+1=s->top[1](此时向量空间全占满)。(1)插入voidpush(DuStack*s,ElemTypex,intI)//当两个栈共享空间时,再由I指定的栈中插入新元素x{if(s->top[0]+1==s->top[1]){printf(“OVERFLOW”);return;}switch(I){case0:s->top[0]++
printf(a[I]);//语句3
else
{for(I=k-1;I a[I]=a[I]+I;//语句5 pp(k+1);//语句6 } }//pp 三、算法的时间复杂度仅与问题的规模相关吗? 四、常用的存储表示方法有哪几种? 五、确定下列算法中输出语句的执行次数,并给出算法的时间复杂度。(1)voidcombi(intn){ intI,j,k; for(I=1;I<=n;I++) for(j=I+1;j<=n;j++) for(k=j+1;k<=n;k++) cout<(2)voidbinary(intn){ while(n){ cout< n=n/2; }} 六、为什么在单循环链表中设置尾指针比设置头指针更好? 七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜? 八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。if(I<1||k<0||I+k>a.length)returnERROR;else{for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK 九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。 十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。十五、简述以下算法的功能。(1)voidDemo1(SeqStack*S){intI;arr[64];n=0;while(StackEmpty(S))arr[n++]=Pop(S);for(I=0,I}//Demo1(2)SeqStackS1,S2,tmp;DataTypex;…//假设栈tmp和S2已做过初始化while(!StackEmpty(&S1)){x=Pop(&S1);Push(&tmp,x);}while(!StackEmpty(&tmp)){x=Pop(&tmp);Push(&S1,x);Push(&S2,x);(3)voidDemo2(SeqStack*S,intm){//设DataType为int型SeqStackT;intI;InitStack(&T);while(!StackEmpty(S))if((I=Pop(S))!=m)Push(&T,I);while(!StackEmpty(&T)){I=Pop(&T);Push(S,I);}}(4)voidDemo3(CirQueue*Q){//设DataType为int型intx;SeqStackS;InitStack(&S);while(!QueueEmpty(Q)){x=DeQueue(Q);Push(&S,x);}while(!StackEmpty(&s)){x=Pop(&S);EnQueue(Q,x);}}//Demo3(5)CirQueueQ1,Q2;//设DataType为int型intx,I,m=0;…//设Q1已有内容,Q2已初始化过while(!QueueEmpty(&Q1)){x=DeQueue(&Q1);EnQueue(&Q2,x);m++;}for(I=0;I{x=DeQueue(&Q2);EnQueue(&Q1,x);EnQueue(&Q2,x);}十六、写一算法voidStrReplace(char*T,char*P,char*S),将T中首次出现的子串P替换为串S。注意:S和P的长度不一定相等。可以使用已有的串操作。 十七、设有三对角矩阵An*n,将其三条对角线上的元素逐行地存储到向量B[0…3n-3]中,使得B[k]=aij,求:(1)用I,j表示k的下标变换公式。(2)用k表示I,j的下标变换公式。 十八、假设在二叉链表中增加两个域:双亲域(parent)以指示其双亲结点;标志域(mark取值0..2)以区分在遍历过程中到达该结点时应继续向左或向右或访问该结点。试以此存储结构编写不用栈进行后序遍历的递推形式的算法。 十九、分别写出下图所示各二叉树的前序、中序和后序序列。 二十、一个深度为h的满k叉树有如下性质:第h层上的结点都是叶子结点,其余各层上每个结点都有k棵非空子树。如果按层次顺序(同层自左至右)从1开始对全部结点编号,问:(1)各层的结点数目是多少?(2)编号为I的结点的双亲结点(若存在)的编号是多少?(3)编号为I的结点的第j个孩子结点(若存在)的编号是多少?(4)编号为I的结点的有右兄弟的条件是什么?其右兄弟的编号是多少? 二十一、编写算法完成下列操作:无重复地输出以孩子兄弟链表存储的树T中所有的边。输出形式为(k1,k2)…(ki,kj)…,其中ki,kj树结点中结点的标识。(提示:修改二叉树遍历的递归算法,使其参数表增加参数father,指向被访问的当前结点的双亲结点。) 二十二、已知以二维数组表示的图的邻接矩阵如下图所示。试分别画出自顶点1出发进行遍历所得的深度优先生成树和广度优先生成树。 二十三、在图所示的各无向图中:(1)找出所有的简单环。(2)哪些图是连通图?对非连通图给出其连通分量。(3)哪些图是自由树(或森林)? 二十四、对下图所示的AOE-网,计算各活动弧的e(ai)和l(aj)函数值、各事件(顶点)的ve(vi)和vl(vj)函数值;列出各条关键路径。 二十五、采用邻接表存储结构,编写一个判别无向图中任意给定的两个顶点之间是否存在一条长度为k的简单路径的算法。二十六、某校学生学号由8位十进制数字组成:c1c2c3c4c5c6c7c8。C1c2为入学时年份的后两砬;c3c4为系别:00~24分别代表该校的25个系:c5为0或1,0表示本科生,1表示研究生;C6c7c8为对某级某系某类学生的顺序编号,对于本科生,它不超过199,对于研究生,它不超过049,共有4个年级,四年级学生1996年入学。(1)当在校生人数达极限情况时,将他们的学号散列到0~24999的地址空间,问装载因子是多少?(2)求一个无冲突的哈希函数H1,它将在校生学号散列到0~24999的地址空间其簇聚性如何?(3)设在校生总数为15000人,散列地址空间为0~19999,你是否能找到一个(2)中要求的H1?若不能,试设计一个哈希函数H2及其解决冲突的方法,使得多数学号可只经一次散列得到(可设各系各年级本科生平均人数为130,研究生平均人数为20)。(4)用算法描述语言表达H2,并写出相应的查找函数。 二十七、在一棵空的二叉查找树中依次插入关键字序列为20、30、8、12、34、5、60、5、1,29,请画出所得到的二叉查找树。 二十八、画出对长度为18的有序的顺序表进行二分查找的判定树,并指出在等概率时查找成功的平均查找长度,以及查找失败时所需的最多的关键字比较次数 二十九、以关键字序列(265,301,751,129,937,863,742,694,076,438)为例,分别写出执行以下排序算法的各趟排序结束时,关键字序列的状态。(1)直接插入排序(2)希尔排序(3)冒泡排序(4)快速排序(5)直接选择排序(6)堆排序(7)归并排序(8)基数排序上述方法中,哪些是稳定的排序?哪些是非稳定的排序?对不稳定的排序试举出一个不稳定的实例。三十、以单链表作为存储结构实现直接插入排序算法。(0012)《数据结构》复习思考题答案一、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。二、下列程序段带标号语句的频度和时间复杂度。(1)I=0; while(I=k) I++;//语句3 return(I);(2)n为不小于1的整数(设k的初值等于1)voidpp(intk) { if(k==n)//语句1 for(I=0;I printf(a[I]);//语句3 else {for(I=k-1;I a[I]=a[I]+I;//语句5 pp(k+1);//语句6 } }//pp(1)这个算法完成在一维数组a[n]中查找给定值k的功能。语句三的频度不仅与问题的规模n有关,还与输入实例中a的各元素取值以及k的取值相关,即与输入实例的初始状态复杂有关。若a中没有与k相等的元素,则语句三的频度为n;若a中的第一个元素a[0]等于k,则语句三的频度是常数0。在这种情况下,可用最坏情况下的时间复杂度作为时间复杂度。在此例中即为O(n)。这样做的原因是:最坏情况下的时间复杂度是在任何输入实例上运行时间的上界。有时,也可能选择将算法的平均(或期望)时间复杂度作为讨论目标。所谓的平均时间复杂度是指所有可能的输入实例以等概率出现的情况下算法的期望运行时间与问题规模的数量级的关系。此例中,以k出现在任何位置的概率相同,都为1/n,则语句三的执行频度为[0+1+2+…+(n-1)]/n=(n-1)/2。它决定了此程序段的平均时间复杂度的数量级为f(n)=n,记作O(n)。(2)在计算包含调用语句的算法的语句频度时,需考虑到调用发生时在被调用算法中各语句的执行情况。本题所示的递归调用较之非递归调用的分析更为复杂。由于k等于n是算法的出口条件,不妨首先分析算法pp(n)的简单情况,这时各语句的执行频度分别为:1,n+1,n,0,0,0;而当k=n-1,n-2,…,1时,语句的执行情况和调度情况,如下表所示。K值不考虑调用时各语句的执行频度调用情况语句1语句2语句3语句4语句5语句6n1n+1n000/n-1100321pp(n)n-2100431pp(n-1)……………………1100n+1n1pp(2)对于k=1即pp(1)而言,各语句的执行次数还须将调用pp(2)时的执行次数累计到内,pp(2)各语句的执行次数又须将调用pp(3)时执行次数累计到内,……由此可的语句频度如下:语句1:1+1+…+1=n语句2:0+0+…+0+(n+1)=n+1语句3:0+0+…+0+n=n语句4:(n+1)+n+…+3=(n-1)(n+4)/2语句5:n+(n-1)+…+2=(n-1)(n+2)/2语句6:1+1+….+1+0=n-1算法的时间复杂度可以基于频度最大的语句,应为O(n2)。三、算法的时间复杂度仅与问题的规模相关吗?不,事实上,算法的时间复杂度不仅与问题的规模相关,还与输入实例中的元素取值等相关,但在最坏的情况下,其时间复杂度就是只与求解问题的规模相关的。我们在讨论时间复杂度时,一般就是以最坏情况下的时间复杂度为准的。 四、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。五、确定下列算法中输出语句的执行次数,并给出算法的时间复杂度。(1)voidcombi(intn){ intI,j,k; for(I=1;I<=n;I++) for(j=I+1;j<=n;j++) for(k=j+1;k<=n;k++) cout<(2)voidbinary(intn){ while(n){ cout< n=n/2; }}(1)I取值范围从1~n,j取值范围从I+1~n,k取值范围从j+1~n,情况如下表所示: I值j值k值输出语句的执行次数123,4,…,nn-2………n-1n1n//234,5,…,nn-3………n-1n1n//…………n-2n-1n1n//n-1n//n///所以,输出语句共执行次数为((n-2)+(n-3)+…+1)+((n-3)+(n-4)+…+1)+…+1=(n-1)(n-2)/2+(n-2)(n-3)/2+…+1=(((n-1)2+(n-2)2+(n-3)2+…+12)-((n-1)+(n-2)+(n-3)+….+1))/2=((n-1)n(2n-1)/6-n(n-1)/2)/2=(n(n-1)(2n-4))/12=n(n-1)(n-2)/6(2)ceil(log2n);六、为什么在单循环链表中设置尾指针比设置头指针更好?答:尾指针是指向终端结点的指针,用它来表示单循环链表可以使得查找链表的开始结点和终端结点都很方便,设一带头结点的单循环链表,其尾指针为rear,则开始结点和终端结点的位置分别是rear->next->next和rear,查找时间都是O(1)。若用头指针来表示该链表,则查找终端结点的时间为O(n)。七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜?答:在实际应用中,应根据具体问题的要求和性质来选择顺序表或链表作为线性表的存储结构,通常有以下几方面的考虑:1.基于空间的考虑。当要求存储的线性表长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序表;反之,当线性表长度变化大,难以估计其存储规模时,采用动态链表作为存储结构为好。2.基于时间的考虑。若线性表的操作主要是进行查找,很少做插入和删除操作时,采用顺序表做存储结构为宜;反之,若需要对线性表进行频繁地插入或删除等的操作时,宜采用链表做存储结构。并且,若链表的插入和删除主要发生在表的首尾两端,则采用尾指针表示的单循环链表为宜。八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。if(I<1||k<0||I+k>a.length)returnERROR;else{for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK更正:for(j=I+k;j<=a.Length;j++)a.elem[j-k]=a.elem[j];a.Length=a.Length–k;九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度算法如下:LinkListLink(LinkListL1,LinkListL2){//将两个单链表连接在一起ListNode*p,*q;p=L1;q=L2;while(p->next)p=p->next;//查找终端结点p->next=q->next;//将L2的开始结点链接在L1之后returnL1;}本算法的主要操作时间花费在查找L1的终端结点上,与L2的长度无关,所以本算的法时间复杂度为:m+1=O(m)十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。要解这样的问题,我们首先想到的是拿链表中的元素一个个地与max和min比较,然后删除这个结点,其实因为已知其是有序链表,所以我们只要找到大于min的结点的直接前趋结点,再找到小于max的结点,然后一并把中间的全部摘掉就可以了。算法如下:voidDeleteList(LinkListL,DataTypemin,DataTypemax){ListNode*p,*q,*r;p=L->next;while(p&&p->data<=min){//找比min大的前一个元素位置q=p;p=p->next;}while(p&&p->data{r=p;p=p->next;free?;//释放这个结点空间}q->next=p;//把断点链上} 十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。答:(1)4132;(2)4213;(3)4231。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。答案:设用变量I表示栈的编号。类型定义:typedefstruct{ElemTypev[m];//栈空间向量inttop[2];//栈顶指针向量}DuStack;栈空条件:s0栈:s->top[0]=-1s1栈:s->top[1]=m栈满条件:s->top[0]+1=s->top[1](此时向量空间全占满)。(1)插入voidpush(DuStack*s,ElemTypex,intI)//当两个栈共享空间时,再由I指定的栈中插入新元素x{if(s->top[0]+1==s->top[1]){printf(“OVERFLOW”);return;}switch(I){case0:s->top[0]++
a[I]=a[I]+I;//语句5
pp(k+1);//语句6
}
}//pp
三、算法的时间复杂度仅与问题的规模相关吗?
四、常用的存储表示方法有哪几种?
五、确定下列算法中输出语句的执行次数,并给出算法的时间复杂度。
(1)voidcombi(intn)
{ intI,j,k;
for(I=1;I<=n;I++)
for(j=I+1;j<=n;j++)
for(k=j+1;k<=n;k++)
cout<
(2)voidbinary(intn)
{ while(n){
cout< n=n/2; }} 六、为什么在单循环链表中设置尾指针比设置头指针更好? 七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜? 八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。if(I<1||k<0||I+k>a.length)returnERROR;else{for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK 九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。 十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。十五、简述以下算法的功能。(1)voidDemo1(SeqStack*S){intI;arr[64];n=0;while(StackEmpty(S))arr[n++]=Pop(S);for(I=0,I}//Demo1(2)SeqStackS1,S2,tmp;DataTypex;…//假设栈tmp和S2已做过初始化while(!StackEmpty(&S1)){x=Pop(&S1);Push(&tmp,x);}while(!StackEmpty(&tmp)){x=Pop(&tmp);Push(&S1,x);Push(&S2,x);(3)voidDemo2(SeqStack*S,intm){//设DataType为int型SeqStackT;intI;InitStack(&T);while(!StackEmpty(S))if((I=Pop(S))!=m)Push(&T,I);while(!StackEmpty(&T)){I=Pop(&T);Push(S,I);}}(4)voidDemo3(CirQueue*Q){//设DataType为int型intx;SeqStackS;InitStack(&S);while(!QueueEmpty(Q)){x=DeQueue(Q);Push(&S,x);}while(!StackEmpty(&s)){x=Pop(&S);EnQueue(Q,x);}}//Demo3(5)CirQueueQ1,Q2;//设DataType为int型intx,I,m=0;…//设Q1已有内容,Q2已初始化过while(!QueueEmpty(&Q1)){x=DeQueue(&Q1);EnQueue(&Q2,x);m++;}for(I=0;I{x=DeQueue(&Q2);EnQueue(&Q1,x);EnQueue(&Q2,x);}十六、写一算法voidStrReplace(char*T,char*P,char*S),将T中首次出现的子串P替换为串S。注意:S和P的长度不一定相等。可以使用已有的串操作。 十七、设有三对角矩阵An*n,将其三条对角线上的元素逐行地存储到向量B[0…3n-3]中,使得B[k]=aij,求:(1)用I,j表示k的下标变换公式。(2)用k表示I,j的下标变换公式。 十八、假设在二叉链表中增加两个域:双亲域(parent)以指示其双亲结点;标志域(mark取值0..2)以区分在遍历过程中到达该结点时应继续向左或向右或访问该结点。试以此存储结构编写不用栈进行后序遍历的递推形式的算法。 十九、分别写出下图所示各二叉树的前序、中序和后序序列。 二十、一个深度为h的满k叉树有如下性质:第h层上的结点都是叶子结点,其余各层上每个结点都有k棵非空子树。如果按层次顺序(同层自左至右)从1开始对全部结点编号,问:(1)各层的结点数目是多少?(2)编号为I的结点的双亲结点(若存在)的编号是多少?(3)编号为I的结点的第j个孩子结点(若存在)的编号是多少?(4)编号为I的结点的有右兄弟的条件是什么?其右兄弟的编号是多少? 二十一、编写算法完成下列操作:无重复地输出以孩子兄弟链表存储的树T中所有的边。输出形式为(k1,k2)…(ki,kj)…,其中ki,kj树结点中结点的标识。(提示:修改二叉树遍历的递归算法,使其参数表增加参数father,指向被访问的当前结点的双亲结点。) 二十二、已知以二维数组表示的图的邻接矩阵如下图所示。试分别画出自顶点1出发进行遍历所得的深度优先生成树和广度优先生成树。 二十三、在图所示的各无向图中:(1)找出所有的简单环。(2)哪些图是连通图?对非连通图给出其连通分量。(3)哪些图是自由树(或森林)? 二十四、对下图所示的AOE-网,计算各活动弧的e(ai)和l(aj)函数值、各事件(顶点)的ve(vi)和vl(vj)函数值;列出各条关键路径。 二十五、采用邻接表存储结构,编写一个判别无向图中任意给定的两个顶点之间是否存在一条长度为k的简单路径的算法。二十六、某校学生学号由8位十进制数字组成:c1c2c3c4c5c6c7c8。C1c2为入学时年份的后两砬;c3c4为系别:00~24分别代表该校的25个系:c5为0或1,0表示本科生,1表示研究生;C6c7c8为对某级某系某类学生的顺序编号,对于本科生,它不超过199,对于研究生,它不超过049,共有4个年级,四年级学生1996年入学。(1)当在校生人数达极限情况时,将他们的学号散列到0~24999的地址空间,问装载因子是多少?(2)求一个无冲突的哈希函数H1,它将在校生学号散列到0~24999的地址空间其簇聚性如何?(3)设在校生总数为15000人,散列地址空间为0~19999,你是否能找到一个(2)中要求的H1?若不能,试设计一个哈希函数H2及其解决冲突的方法,使得多数学号可只经一次散列得到(可设各系各年级本科生平均人数为130,研究生平均人数为20)。(4)用算法描述语言表达H2,并写出相应的查找函数。 二十七、在一棵空的二叉查找树中依次插入关键字序列为20、30、8、12、34、5、60、5、1,29,请画出所得到的二叉查找树。 二十八、画出对长度为18的有序的顺序表进行二分查找的判定树,并指出在等概率时查找成功的平均查找长度,以及查找失败时所需的最多的关键字比较次数 二十九、以关键字序列(265,301,751,129,937,863,742,694,076,438)为例,分别写出执行以下排序算法的各趟排序结束时,关键字序列的状态。(1)直接插入排序(2)希尔排序(3)冒泡排序(4)快速排序(5)直接选择排序(6)堆排序(7)归并排序(8)基数排序上述方法中,哪些是稳定的排序?哪些是非稳定的排序?对不稳定的排序试举出一个不稳定的实例。三十、以单链表作为存储结构实现直接插入排序算法。(0012)《数据结构》复习思考题答案一、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。二、下列程序段带标号语句的频度和时间复杂度。(1)I=0; while(I=k) I++;//语句3 return(I);(2)n为不小于1的整数(设k的初值等于1)voidpp(intk) { if(k==n)//语句1 for(I=0;I printf(a[I]);//语句3 else {for(I=k-1;I a[I]=a[I]+I;//语句5 pp(k+1);//语句6 } }//pp(1)这个算法完成在一维数组a[n]中查找给定值k的功能。语句三的频度不仅与问题的规模n有关,还与输入实例中a的各元素取值以及k的取值相关,即与输入实例的初始状态复杂有关。若a中没有与k相等的元素,则语句三的频度为n;若a中的第一个元素a[0]等于k,则语句三的频度是常数0。在这种情况下,可用最坏情况下的时间复杂度作为时间复杂度。在此例中即为O(n)。这样做的原因是:最坏情况下的时间复杂度是在任何输入实例上运行时间的上界。有时,也可能选择将算法的平均(或期望)时间复杂度作为讨论目标。所谓的平均时间复杂度是指所有可能的输入实例以等概率出现的情况下算法的期望运行时间与问题规模的数量级的关系。此例中,以k出现在任何位置的概率相同,都为1/n,则语句三的执行频度为[0+1+2+…+(n-1)]/n=(n-1)/2。它决定了此程序段的平均时间复杂度的数量级为f(n)=n,记作O(n)。(2)在计算包含调用语句的算法的语句频度时,需考虑到调用发生时在被调用算法中各语句的执行情况。本题所示的递归调用较之非递归调用的分析更为复杂。由于k等于n是算法的出口条件,不妨首先分析算法pp(n)的简单情况,这时各语句的执行频度分别为:1,n+1,n,0,0,0;而当k=n-1,n-2,…,1时,语句的执行情况和调度情况,如下表所示。K值不考虑调用时各语句的执行频度调用情况语句1语句2语句3语句4语句5语句6n1n+1n000/n-1100321pp(n)n-2100431pp(n-1)……………………1100n+1n1pp(2)对于k=1即pp(1)而言,各语句的执行次数还须将调用pp(2)时的执行次数累计到内,pp(2)各语句的执行次数又须将调用pp(3)时执行次数累计到内,……由此可的语句频度如下:语句1:1+1+…+1=n语句2:0+0+…+0+(n+1)=n+1语句3:0+0+…+0+n=n语句4:(n+1)+n+…+3=(n-1)(n+4)/2语句5:n+(n-1)+…+2=(n-1)(n+2)/2语句6:1+1+….+1+0=n-1算法的时间复杂度可以基于频度最大的语句,应为O(n2)。三、算法的时间复杂度仅与问题的规模相关吗?不,事实上,算法的时间复杂度不仅与问题的规模相关,还与输入实例中的元素取值等相关,但在最坏的情况下,其时间复杂度就是只与求解问题的规模相关的。我们在讨论时间复杂度时,一般就是以最坏情况下的时间复杂度为准的。 四、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。五、确定下列算法中输出语句的执行次数,并给出算法的时间复杂度。(1)voidcombi(intn){ intI,j,k; for(I=1;I<=n;I++) for(j=I+1;j<=n;j++) for(k=j+1;k<=n;k++) cout<(2)voidbinary(intn){ while(n){ cout< n=n/2; }}(1)I取值范围从1~n,j取值范围从I+1~n,k取值范围从j+1~n,情况如下表所示: I值j值k值输出语句的执行次数123,4,…,nn-2………n-1n1n//234,5,…,nn-3………n-1n1n//…………n-2n-1n1n//n-1n//n///所以,输出语句共执行次数为((n-2)+(n-3)+…+1)+((n-3)+(n-4)+…+1)+…+1=(n-1)(n-2)/2+(n-2)(n-3)/2+…+1=(((n-1)2+(n-2)2+(n-3)2+…+12)-((n-1)+(n-2)+(n-3)+….+1))/2=((n-1)n(2n-1)/6-n(n-1)/2)/2=(n(n-1)(2n-4))/12=n(n-1)(n-2)/6(2)ceil(log2n);六、为什么在单循环链表中设置尾指针比设置头指针更好?答:尾指针是指向终端结点的指针,用它来表示单循环链表可以使得查找链表的开始结点和终端结点都很方便,设一带头结点的单循环链表,其尾指针为rear,则开始结点和终端结点的位置分别是rear->next->next和rear,查找时间都是O(1)。若用头指针来表示该链表,则查找终端结点的时间为O(n)。七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜?答:在实际应用中,应根据具体问题的要求和性质来选择顺序表或链表作为线性表的存储结构,通常有以下几方面的考虑:1.基于空间的考虑。当要求存储的线性表长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序表;反之,当线性表长度变化大,难以估计其存储规模时,采用动态链表作为存储结构为好。2.基于时间的考虑。若线性表的操作主要是进行查找,很少做插入和删除操作时,采用顺序表做存储结构为宜;反之,若需要对线性表进行频繁地插入或删除等的操作时,宜采用链表做存储结构。并且,若链表的插入和删除主要发生在表的首尾两端,则采用尾指针表示的单循环链表为宜。八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。if(I<1||k<0||I+k>a.length)returnERROR;else{for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK更正:for(j=I+k;j<=a.Length;j++)a.elem[j-k]=a.elem[j];a.Length=a.Length–k;九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度算法如下:LinkListLink(LinkListL1,LinkListL2){//将两个单链表连接在一起ListNode*p,*q;p=L1;q=L2;while(p->next)p=p->next;//查找终端结点p->next=q->next;//将L2的开始结点链接在L1之后returnL1;}本算法的主要操作时间花费在查找L1的终端结点上,与L2的长度无关,所以本算的法时间复杂度为:m+1=O(m)十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。要解这样的问题,我们首先想到的是拿链表中的元素一个个地与max和min比较,然后删除这个结点,其实因为已知其是有序链表,所以我们只要找到大于min的结点的直接前趋结点,再找到小于max的结点,然后一并把中间的全部摘掉就可以了。算法如下:voidDeleteList(LinkListL,DataTypemin,DataTypemax){ListNode*p,*q,*r;p=L->next;while(p&&p->data<=min){//找比min大的前一个元素位置q=p;p=p->next;}while(p&&p->data{r=p;p=p->next;free?;//释放这个结点空间}q->next=p;//把断点链上} 十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。答:(1)4132;(2)4213;(3)4231。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。答案:设用变量I表示栈的编号。类型定义:typedefstruct{ElemTypev[m];//栈空间向量inttop[2];//栈顶指针向量}DuStack;栈空条件:s0栈:s->top[0]=-1s1栈:s->top[1]=m栈满条件:s->top[0]+1=s->top[1](此时向量空间全占满)。(1)插入voidpush(DuStack*s,ElemTypex,intI)//当两个栈共享空间时,再由I指定的栈中插入新元素x{if(s->top[0]+1==s->top[1]){printf(“OVERFLOW”);return;}switch(I){case0:s->top[0]++
n=n/2;
}}
六、为什么在单循环链表中设置尾指针比设置头指针更好?
七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜?
八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。
StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。
if(I<1||k<0||I+k>a.length)returnERROR;
else{
for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK 九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。 十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。十五、简述以下算法的功能。(1)voidDemo1(SeqStack*S){intI;arr[64];n=0;while(StackEmpty(S))arr[n++]=Pop(S);for(I=0,I}//Demo1(2)SeqStackS1,S2,tmp;DataTypex;…//假设栈tmp和S2已做过初始化while(!StackEmpty(&S1)){x=Pop(&S1);Push(&tmp,x);}while(!StackEmpty(&tmp)){x=Pop(&tmp);Push(&S1,x);Push(&S2,x);(3)voidDemo2(SeqStack*S,intm){//设DataType为int型SeqStackT;intI;InitStack(&T);while(!StackEmpty(S))if((I=Pop(S))!=m)Push(&T,I);while(!StackEmpty(&T)){I=Pop(&T);Push(S,I);}}(4)voidDemo3(CirQueue*Q){//设DataType为int型intx;SeqStackS;InitStack(&S);while(!QueueEmpty(Q)){x=DeQueue(Q);Push(&S,x);}while(!StackEmpty(&s)){x=Pop(&S);EnQueue(Q,x);}}//Demo3(5)CirQueueQ1,Q2;//设DataType为int型intx,I,m=0;…//设Q1已有内容,Q2已初始化过while(!QueueEmpty(&Q1)){x=DeQueue(&Q1);EnQueue(&Q2,x);m++;}for(I=0;I{x=DeQueue(&Q2);EnQueue(&Q1,x);EnQueue(&Q2,x);}十六、写一算法voidStrReplace(char*T,char*P,char*S),将T中首次出现的子串P替换为串S。注意:S和P的长度不一定相等。可以使用已有的串操作。 十七、设有三对角矩阵An*n,将其三条对角线上的元素逐行地存储到向量B[0…3n-3]中,使得B[k]=aij,求:(1)用I,j表示k的下标变换公式。(2)用k表示I,j的下标变换公式。 十八、假设在二叉链表中增加两个域:双亲域(parent)以指示其双亲结点;标志域(mark取值0..2)以区分在遍历过程中到达该结点时应继续向左或向右或访问该结点。试以此存储结构编写不用栈进行后序遍历的递推形式的算法。 十九、分别写出下图所示各二叉树的前序、中序和后序序列。 二十、一个深度为h的满k叉树有如下性质:第h层上的结点都是叶子结点,其余各层上每个结点都有k棵非空子树。如果按层次顺序(同层自左至右)从1开始对全部结点编号,问:(1)各层的结点数目是多少?(2)编号为I的结点的双亲结点(若存在)的编号是多少?(3)编号为I的结点的第j个孩子结点(若存在)的编号是多少?(4)编号为I的结点的有右兄弟的条件是什么?其右兄弟的编号是多少? 二十一、编写算法完成下列操作:无重复地输出以孩子兄弟链表存储的树T中所有的边。输出形式为(k1,k2)…(ki,kj)…,其中ki,kj树结点中结点的标识。(提示:修改二叉树遍历的递归算法,使其参数表增加参数father,指向被访问的当前结点的双亲结点。) 二十二、已知以二维数组表示的图的邻接矩阵如下图所示。试分别画出自顶点1出发进行遍历所得的深度优先生成树和广度优先生成树。 二十三、在图所示的各无向图中:(1)找出所有的简单环。(2)哪些图是连通图?对非连通图给出其连通分量。(3)哪些图是自由树(或森林)? 二十四、对下图所示的AOE-网,计算各活动弧的e(ai)和l(aj)函数值、各事件(顶点)的ve(vi)和vl(vj)函数值;列出各条关键路径。 二十五、采用邻接表存储结构,编写一个判别无向图中任意给定的两个顶点之间是否存在一条长度为k的简单路径的算法。二十六、某校学生学号由8位十进制数字组成:c1c2c3c4c5c6c7c8。C1c2为入学时年份的后两砬;c3c4为系别:00~24分别代表该校的25个系:c5为0或1,0表示本科生,1表示研究生;C6c7c8为对某级某系某类学生的顺序编号,对于本科生,它不超过199,对于研究生,它不超过049,共有4个年级,四年级学生1996年入学。(1)当在校生人数达极限情况时,将他们的学号散列到0~24999的地址空间,问装载因子是多少?(2)求一个无冲突的哈希函数H1,它将在校生学号散列到0~24999的地址空间其簇聚性如何?(3)设在校生总数为15000人,散列地址空间为0~19999,你是否能找到一个(2)中要求的H1?若不能,试设计一个哈希函数H2及其解决冲突的方法,使得多数学号可只经一次散列得到(可设各系各年级本科生平均人数为130,研究生平均人数为20)。(4)用算法描述语言表达H2,并写出相应的查找函数。 二十七、在一棵空的二叉查找树中依次插入关键字序列为20、30、8、12、34、5、60、5、1,29,请画出所得到的二叉查找树。 二十八、画出对长度为18的有序的顺序表进行二分查找的判定树,并指出在等概率时查找成功的平均查找长度,以及查找失败时所需的最多的关键字比较次数 二十九、以关键字序列(265,301,751,129,937,863,742,694,076,438)为例,分别写出执行以下排序算法的各趟排序结束时,关键字序列的状态。(1)直接插入排序(2)希尔排序(3)冒泡排序(4)快速排序(5)直接选择排序(6)堆排序(7)归并排序(8)基数排序上述方法中,哪些是稳定的排序?哪些是非稳定的排序?对不稳定的排序试举出一个不稳定的实例。三十、以单链表作为存储结构实现直接插入排序算法。(0012)《数据结构》复习思考题答案一、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。二、下列程序段带标号语句的频度和时间复杂度。(1)I=0; while(I=k) I++;//语句3 return(I);(2)n为不小于1的整数(设k的初值等于1)voidpp(intk) { if(k==n)//语句1 for(I=0;I printf(a[I]);//语句3 else {for(I=k-1;I a[I]=a[I]+I;//语句5 pp(k+1);//语句6 } }//pp(1)这个算法完成在一维数组a[n]中查找给定值k的功能。语句三的频度不仅与问题的规模n有关,还与输入实例中a的各元素取值以及k的取值相关,即与输入实例的初始状态复杂有关。若a中没有与k相等的元素,则语句三的频度为n;若a中的第一个元素a[0]等于k,则语句三的频度是常数0。在这种情况下,可用最坏情况下的时间复杂度作为时间复杂度。在此例中即为O(n)。这样做的原因是:最坏情况下的时间复杂度是在任何输入实例上运行时间的上界。有时,也可能选择将算法的平均(或期望)时间复杂度作为讨论目标。所谓的平均时间复杂度是指所有可能的输入实例以等概率出现的情况下算法的期望运行时间与问题规模的数量级的关系。此例中,以k出现在任何位置的概率相同,都为1/n,则语句三的执行频度为[0+1+2+…+(n-1)]/n=(n-1)/2。它决定了此程序段的平均时间复杂度的数量级为f(n)=n,记作O(n)。(2)在计算包含调用语句的算法的语句频度时,需考虑到调用发生时在被调用算法中各语句的执行情况。本题所示的递归调用较之非递归调用的分析更为复杂。由于k等于n是算法的出口条件,不妨首先分析算法pp(n)的简单情况,这时各语句的执行频度分别为:1,n+1,n,0,0,0;而当k=n-1,n-2,…,1时,语句的执行情况和调度情况,如下表所示。K值不考虑调用时各语句的执行频度调用情况语句1语句2语句3语句4语句5语句6n1n+1n000/n-1100321pp(n)n-2100431pp(n-1)……………………1100n+1n1pp(2)对于k=1即pp(1)而言,各语句的执行次数还须将调用pp(2)时的执行次数累计到内,pp(2)各语句的执行次数又须将调用pp(3)时执行次数累计到内,……由此可的语句频度如下:语句1:1+1+…+1=n语句2:0+0+…+0+(n+1)=n+1语句3:0+0+…+0+n=n语句4:(n+1)+n+…+3=(n-1)(n+4)/2语句5:n+(n-1)+…+2=(n-1)(n+2)/2语句6:1+1+….+1+0=n-1算法的时间复杂度可以基于频度最大的语句,应为O(n2)。三、算法的时间复杂度仅与问题的规模相关吗?不,事实上,算法的时间复杂度不仅与问题的规模相关,还与输入实例中的元素取值等相关,但在最坏的情况下,其时间复杂度就是只与求解问题的规模相关的。我们在讨论时间复杂度时,一般就是以最坏情况下的时间复杂度为准的。 四、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。五、确定下列算法中输出语句的执行次数,并给出算法的时间复杂度。(1)voidcombi(intn){ intI,j,k; for(I=1;I<=n;I++) for(j=I+1;j<=n;j++) for(k=j+1;k<=n;k++) cout<(2)voidbinary(intn){ while(n){ cout< n=n/2; }}(1)I取值范围从1~n,j取值范围从I+1~n,k取值范围从j+1~n,情况如下表所示: I值j值k值输出语句的执行次数123,4,…,nn-2………n-1n1n//234,5,…,nn-3………n-1n1n//…………n-2n-1n1n//n-1n//n///所以,输出语句共执行次数为((n-2)+(n-3)+…+1)+((n-3)+(n-4)+…+1)+…+1=(n-1)(n-2)/2+(n-2)(n-3)/2+…+1=(((n-1)2+(n-2)2+(n-3)2+…+12)-((n-1)+(n-2)+(n-3)+….+1))/2=((n-1)n(2n-1)/6-n(n-1)/2)/2=(n(n-1)(2n-4))/12=n(n-1)(n-2)/6(2)ceil(log2n);六、为什么在单循环链表中设置尾指针比设置头指针更好?答:尾指针是指向终端结点的指针,用它来表示单循环链表可以使得查找链表的开始结点和终端结点都很方便,设一带头结点的单循环链表,其尾指针为rear,则开始结点和终端结点的位置分别是rear->next->next和rear,查找时间都是O(1)。若用头指针来表示该链表,则查找终端结点的时间为O(n)。七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜?答:在实际应用中,应根据具体问题的要求和性质来选择顺序表或链表作为线性表的存储结构,通常有以下几方面的考虑:1.基于空间的考虑。当要求存储的线性表长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序表;反之,当线性表长度变化大,难以估计其存储规模时,采用动态链表作为存储结构为好。2.基于时间的考虑。若线性表的操作主要是进行查找,很少做插入和删除操作时,采用顺序表做存储结构为宜;反之,若需要对线性表进行频繁地插入或删除等的操作时,宜采用链表做存储结构。并且,若链表的插入和删除主要发生在表的首尾两端,则采用尾指针表示的单循环链表为宜。八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。if(I<1||k<0||I+k>a.length)returnERROR;else{for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK更正:for(j=I+k;j<=a.Length;j++)a.elem[j-k]=a.elem[j];a.Length=a.Length–k;九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度算法如下:LinkListLink(LinkListL1,LinkListL2){//将两个单链表连接在一起ListNode*p,*q;p=L1;q=L2;while(p->next)p=p->next;//查找终端结点p->next=q->next;//将L2的开始结点链接在L1之后returnL1;}本算法的主要操作时间花费在查找L1的终端结点上,与L2的长度无关,所以本算的法时间复杂度为:m+1=O(m)十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。要解这样的问题,我们首先想到的是拿链表中的元素一个个地与max和min比较,然后删除这个结点,其实因为已知其是有序链表,所以我们只要找到大于min的结点的直接前趋结点,再找到小于max的结点,然后一并把中间的全部摘掉就可以了。算法如下:voidDeleteList(LinkListL,DataTypemin,DataTypemax){ListNode*p,*q,*r;p=L->next;while(p&&p->data<=min){//找比min大的前一个元素位置q=p;p=p->next;}while(p&&p->data{r=p;p=p->next;free?;//释放这个结点空间}q->next=p;//把断点链上} 十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。答:(1)4132;(2)4213;(3)4231。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。答案:设用变量I表示栈的编号。类型定义:typedefstruct{ElemTypev[m];//栈空间向量inttop[2];//栈顶指针向量}DuStack;栈空条件:s0栈:s->top[0]=-1s1栈:s->top[1]=m栈满条件:s->top[0]+1=s->top[1](此时向量空间全占满)。(1)插入voidpush(DuStack*s,ElemTypex,intI)//当两个栈共享空间时,再由I指定的栈中插入新元素x{if(s->top[0]+1==s->top[1]){printf(“OVERFLOW”);return;}switch(I){case0:s->top[0]++
for(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];
a.length--;
rreturnOK;
}//DeleteK
九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。
已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。
十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。
十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。
试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度
十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。
请分析你的算法的时间复杂度。
十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:
(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;
(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;
(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。
十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。
试设计这两个栈的插入和删除算法。
十五、
简述以下算法的功能。
(1)voidDemo1(SeqStack*S){
intI;arr[64];n=0;
while(StackEmpty(S))arr[n++]=Pop(S);
for(I=0,I}//Demo1(2)SeqStackS1,S2,tmp;DataTypex;…//假设栈tmp和S2已做过初始化while(!StackEmpty(&S1)){x=Pop(&S1);Push(&tmp,x);}while(!StackEmpty(&tmp)){x=Pop(&tmp);Push(&S1,x);Push(&S2,x);(3)voidDemo2(SeqStack*S,intm){//设DataType为int型SeqStackT;intI;InitStack(&T);while(!StackEmpty(S))if((I=Pop(S))!=m)Push(&T,I);while(!StackEmpty(&T)){I=Pop(&T);Push(S,I);}}(4)voidDemo3(CirQueue*Q){//设DataType为int型intx;SeqStackS;InitStack(&S);while(!QueueEmpty(Q)){x=DeQueue(Q);Push(&S,x);}while(!StackEmpty(&s)){x=Pop(&S);EnQueue(Q,x);}}//Demo3(5)CirQueueQ1,Q2;//设DataType为int型intx,I,m=0;…//设Q1已有内容,Q2已初始化过while(!QueueEmpty(&Q1)){x=DeQueue(&Q1);EnQueue(&Q2,x);m++;}for(I=0;I{x=DeQueue(&Q2);EnQueue(&Q1,x);EnQueue(&Q2,x);}十六、写一算法voidStrReplace(char*T,char*P,char*S),将T中首次出现的子串P替换为串S。注意:S和P的长度不一定相等。可以使用已有的串操作。 十七、设有三对角矩阵An*n,将其三条对角线上的元素逐行地存储到向量B[0…3n-3]中,使得B[k]=aij,求:(1)用I,j表示k的下标变换公式。(2)用k表示I,j的下标变换公式。 十八、假设在二叉链表中增加两个域:双亲域(parent)以指示其双亲结点;标志域(mark取值0..2)以区分在遍历过程中到达该结点时应继续向左或向右或访问该结点。试以此存储结构编写不用栈进行后序遍历的递推形式的算法。 十九、分别写出下图所示各二叉树的前序、中序和后序序列。 二十、一个深度为h的满k叉树有如下性质:第h层上的结点都是叶子结点,其余各层上每个结点都有k棵非空子树。如果按层次顺序(同层自左至右)从1开始对全部结点编号,问:(1)各层的结点数目是多少?(2)编号为I的结点的双亲结点(若存在)的编号是多少?(3)编号为I的结点的第j个孩子结点(若存在)的编号是多少?(4)编号为I的结点的有右兄弟的条件是什么?其右兄弟的编号是多少? 二十一、编写算法完成下列操作:无重复地输出以孩子兄弟链表存储的树T中所有的边。输出形式为(k1,k2)…(ki,kj)…,其中ki,kj树结点中结点的标识。(提示:修改二叉树遍历的递归算法,使其参数表增加参数father,指向被访问的当前结点的双亲结点。) 二十二、已知以二维数组表示的图的邻接矩阵如下图所示。试分别画出自顶点1出发进行遍历所得的深度优先生成树和广度优先生成树。 二十三、在图所示的各无向图中:(1)找出所有的简单环。(2)哪些图是连通图?对非连通图给出其连通分量。(3)哪些图是自由树(或森林)? 二十四、对下图所示的AOE-网,计算各活动弧的e(ai)和l(aj)函数值、各事件(顶点)的ve(vi)和vl(vj)函数值;列出各条关键路径。 二十五、采用邻接表存储结构,编写一个判别无向图中任意给定的两个顶点之间是否存在一条长度为k的简单路径的算法。二十六、某校学生学号由8位十进制数字组成:c1c2c3c4c5c6c7c8。C1c2为入学时年份的后两砬;c3c4为系别:00~24分别代表该校的25个系:c5为0或1,0表示本科生,1表示研究生;C6c7c8为对某级某系某类学生的顺序编号,对于本科生,它不超过199,对于研究生,它不超过049,共有4个年级,四年级学生1996年入学。(1)当在校生人数达极限情况时,将他们的学号散列到0~24999的地址空间,问装载因子是多少?(2)求一个无冲突的哈希函数H1,它将在校生学号散列到0~24999的地址空间其簇聚性如何?(3)设在校生总数为15000人,散列地址空间为0~19999,你是否能找到一个(2)中要求的H1?若不能,试设计一个哈希函数H2及其解决冲突的方法,使得多数学号可只经一次散列得到(可设各系各年级本科生平均人数为130,研究生平均人数为20)。(4)用算法描述语言表达H2,并写出相应的查找函数。 二十七、在一棵空的二叉查找树中依次插入关键字序列为20、30、8、12、34、5、60、5、1,29,请画出所得到的二叉查找树。 二十八、画出对长度为18的有序的顺序表进行二分查找的判定树,并指出在等概率时查找成功的平均查找长度,以及查找失败时所需的最多的关键字比较次数 二十九、以关键字序列(265,301,751,129,937,863,742,694,076,438)为例,分别写出执行以下排序算法的各趟排序结束时,关键字序列的状态。(1)直接插入排序(2)希尔排序(3)冒泡排序(4)快速排序(5)直接选择排序(6)堆排序(7)归并排序(8)基数排序上述方法中,哪些是稳定的排序?哪些是非稳定的排序?对不稳定的排序试举出一个不稳定的实例。三十、以单链表作为存储结构实现直接插入排序算法。(0012)《数据结构》复习思考题答案一、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。二、下列程序段带标号语句的频度和时间复杂度。(1)I=0; while(I=k) I++;//语句3 return(I);(2)n为不小于1的整数(设k的初值等于1)voidpp(intk) { if(k==n)//语句1 for(I=0;I printf(a[I]);//语句3 else {for(I=k-1;I a[I]=a[I]+I;//语句5 pp(k+1);//语句6 } }//pp(1)这个算法完成在一维数组a[n]中查找给定值k的功能。语句三的频度不仅与问题的规模n有关,还与输入实例中a的各元素取值以及k的取值相关,即与输入实例的初始状态复杂有关。若a中没有与k相等的元素,则语句三的频度为n;若a中的第一个元素a[0]等于k,则语句三的频度是常数0。在这种情况下,可用最坏情况下的时间复杂度作为时间复杂度。在此例中即为O(n)。这样做的原因是:最坏情况下的时间复杂度是在任何输入实例上运行时间的上界。有时,也可能选择将算法的平均(或期望)时间复杂度作为讨论目标。所谓的平均时间复杂度是指所有可能的输入实例以等概率出现的情况下算法的期望运行时间与问题规模的数量级的关系。此例中,以k出现在任何位置的概率相同,都为1/n,则语句三的执行频度为[0+1+2+…+(n-1)]/n=(n-1)/2。它决定了此程序段的平均时间复杂度的数量级为f(n)=n,记作O(n)。(2)在计算包含调用语句的算法的语句频度时,需考虑到调用发生时在被调用算法中各语句的执行情况。本题所示的递归调用较之非递归调用的分析更为复杂。由于k等于n是算法的出口条件,不妨首先分析算法pp(n)的简单情况,这时各语句的执行频度分别为:1,n+1,n,0,0,0;而当k=n-1,n-2,…,1时,语句的执行情况和调度情况,如下表所示。K值不考虑调用时各语句的执行频度调用情况语句1语句2语句3语句4语句5语句6n1n+1n000/n-1100321pp(n)n-2100431pp(n-1)……………………1100n+1n1pp(2)对于k=1即pp(1)而言,各语句的执行次数还须将调用pp(2)时的执行次数累计到内,pp(2)各语句的执行次数又须将调用pp(3)时执行次数累计到内,……由此可的语句频度如下:语句1:1+1+…+1=n语句2:0+0+…+0+(n+1)=n+1语句3:0+0+…+0+n=n语句4:(n+1)+n+…+3=(n-1)(n+4)/2语句5:n+(n-1)+…+2=(n-1)(n+2)/2语句6:1+1+….+1+0=n-1算法的时间复杂度可以基于频度最大的语句,应为O(n2)。三、算法的时间复杂度仅与问题的规模相关吗?不,事实上,算法的时间复杂度不仅与问题的规模相关,还与输入实例中的元素取值等相关,但在最坏的情况下,其时间复杂度就是只与求解问题的规模相关的。我们在讨论时间复杂度时,一般就是以最坏情况下的时间复杂度为准的。 四、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。五、确定下列算法中输出语句的执行次数,并给出算法的时间复杂度。(1)voidcombi(intn){ intI,j,k; for(I=1;I<=n;I++) for(j=I+1;j<=n;j++) for(k=j+1;k<=n;k++) cout<(2)voidbinary(intn){ while(n){ cout< n=n/2; }}(1)I取值范围从1~n,j取值范围从I+1~n,k取值范围从j+1~n,情况如下表所示: I值j值k值输出语句的执行次数123,4,…,nn-2………n-1n1n//234,5,…,nn-3………n-1n1n//…………n-2n-1n1n//n-1n//n///所以,输出语句共执行次数为((n-2)+(n-3)+…+1)+((n-3)+(n-4)+…+1)+…+1=(n-1)(n-2)/2+(n-2)(n-3)/2+…+1=(((n-1)2+(n-2)2+(n-3)2+…+12)-((n-1)+(n-2)+(n-3)+….+1))/2=((n-1)n(2n-1)/6-n(n-1)/2)/2=(n(n-1)(2n-4))/12=n(n-1)(n-2)/6(2)ceil(log2n);六、为什么在单循环链表中设置尾指针比设置头指针更好?答:尾指针是指向终端结点的指针,用它来表示单循环链表可以使得查找链表的开始结点和终端结点都很方便,设一带头结点的单循环链表,其尾指针为rear,则开始结点和终端结点的位置分别是rear->next->next和rear,查找时间都是O(1)。若用头指针来表示该链表,则查找终端结点的时间为O(n)。七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜?答:在实际应用中,应根据具体问题的要求和性质来选择顺序表或链表作为线性表的存储结构,通常有以下几方面的考虑:1.基于空间的考虑。当要求存储的线性表长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序表;反之,当线性表长度变化大,难以估计其存储规模时,采用动态链表作为存储结构为好。2.基于时间的考虑。若线性表的操作主要是进行查找,很少做插入和删除操作时,采用顺序表做存储结构为宜;反之,若需要对线性表进行频繁地插入或删除等的操作时,宜采用链表做存储结构。并且,若链表的插入和删除主要发生在表的首尾两端,则采用尾指针表示的单循环链表为宜。八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。if(I<1||k<0||I+k>a.length)returnERROR;else{for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK更正:for(j=I+k;j<=a.Length;j++)a.elem[j-k]=a.elem[j];a.Length=a.Length–k;九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度算法如下:LinkListLink(LinkListL1,LinkListL2){//将两个单链表连接在一起ListNode*p,*q;p=L1;q=L2;while(p->next)p=p->next;//查找终端结点p->next=q->next;//将L2的开始结点链接在L1之后returnL1;}本算法的主要操作时间花费在查找L1的终端结点上,与L2的长度无关,所以本算的法时间复杂度为:m+1=O(m)十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。要解这样的问题,我们首先想到的是拿链表中的元素一个个地与max和min比较,然后删除这个结点,其实因为已知其是有序链表,所以我们只要找到大于min的结点的直接前趋结点,再找到小于max的结点,然后一并把中间的全部摘掉就可以了。算法如下:voidDeleteList(LinkListL,DataTypemin,DataTypemax){ListNode*p,*q,*r;p=L->next;while(p&&p->data<=min){//找比min大的前一个元素位置q=p;p=p->next;}while(p&&p->data{r=p;p=p->next;free?;//释放这个结点空间}q->next=p;//把断点链上} 十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。答:(1)4132;(2)4213;(3)4231。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。答案:设用变量I表示栈的编号。类型定义:typedefstruct{ElemTypev[m];//栈空间向量inttop[2];//栈顶指针向量}DuStack;栈空条件:s0栈:s->top[0]=-1s1栈:s->top[1]=m栈满条件:s->top[0]+1=s->top[1](此时向量空间全占满)。(1)插入voidpush(DuStack*s,ElemTypex,intI)//当两个栈共享空间时,再由I指定的栈中插入新元素x{if(s->top[0]+1==s->top[1]){printf(“OVERFLOW”);return;}switch(I){case0:s->top[0]++
}//Demo1
(2)SeqStackS1,S2,tmp;
DataTypex;
…//假设栈tmp和S2已做过初始化
while(!
StackEmpty(&S1)){
x=Pop(&S1);
Push(&tmp,x);
StackEmpty(&tmp)){
x=Pop(&tmp);
Push(&S1,x);
Push(&S2,x);
(3)voidDemo2(SeqStack*S,intm){
//设DataType为int型
SeqStackT;intI;
InitStack(&T);
StackEmpty(S))
if((I=Pop(S))!
=m)Push(&T,I);
StackEmpty(&T)){
I=Pop(&T);Push(S,I);
(4)voidDemo3(CirQueue*Q){
intx;SeqStackS;
InitStack(&S);
QueueEmpty(Q))
{x=DeQueue(Q);Push(&S,x);}
StackEmpty(&s))
{x=Pop(&S);EnQueue(Q,x);}
}//Demo3
(5)CirQueueQ1,Q2;//设DataType为int型
intx,I,m=0;
…//设Q1已有内容,Q2已初始化过
QueueEmpty(&Q1))
{x=DeQueue(&Q1);EnQueue(&Q2,x);m++;}
for(I=0;I{x=DeQueue(&Q2);EnQueue(&Q1,x);EnQueue(&Q2,x);}十六、写一算法voidStrReplace(char*T,char*P,char*S),将T中首次出现的子串P替换为串S。注意:S和P的长度不一定相等。可以使用已有的串操作。 十七、设有三对角矩阵An*n,将其三条对角线上的元素逐行地存储到向量B[0…3n-3]中,使得B[k]=aij,求:(1)用I,j表示k的下标变换公式。(2)用k表示I,j的下标变换公式。 十八、假设在二叉链表中增加两个域:双亲域(parent)以指示其双亲结点;标志域(mark取值0..2)以区分在遍历过程中到达该结点时应继续向左或向右或访问该结点。试以此存储结构编写不用栈进行后序遍历的递推形式的算法。 十九、分别写出下图所示各二叉树的前序、中序和后序序列。 二十、一个深度为h的满k叉树有如下性质:第h层上的结点都是叶子结点,其余各层上每个结点都有k棵非空子树。如果按层次顺序(同层自左至右)从1开始对全部结点编号,问:(1)各层的结点数目是多少?(2)编号为I的结点的双亲结点(若存在)的编号是多少?(3)编号为I的结点的第j个孩子结点(若存在)的编号是多少?(4)编号为I的结点的有右兄弟的条件是什么?其右兄弟的编号是多少? 二十一、编写算法完成下列操作:无重复地输出以孩子兄弟链表存储的树T中所有的边。输出形式为(k1,k2)…(ki,kj)…,其中ki,kj树结点中结点的标识。(提示:修改二叉树遍历的递归算法,使其参数表增加参数father,指向被访问的当前结点的双亲结点。) 二十二、已知以二维数组表示的图的邻接矩阵如下图所示。试分别画出自顶点1出发进行遍历所得的深度优先生成树和广度优先生成树。 二十三、在图所示的各无向图中:(1)找出所有的简单环。(2)哪些图是连通图?对非连通图给出其连通分量。(3)哪些图是自由树(或森林)? 二十四、对下图所示的AOE-网,计算各活动弧的e(ai)和l(aj)函数值、各事件(顶点)的ve(vi)和vl(vj)函数值;列出各条关键路径。 二十五、采用邻接表存储结构,编写一个判别无向图中任意给定的两个顶点之间是否存在一条长度为k的简单路径的算法。二十六、某校学生学号由8位十进制数字组成:c1c2c3c4c5c6c7c8。C1c2为入学时年份的后两砬;c3c4为系别:00~24分别代表该校的25个系:c5为0或1,0表示本科生,1表示研究生;C6c7c8为对某级某系某类学生的顺序编号,对于本科生,它不超过199,对于研究生,它不超过049,共有4个年级,四年级学生1996年入学。(1)当在校生人数达极限情况时,将他们的学号散列到0~24999的地址空间,问装载因子是多少?(2)求一个无冲突的哈希函数H1,它将在校生学号散列到0~24999的地址空间其簇聚性如何?(3)设在校生总数为15000人,散列地址空间为0~19999,你是否能找到一个(2)中要求的H1?若不能,试设计一个哈希函数H2及其解决冲突的方法,使得多数学号可只经一次散列得到(可设各系各年级本科生平均人数为130,研究生平均人数为20)。(4)用算法描述语言表达H2,并写出相应的查找函数。 二十七、在一棵空的二叉查找树中依次插入关键字序列为20、30、8、12、34、5、60、5、1,29,请画出所得到的二叉查找树。 二十八、画出对长度为18的有序的顺序表进行二分查找的判定树,并指出在等概率时查找成功的平均查找长度,以及查找失败时所需的最多的关键字比较次数 二十九、以关键字序列(265,301,751,129,937,863,742,694,076,438)为例,分别写出执行以下排序算法的各趟排序结束时,关键字序列的状态。(1)直接插入排序(2)希尔排序(3)冒泡排序(4)快速排序(5)直接选择排序(6)堆排序(7)归并排序(8)基数排序上述方法中,哪些是稳定的排序?哪些是非稳定的排序?对不稳定的排序试举出一个不稳定的实例。三十、以单链表作为存储结构实现直接插入排序算法。(0012)《数据结构》复习思考题答案一、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。二、下列程序段带标号语句的频度和时间复杂度。(1)I=0; while(I=k) I++;//语句3 return(I);(2)n为不小于1的整数(设k的初值等于1)voidpp(intk) { if(k==n)//语句1 for(I=0;I printf(a[I]);//语句3 else {for(I=k-1;I a[I]=a[I]+I;//语句5 pp(k+1);//语句6 } }//pp(1)这个算法完成在一维数组a[n]中查找给定值k的功能。语句三的频度不仅与问题的规模n有关,还与输入实例中a的各元素取值以及k的取值相关,即与输入实例的初始状态复杂有关。若a中没有与k相等的元素,则语句三的频度为n;若a中的第一个元素a[0]等于k,则语句三的频度是常数0。在这种情况下,可用最坏情况下的时间复杂度作为时间复杂度。在此例中即为O(n)。这样做的原因是:最坏情况下的时间复杂度是在任何输入实例上运行时间的上界。有时,也可能选择将算法的平均(或期望)时间复杂度作为讨论目标。所谓的平均时间复杂度是指所有可能的输入实例以等概率出现的情况下算法的期望运行时间与问题规模的数量级的关系。此例中,以k出现在任何位置的概率相同,都为1/n,则语句三的执行频度为[0+1+2+…+(n-1)]/n=(n-1)/2。它决定了此程序段的平均时间复杂度的数量级为f(n)=n,记作O(n)。(2)在计算包含调用语句的算法的语句频度时,需考虑到调用发生时在被调用算法中各语句的执行情况。本题所示的递归调用较之非递归调用的分析更为复杂。由于k等于n是算法的出口条件,不妨首先分析算法pp(n)的简单情况,这时各语句的执行频度分别为:1,n+1,n,0,0,0;而当k=n-1,n-2,…,1时,语句的执行情况和调度情况,如下表所示。K值不考虑调用时各语句的执行频度调用情况语句1语句2语句3语句4语句5语句6n1n+1n000/n-1100321pp(n)n-2100431pp(n-1)……………………1100n+1n1pp(2)对于k=1即pp(1)而言,各语句的执行次数还须将调用pp(2)时的执行次数累计到内,pp(2)各语句的执行次数又须将调用pp(3)时执行次数累计到内,……由此可的语句频度如下:语句1:1+1+…+1=n语句2:0+0+…+0+(n+1)=n+1语句3:0+0+…+0+n=n语句4:(n+1)+n+…+3=(n-1)(n+4)/2语句5:n+(n-1)+…+2=(n-1)(n+2)/2语句6:1+1+….+1+0=n-1算法的时间复杂度可以基于频度最大的语句,应为O(n2)。三、算法的时间复杂度仅与问题的规模相关吗?不,事实上,算法的时间复杂度不仅与问题的规模相关,还与输入实例中的元素取值等相关,但在最坏的情况下,其时间复杂度就是只与求解问题的规模相关的。我们在讨论时间复杂度时,一般就是以最坏情况下的时间复杂度为准的。 四、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。五、确定下列算法中输出语句的执行次数,并给出算法的时间复杂度。(1)voidcombi(intn){ intI,j,k; for(I=1;I<=n;I++) for(j=I+1;j<=n;j++) for(k=j+1;k<=n;k++) cout<(2)voidbinary(intn){ while(n){ cout< n=n/2; }}(1)I取值范围从1~n,j取值范围从I+1~n,k取值范围从j+1~n,情况如下表所示: I值j值k值输出语句的执行次数123,4,…,nn-2………n-1n1n//234,5,…,nn-3………n-1n1n//…………n-2n-1n1n//n-1n//n///所以,输出语句共执行次数为((n-2)+(n-3)+…+1)+((n-3)+(n-4)+…+1)+…+1=(n-1)(n-2)/2+(n-2)(n-3)/2+…+1=(((n-1)2+(n-2)2+(n-3)2+…+12)-((n-1)+(n-2)+(n-3)+….+1))/2=((n-1)n(2n-1)/6-n(n-1)/2)/2=(n(n-1)(2n-4))/12=n(n-1)(n-2)/6(2)ceil(log2n);六、为什么在单循环链表中设置尾指针比设置头指针更好?答:尾指针是指向终端结点的指针,用它来表示单循环链表可以使得查找链表的开始结点和终端结点都很方便,设一带头结点的单循环链表,其尾指针为rear,则开始结点和终端结点的位置分别是rear->next->next和rear,查找时间都是O(1)。若用头指针来表示该链表,则查找终端结点的时间为O(n)。七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜?答:在实际应用中,应根据具体问题的要求和性质来选择顺序表或链表作为线性表的存储结构,通常有以下几方面的考虑:1.基于空间的考虑。当要求存储的线性表长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序表;反之,当线性表长度变化大,难以估计其存储规模时,采用动态链表作为存储结构为好。2.基于时间的考虑。若线性表的操作主要是进行查找,很少做插入和删除操作时,采用顺序表做存储结构为宜;反之,若需要对线性表进行频繁地插入或删除等的操作时,宜采用链表做存储结构。并且,若链表的插入和删除主要发生在表的首尾两端,则采用尾指针表示的单循环链表为宜。八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。if(I<1||k<0||I+k>a.length)returnERROR;else{for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK更正:for(j=I+k;j<=a.Length;j++)a.elem[j-k]=a.elem[j];a.Length=a.Length–k;九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度算法如下:LinkListLink(LinkListL1,LinkListL2){//将两个单链表连接在一起ListNode*p,*q;p=L1;q=L2;while(p->next)p=p->next;//查找终端结点p->next=q->next;//将L2的开始结点链接在L1之后returnL1;}本算法的主要操作时间花费在查找L1的终端结点上,与L2的长度无关,所以本算的法时间复杂度为:m+1=O(m)十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。要解这样的问题,我们首先想到的是拿链表中的元素一个个地与max和min比较,然后删除这个结点,其实因为已知其是有序链表,所以我们只要找到大于min的结点的直接前趋结点,再找到小于max的结点,然后一并把中间的全部摘掉就可以了。算法如下:voidDeleteList(LinkListL,DataTypemin,DataTypemax){ListNode*p,*q,*r;p=L->next;while(p&&p->data<=min){//找比min大的前一个元素位置q=p;p=p->next;}while(p&&p->data{r=p;p=p->next;free?;//释放这个结点空间}q->next=p;//把断点链上} 十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。答:(1)4132;(2)4213;(3)4231。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。答案:设用变量I表示栈的编号。类型定义:typedefstruct{ElemTypev[m];//栈空间向量inttop[2];//栈顶指针向量}DuStack;栈空条件:s0栈:s->top[0]=-1s1栈:s->top[1]=m栈满条件:s->top[0]+1=s->top[1](此时向量空间全占满)。(1)插入voidpush(DuStack*s,ElemTypex,intI)//当两个栈共享空间时,再由I指定的栈中插入新元素x{if(s->top[0]+1==s->top[1]){printf(“OVERFLOW”);return;}switch(I){case0:s->top[0]++
{x=DeQueue(&Q2);EnQueue(&Q1,x);EnQueue(&Q2,x);}
十六、写一算法voidStrReplace(char*T,char*P,char*S),将T中首次出现的子串P替换为串S。
注意:
S和P的长度不一定相等。
可以使用已有的串操作。
十七、设有三对角矩阵An*n,将其三条对角线上的元素逐行地存储到向量B[0…3n-3]中,使得B[k]=aij,求:
(1)用I,j表示k的下标变换公式。
(2)用k表示I,j的下标变换公式。
十八、假设在二叉链表中增加两个域:
双亲域(parent)以指示其双亲结点;标志域(mark取值0..2)以区分在遍历过程中到达该结点时应继续向左或向右或访问该结点。
试以此存储结构编写不用栈进行后序遍历的递推形式的算法。
十九、分别写出下图所示各二叉树的前序、中序和后序序列。
二十、一个深度为h的满k叉树有如下性质:
第h层上的结点都是叶子结点,其余各层上每个结点都有k棵非空子树。
如果按层次顺序(同层自左至右)从1开始对全部结点编号,问:
(1)各层的结点数目是多少?
(2)编号为I的结点的双亲结点(若存在)的编号是多少?
(3)编号为I的结点的第j个孩子结点(若存在)的编号是多少?
(4)编号为I的结点的有右兄弟的条件是什么?
其右兄弟的编号是多少?
二十一、编写算法完成下列操作:
无重复地输出以孩子兄弟链表存储的树T中所有的边。
输出形式为(k1,k2)…(ki,kj)…,其中ki,kj树结点中结点的标识。
(提示:
修改二叉树遍历的递归算法,使其参数表增加参数father,指向被访问的当前结点的双亲结点。
)
二十二、已知以二维数组表示的图的邻接矩阵如下图所示。
试分别画出自顶点1出发进行遍历所得的深度优先生成树和广度优先生成树。
二十三、
在图所示的各无向图中:
(1)找出所有的简单环。
(2)哪些图是连通图?
对非连通图给出其连通分量。
(3)哪些图是自由树(或森林)?
二十四、对下图所示的AOE-网,计算各活动弧的e(ai)和l(aj)函数值、各事件(顶点)的ve(vi)和vl(vj)函数值;列出各
条关键路径。
二十五、采用邻接表存储结构,编写一个判别无向图中任意给定的两个顶点之间是否存在一条长度为k的简单路径的算法。
二十六、某校学生学号由8位十进制数字组成:
c1c2c3c4c5c6c7c8。
C1c2为入学时年份的后两砬;c3c4为系别:
00~24分别代表该校的25个系:
c5为0或1,0表示本科生,1表示研究生;C6c7c8为对某级某系某类学生的顺序编号,对于本科生,它不超过199,对于研究生,它不超过049,共有4个年级,四年级学生1996年入学。
(1)当在校生人数达极限情况时,将他们的学号散列到0~24999的地址空间,问装载因子是多少?
(2)求一个无冲突的哈希函数H1,它将在校生学号散列到0~24999的地址空间其簇聚性如何?
(3)设在校生总数为15000人,散列地址空间为0~19999,你是否能找到一个
(2)中要求的H1?
若不能,试设计一个哈希函数H2及其解决冲突的方法,使得多数学号可只经一次散列得到(可设各系各年级本科生平均人数为130,研究生平均人数为20)。
(4)用算法描述语言表达H2,并写出相应的查找函数。
二十七、
在一棵空的二叉查找树中依次插入关键字序列为20、30、8、12、34、5、60、5、1,29,请画出所得到的二叉查找树。
二十八、
画出对长度为18的有序的顺序表进行二分查找的判定树,并指出在等概率时查找成功的平均查找长度,以及查找失败时所需的最多的关键字比较次数
二十九、以关键字序列(265,301,751,129,937,863,742,694,076,438)为例,分别写出执行以下排序算法的各趟排序结束时,关键字序列的状态。
(1)直接插入排序
(2)希尔排序(3)冒泡排序(4)快速排序(5)直接选择排序(6)堆排序(7)归并排序(8)基数排序上述方法中,哪些是稳定的排序?
哪些是非稳定的排序?
对不稳定的排序试举出一个不稳定的实例。
三十、以单链表作为存储结构实现直接插入排序算法。
(0012)《数据结构》复习思考题答案
一、
常用的存储表示方法有哪几种?
常用的存储表示方法有四种:
顺序存储方法:
它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。
由此得到的存储表示称为顺序存储结构。
链接存储方法:
它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。
由此得到的存储表示称为链式存储结构。
索引存储方法:
除建立存储结点信息外,还建立附加的索引表来标识结点的地址。
散列存储方法:
就是根据结点的关键字直接计算出该结点的存储地址。
二、
下列程序段带标号语句的频度和时间复杂度。
while(I=k) I++;//语句3 return(I);(2)n为不小于1的整数(设k的初值等于1)voidpp(intk) { if(k==n)//语句1 for(I=0;I printf(a[I]);//语句3 else {for(I=k-1;I a[I]=a[I]+I;//语句5 pp(k+1);//语句6 } }//pp(1)这个算法完成在一维数组a[n]中查找给定值k的功能。语句三的频度不仅与问题的规模n有关,还与输入实例中a的各元素取值以及k的取值相关,即与输入实例的初始状态复杂有关。若a中没有与k相等的元素,则语句三的频度为n;若a中的第一个元素a[0]等于k,则语句三的频度是常数0。在这种情况下,可用最坏情况下的时间复杂度作为时间复杂度。在此例中即为O(n)。这样做的原因是:最坏情况下的时间复杂度是在任何输入实例上运行时间的上界。有时,也可能选择将算法的平均(或期望)时间复杂度作为讨论目标。所谓的平均时间复杂度是指所有可能的输入实例以等概率出现的情况下算法的期望运行时间与问题规模的数量级的关系。此例中,以k出现在任何位置的概率相同,都为1/n,则语句三的执行频度为[0+1+2+…+(n-1)]/n=(n-1)/2。它决定了此程序段的平均时间复杂度的数量级为f(n)=n,记作O(n)。(2)在计算包含调用语句的算法的语句频度时,需考虑到调用发生时在被调用算法中各语句的执行情况。本题所示的递归调用较之非递归调用的分析更为复杂。由于k等于n是算法的出口条件,不妨首先分析算法pp(n)的简单情况,这时各语句的执行频度分别为:1,n+1,n,0,0,0;而当k=n-1,n-2,…,1时,语句的执行情况和调度情况,如下表所示。K值不考虑调用时各语句的执行频度调用情况语句1语句2语句3语句4语句5语句6n1n+1n000/n-1100321pp(n)n-2100431pp(n-1)……………………1100n+1n1pp(2)对于k=1即pp(1)而言,各语句的执行次数还须将调用pp(2)时的执行次数累计到内,pp(2)各语句的执行次数又须将调用pp(3)时执行次数累计到内,……由此可的语句频度如下:语句1:1+1+…+1=n语句2:0+0+…+0+(n+1)=n+1语句3:0+0+…+0+n=n语句4:(n+1)+n+…+3=(n-1)(n+4)/2语句5:n+(n-1)+…+2=(n-1)(n+2)/2语句6:1+1+….+1+0=n-1算法的时间复杂度可以基于频度最大的语句,应为O(n2)。三、算法的时间复杂度仅与问题的规模相关吗?不,事实上,算法的时间复杂度不仅与问题的规模相关,还与输入实例中的元素取值等相关,但在最坏的情况下,其时间复杂度就是只与求解问题的规模相关的。我们在讨论时间复杂度时,一般就是以最坏情况下的时间复杂度为准的。 四、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。五、确定下列算法中输出语句的执行次数,并给出算法的时间复杂度。(1)voidcombi(intn){ intI,j,k; for(I=1;I<=n;I++) for(j=I+1;j<=n;j++) for(k=j+1;k<=n;k++) cout<(2)voidbinary(intn){ while(n){ cout< n=n/2; }}(1)I取值范围从1~n,j取值范围从I+1~n,k取值范围从j+1~n,情况如下表所示: I值j值k值输出语句的执行次数123,4,…,nn-2………n-1n1n//234,5,…,nn-3………n-1n1n//…………n-2n-1n1n//n-1n//n///所以,输出语句共执行次数为((n-2)+(n-3)+…+1)+((n-3)+(n-4)+…+1)+…+1=(n-1)(n-2)/2+(n-2)(n-3)/2+…+1=(((n-1)2+(n-2)2+(n-3)2+…+12)-((n-1)+(n-2)+(n-3)+….+1))/2=((n-1)n(2n-1)/6-n(n-1)/2)/2=(n(n-1)(2n-4))/12=n(n-1)(n-2)/6(2)ceil(log2n);六、为什么在单循环链表中设置尾指针比设置头指针更好?答:尾指针是指向终端结点的指针,用它来表示单循环链表可以使得查找链表的开始结点和终端结点都很方便,设一带头结点的单循环链表,其尾指针为rear,则开始结点和终端结点的位置分别是rear->next->next和rear,查找时间都是O(1)。若用头指针来表示该链表,则查找终端结点的时间为O(n)。七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜?答:在实际应用中,应根据具体问题的要求和性质来选择顺序表或链表作为线性表的存储结构,通常有以下几方面的考虑:1.基于空间的考虑。当要求存储的线性表长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序表;反之,当线性表长度变化大,难以估计其存储规模时,采用动态链表作为存储结构为好。2.基于时间的考虑。若线性表的操作主要是进行查找,很少做插入和删除操作时,采用顺序表做存储结构为宜;反之,若需要对线性表进行频繁地插入或删除等的操作时,宜采用链表做存储结构。并且,若链表的插入和删除主要发生在表的首尾两端,则采用尾指针表示的单循环链表为宜。八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。if(I<1||k<0||I+k>a.length)returnERROR;else{for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK更正:for(j=I+k;j<=a.Length;j++)a.elem[j-k]=a.elem[j];a.Length=a.Length–k;九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度算法如下:LinkListLink(LinkListL1,LinkListL2){//将两个单链表连接在一起ListNode*p,*q;p=L1;q=L2;while(p->next)p=p->next;//查找终端结点p->next=q->next;//将L2的开始结点链接在L1之后returnL1;}本算法的主要操作时间花费在查找L1的终端结点上,与L2的长度无关,所以本算的法时间复杂度为:m+1=O(m)十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。要解这样的问题,我们首先想到的是拿链表中的元素一个个地与max和min比较,然后删除这个结点,其实因为已知其是有序链表,所以我们只要找到大于min的结点的直接前趋结点,再找到小于max的结点,然后一并把中间的全部摘掉就可以了。算法如下:voidDeleteList(LinkListL,DataTypemin,DataTypemax){ListNode*p,*q,*r;p=L->next;while(p&&p->data<=min){//找比min大的前一个元素位置q=p;p=p->next;}while(p&&p->data{r=p;p=p->next;free?;//释放这个结点空间}q->next=p;//把断点链上} 十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。答:(1)4132;(2)4213;(3)4231。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。答案:设用变量I表示栈的编号。类型定义:typedefstruct{ElemTypev[m];//栈空间向量inttop[2];//栈顶指针向量}DuStack;栈空条件:s0栈:s->top[0]=-1s1栈:s->top[1]=m栈满条件:s->top[0]+1=s->top[1](此时向量空间全占满)。(1)插入voidpush(DuStack*s,ElemTypex,intI)//当两个栈共享空间时,再由I指定的栈中插入新元素x{if(s->top[0]+1==s->top[1]){printf(“OVERFLOW”);return;}switch(I){case0:s->top[0]++
for(I=0;I printf(a[I]);//语句3 else {for(I=k-1;I a[I]=a[I]+I;//语句5 pp(k+1);//语句6 } }//pp(1)这个算法完成在一维数组a[n]中查找给定值k的功能。语句三的频度不仅与问题的规模n有关,还与输入实例中a的各元素取值以及k的取值相关,即与输入实例的初始状态复杂有关。若a中没有与k相等的元素,则语句三的频度为n;若a中的第一个元素a[0]等于k,则语句三的频度是常数0。在这种情况下,可用最坏情况下的时间复杂度作为时间复杂度。在此例中即为O(n)。这样做的原因是:最坏情况下的时间复杂度是在任何输入实例上运行时间的上界。有时,也可能选择将算法的平均(或期望)时间复杂度作为讨论目标。所谓的平均时间复杂度是指所有可能的输入实例以等概率出现的情况下算法的期望运行时间与问题规模的数量级的关系。此例中,以k出现在任何位置的概率相同,都为1/n,则语句三的执行频度为[0+1+2+…+(n-1)]/n=(n-1)/2。它决定了此程序段的平均时间复杂度的数量级为f(n)=n,记作O(n)。(2)在计算包含调用语句的算法的语句频度时,需考虑到调用发生时在被调用算法中各语句的执行情况。本题所示的递归调用较之非递归调用的分析更为复杂。由于k等于n是算法的出口条件,不妨首先分析算法pp(n)的简单情况,这时各语句的执行频度分别为:1,n+1,n,0,0,0;而当k=n-1,n-2,…,1时,语句的执行情况和调度情况,如下表所示。K值不考虑调用时各语句的执行频度调用情况语句1语句2语句3语句4语句5语句6n1n+1n000/n-1100321pp(n)n-2100431pp(n-1)……………………1100n+1n1pp(2)对于k=1即pp(1)而言,各语句的执行次数还须将调用pp(2)时的执行次数累计到内,pp(2)各语句的执行次数又须将调用pp(3)时执行次数累计到内,……由此可的语句频度如下:语句1:1+1+…+1=n语句2:0+0+…+0+(n+1)=n+1语句3:0+0+…+0+n=n语句4:(n+1)+n+…+3=(n-1)(n+4)/2语句5:n+(n-1)+…+2=(n-1)(n+2)/2语句6:1+1+….+1+0=n-1算法的时间复杂度可以基于频度最大的语句,应为O(n2)。三、算法的时间复杂度仅与问题的规模相关吗?不,事实上,算法的时间复杂度不仅与问题的规模相关,还与输入实例中的元素取值等相关,但在最坏的情况下,其时间复杂度就是只与求解问题的规模相关的。我们在讨论时间复杂度时,一般就是以最坏情况下的时间复杂度为准的。 四、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。五、确定下列算法中输出语句的执行次数,并给出算法的时间复杂度。(1)voidcombi(intn){ intI,j,k; for(I=1;I<=n;I++) for(j=I+1;j<=n;j++) for(k=j+1;k<=n;k++) cout<(2)voidbinary(intn){ while(n){ cout< n=n/2; }}(1)I取值范围从1~n,j取值范围从I+1~n,k取值范围从j+1~n,情况如下表所示: I值j值k值输出语句的执行次数123,4,…,nn-2………n-1n1n//234,5,…,nn-3………n-1n1n//…………n-2n-1n1n//n-1n//n///所以,输出语句共执行次数为((n-2)+(n-3)+…+1)+((n-3)+(n-4)+…+1)+…+1=(n-1)(n-2)/2+(n-2)(n-3)/2+…+1=(((n-1)2+(n-2)2+(n-3)2+…+12)-((n-1)+(n-2)+(n-3)+….+1))/2=((n-1)n(2n-1)/6-n(n-1)/2)/2=(n(n-1)(2n-4))/12=n(n-1)(n-2)/6(2)ceil(log2n);六、为什么在单循环链表中设置尾指针比设置头指针更好?答:尾指针是指向终端结点的指针,用它来表示单循环链表可以使得查找链表的开始结点和终端结点都很方便,设一带头结点的单循环链表,其尾指针为rear,则开始结点和终端结点的位置分别是rear->next->next和rear,查找时间都是O(1)。若用头指针来表示该链表,则查找终端结点的时间为O(n)。七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜?答:在实际应用中,应根据具体问题的要求和性质来选择顺序表或链表作为线性表的存储结构,通常有以下几方面的考虑:1.基于空间的考虑。当要求存储的线性表长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序表;反之,当线性表长度变化大,难以估计其存储规模时,采用动态链表作为存储结构为好。2.基于时间的考虑。若线性表的操作主要是进行查找,很少做插入和删除操作时,采用顺序表做存储结构为宜;反之,若需要对线性表进行频繁地插入或删除等的操作时,宜采用链表做存储结构。并且,若链表的插入和删除主要发生在表的首尾两端,则采用尾指针表示的单循环链表为宜。八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。if(I<1||k<0||I+k>a.length)returnERROR;else{for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK更正:for(j=I+k;j<=a.Length;j++)a.elem[j-k]=a.elem[j];a.Length=a.Length–k;九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度算法如下:LinkListLink(LinkListL1,LinkListL2){//将两个单链表连接在一起ListNode*p,*q;p=L1;q=L2;while(p->next)p=p->next;//查找终端结点p->next=q->next;//将L2的开始结点链接在L1之后returnL1;}本算法的主要操作时间花费在查找L1的终端结点上,与L2的长度无关,所以本算的法时间复杂度为:m+1=O(m)十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。要解这样的问题,我们首先想到的是拿链表中的元素一个个地与max和min比较,然后删除这个结点,其实因为已知其是有序链表,所以我们只要找到大于min的结点的直接前趋结点,再找到小于max的结点,然后一并把中间的全部摘掉就可以了。算法如下:voidDeleteList(LinkListL,DataTypemin,DataTypemax){ListNode*p,*q,*r;p=L->next;while(p&&p->data<=min){//找比min大的前一个元素位置q=p;p=p->next;}while(p&&p->data{r=p;p=p->next;free?;//释放这个结点空间}q->next=p;//把断点链上} 十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。答:(1)4132;(2)4213;(3)4231。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。答案:设用变量I表示栈的编号。类型定义:typedefstruct{ElemTypev[m];//栈空间向量inttop[2];//栈顶指针向量}DuStack;栈空条件:s0栈:s->top[0]=-1s1栈:s->top[1]=m栈满条件:s->top[0]+1=s->top[1](此时向量空间全占满)。(1)插入voidpush(DuStack*s,ElemTypex,intI)//当两个栈共享空间时,再由I指定的栈中插入新元素x{if(s->top[0]+1==s->top[1]){printf(“OVERFLOW”);return;}switch(I){case0:s->top[0]++
{for(I=k-1;I a[I]=a[I]+I;//语句5 pp(k+1);//语句6 } }//pp(1)这个算法完成在一维数组a[n]中查找给定值k的功能。语句三的频度不仅与问题的规模n有关,还与输入实例中a的各元素取值以及k的取值相关,即与输入实例的初始状态复杂有关。若a中没有与k相等的元素,则语句三的频度为n;若a中的第一个元素a[0]等于k,则语句三的频度是常数0。在这种情况下,可用最坏情况下的时间复杂度作为时间复杂度。在此例中即为O(n)。这样做的原因是:最坏情况下的时间复杂度是在任何输入实例上运行时间的上界。有时,也可能选择将算法的平均(或期望)时间复杂度作为讨论目标。所谓的平均时间复杂度是指所有可能的输入实例以等概率出现的情况下算法的期望运行时间与问题规模的数量级的关系。此例中,以k出现在任何位置的概率相同,都为1/n,则语句三的执行频度为[0+1+2+…+(n-1)]/n=(n-1)/2。它决定了此程序段的平均时间复杂度的数量级为f(n)=n,记作O(n)。(2)在计算包含调用语句的算法的语句频度时,需考虑到调用发生时在被调用算法中各语句的执行情况。本题所示的递归调用较之非递归调用的分析更为复杂。由于k等于n是算法的出口条件,不妨首先分析算法pp(n)的简单情况,这时各语句的执行频度分别为:1,n+1,n,0,0,0;而当k=n-1,n-2,…,1时,语句的执行情况和调度情况,如下表所示。K值不考虑调用时各语句的执行频度调用情况语句1语句2语句3语句4语句5语句6n1n+1n000/n-1100321pp(n)n-2100431pp(n-1)……………………1100n+1n1pp(2)对于k=1即pp(1)而言,各语句的执行次数还须将调用pp(2)时的执行次数累计到内,pp(2)各语句的执行次数又须将调用pp(3)时执行次数累计到内,……由此可的语句频度如下:语句1:1+1+…+1=n语句2:0+0+…+0+(n+1)=n+1语句3:0+0+…+0+n=n语句4:(n+1)+n+…+3=(n-1)(n+4)/2语句5:n+(n-1)+…+2=(n-1)(n+2)/2语句6:1+1+….+1+0=n-1算法的时间复杂度可以基于频度最大的语句,应为O(n2)。三、算法的时间复杂度仅与问题的规模相关吗?不,事实上,算法的时间复杂度不仅与问题的规模相关,还与输入实例中的元素取值等相关,但在最坏的情况下,其时间复杂度就是只与求解问题的规模相关的。我们在讨论时间复杂度时,一般就是以最坏情况下的时间复杂度为准的。 四、常用的存储表示方法有哪几种?常用的存储表示方法有四种:顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间的逻辑关系由存储单元的邻接关系来体现。由此得到的存储表示称为顺序存储结构。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。五、确定下列算法中输出语句的执行次数,并给出算法的时间复杂度。(1)voidcombi(intn){ intI,j,k; for(I=1;I<=n;I++) for(j=I+1;j<=n;j++) for(k=j+1;k<=n;k++) cout<(2)voidbinary(intn){ while(n){ cout< n=n/2; }}(1)I取值范围从1~n,j取值范围从I+1~n,k取值范围从j+1~n,情况如下表所示: I值j值k值输出语句的执行次数123,4,…,nn-2………n-1n1n//234,5,…,nn-3………n-1n1n//…………n-2n-1n1n//n-1n//n///所以,输出语句共执行次数为((n-2)+(n-3)+…+1)+((n-3)+(n-4)+…+1)+…+1=(n-1)(n-2)/2+(n-2)(n-3)/2+…+1=(((n-1)2+(n-2)2+(n-3)2+…+12)-((n-1)+(n-2)+(n-3)+….+1))/2=((n-1)n(2n-1)/6-n(n-1)/2)/2=(n(n-1)(2n-4))/12=n(n-1)(n-2)/6(2)ceil(log2n);六、为什么在单循环链表中设置尾指针比设置头指针更好?答:尾指针是指向终端结点的指针,用它来表示单循环链表可以使得查找链表的开始结点和终端结点都很方便,设一带头结点的单循环链表,其尾指针为rear,则开始结点和终端结点的位置分别是rear->next->next和rear,查找时间都是O(1)。若用头指针来表示该链表,则查找终端结点的时间为O(n)。七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜?答:在实际应用中,应根据具体问题的要求和性质来选择顺序表或链表作为线性表的存储结构,通常有以下几方面的考虑:1.基于空间的考虑。当要求存储的线性表长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序表;反之,当线性表长度变化大,难以估计其存储规模时,采用动态链表作为存储结构为好。2.基于时间的考虑。若线性表的操作主要是进行查找,很少做插入和删除操作时,采用顺序表做存储结构为宜;反之,若需要对线性表进行频繁地插入或删除等的操作时,宜采用链表做存储结构。并且,若链表的插入和删除主要发生在表的首尾两端,则采用尾指针表示的单循环链表为宜。八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。if(I<1||k<0||I+k>a.length)returnERROR;else{for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK更正:for(j=I+k;j<=a.Length;j++)a.elem[j-k]=a.elem[j];a.Length=a.Length–k;九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度算法如下:LinkListLink(LinkListL1,LinkListL2){//将两个单链表连接在一起ListNode*p,*q;p=L1;q=L2;while(p->next)p=p->next;//查找终端结点p->next=q->next;//将L2的开始结点链接在L1之后returnL1;}本算法的主要操作时间花费在查找L1的终端结点上,与L2的长度无关,所以本算的法时间复杂度为:m+1=O(m)十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。要解这样的问题,我们首先想到的是拿链表中的元素一个个地与max和min比较,然后删除这个结点,其实因为已知其是有序链表,所以我们只要找到大于min的结点的直接前趋结点,再找到小于max的结点,然后一并把中间的全部摘掉就可以了。算法如下:voidDeleteList(LinkListL,DataTypemin,DataTypemax){ListNode*p,*q,*r;p=L->next;while(p&&p->data<=min){//找比min大的前一个元素位置q=p;p=p->next;}while(p&&p->data{r=p;p=p->next;free?;//释放这个结点空间}q->next=p;//把断点链上} 十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。答:(1)4132;(2)4213;(3)4231。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。答案:设用变量I表示栈的编号。类型定义:typedefstruct{ElemTypev[m];//栈空间向量inttop[2];//栈顶指针向量}DuStack;栈空条件:s0栈:s->top[0]=-1s1栈:s->top[1]=m栈满条件:s->top[0]+1=s->top[1](此时向量空间全占满)。(1)插入voidpush(DuStack*s,ElemTypex,intI)//当两个栈共享空间时,再由I指定的栈中插入新元素x{if(s->top[0]+1==s->top[1]){printf(“OVERFLOW”);return;}switch(I){case0:s->top[0]++
(1)这个算法完成在一维数组a[n]中查找给定值k的功能。
语句三的频度不仅与问题的规模n有关,还与输入实例中a的各元素取值以及k的取值相关,即与输入实例的初始状态复杂有关。
若a中没有与k相等的元素,则语句三的频度为n;若a中的第一个元素a[0]等于k,则语句三的频度是常数0。
在这种情况下,可用最坏情况下的时间复杂度作为时间复杂度。
在此例中即为O(n)。
这样做的原因是:
最坏情况下的时间复杂度是在任何输入实例上运行时间的上界。
有时,也可能选择将算法的平均(或期望)时间复杂度作为讨论目标。
所谓的平均时间复杂度是指所有可能的输入实例以等概率出现的情况下算法的期望运行时间与问题规模的数量级的关系。
此例中,以k出现在任何位置的概率相同,都为1/n,则语句三的执行频度为[0+1+2+…+(n-1)]/n=(n-1)/2。
它决定了此程序段的平均时间复杂度的数量级为f(n)=n,记作O(n)。
(2)在计算包含调用语句的算法的语句频度时,需考虑到调用发生时在被调用算法中各语句的执行情况。
本题所示的递归调用较之非递归调用的分析更为复杂。
由于k等于n是算法的出口条件,不妨首先分析算法pp(n)的简单情况,这时各语句的执行频度分别为:
1,n+1,n,0,0,0;而当k=n-1,n-2,…,1时,语句的执行情况和调度情况,如下表所示。
K值
不考虑调用时各语句的执行频度
调用情况
语句1
语句2
语句3
语句4
语句5
语句6
n
1
n+1
0
/
n-1
3
2
pp(n)
n-2
4
pp(n-1)
…
pp
(2)
对于k=1即pp
(1)而言,各语句的执行次数还须将调用pp
(2)时的执行次数累计到内,pp
(2)各语句的执行次数又须将调用pp(3)时执行次数累计到内,……由此可的语句频度如下:
语句1:
1+1+…+1=n
语句2:
0+0+…+0+(n+1)=n+1
语句3:
0+0+…+0+n=n
语句4:
(n+1)+n+…+3=(n-1)(n+4)/2
语句5:
n+(n-1)+…+2=(n-1)(n+2)/2
语句6:
1+1+….+1+0=n-1
算法的时间复杂度可以基于频度最大的语句,应为O(n2)。
不,事实上,算法的时间复杂度不仅与问题的规模相关,还与输入实例中的元素取值等相关,但在最坏的情况下,其时间复杂度就是只与求解问题的规模相关的。
我们在讨论时间复杂度时,一般就是以最坏情况下的时间复杂度为准的。
四、
五、
确定下列算法中输出语句的执行次数,并给出算法的时间复杂度。
cout< n=n/2; }}(1)I取值范围从1~n,j取值范围从I+1~n,k取值范围从j+1~n,情况如下表所示: I值j值k值输出语句的执行次数123,4,…,nn-2………n-1n1n//234,5,…,nn-3………n-1n1n//…………n-2n-1n1n//n-1n//n///所以,输出语句共执行次数为((n-2)+(n-3)+…+1)+((n-3)+(n-4)+…+1)+…+1=(n-1)(n-2)/2+(n-2)(n-3)/2+…+1=(((n-1)2+(n-2)2+(n-3)2+…+12)-((n-1)+(n-2)+(n-3)+….+1))/2=((n-1)n(2n-1)/6-n(n-1)/2)/2=(n(n-1)(2n-4))/12=n(n-1)(n-2)/6(2)ceil(log2n);六、为什么在单循环链表中设置尾指针比设置头指针更好?答:尾指针是指向终端结点的指针,用它来表示单循环链表可以使得查找链表的开始结点和终端结点都很方便,设一带头结点的单循环链表,其尾指针为rear,则开始结点和终端结点的位置分别是rear->next->next和rear,查找时间都是O(1)。若用头指针来表示该链表,则查找终端结点的时间为O(n)。七、何时选用顺序表、何时选用链表作为线性表的存储结构为宜?答:在实际应用中,应根据具体问题的要求和性质来选择顺序表或链表作为线性表的存储结构,通常有以下几方面的考虑:1.基于空间的考虑。当要求存储的线性表长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序表;反之,当线性表长度变化大,难以估计其存储规模时,采用动态链表作为存储结构为好。2.基于时间的考虑。若线性表的操作主要是进行查找,很少做插入和删除操作时,采用顺序表做存储结构为宜;反之,若需要对线性表进行频繁地插入或删除等的操作时,宜采用链表做存储结构。并且,若链表的插入和删除主要发生在表的首尾两端,则采用尾指针表示的单循环链表为宜。八、指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。StatusDeleteK(SqList&a,intI,intk){//本过程从顺序存储结构的线性表a中删除第I个元素起的k个元素。if(I<1||k<0||I+k>a.length)returnERROR;else{for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK更正:for(j=I+k;j<=a.Length;j++)a.elem[j-k]=a.elem[j];a.Length=a.Length–k;九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度算法如下:LinkListLink(LinkListL1,LinkListL2){//将两个单链表连接在一起ListNode*p,*q;p=L1;q=L2;while(p->next)p=p->next;//查找终端结点p->next=q->next;//将L2的开始结点链接在L1之后returnL1;}本算法的主要操作时间花费在查找L1的终端结点上,与L2的长度无关,所以本算的法时间复杂度为:m+1=O(m)十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。要解这样的问题,我们首先想到的是拿链表中的元素一个个地与max和min比较,然后删除这个结点,其实因为已知其是有序链表,所以我们只要找到大于min的结点的直接前趋结点,再找到小于max的结点,然后一并把中间的全部摘掉就可以了。算法如下:voidDeleteList(LinkListL,DataTypemin,DataTypemax){ListNode*p,*q,*r;p=L->next;while(p&&p->data<=min){//找比min大的前一个元素位置q=p;p=p->next;}while(p&&p->data{r=p;p=p->next;free?;//释放这个结点空间}q->next=p;//把断点链上} 十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。答:(1)4132;(2)4213;(3)4231。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。答案:设用变量I表示栈的编号。类型定义:typedefstruct{ElemTypev[m];//栈空间向量inttop[2];//栈顶指针向量}DuStack;栈空条件:s0栈:s->top[0]=-1s1栈:s->top[1]=m栈满条件:s->top[0]+1=s->top[1](此时向量空间全占满)。(1)插入voidpush(DuStack*s,ElemTypex,intI)//当两个栈共享空间时,再由I指定的栈中插入新元素x{if(s->top[0]+1==s->top[1]){printf(“OVERFLOW”);return;}switch(I){case0:s->top[0]++
(1)I取值范围从1~n,j取值范围从I+1~n,k取值范围从j+1~n,情况如下表所示:
I值
j值
k值
输出语句的执行次数
3,4,…,n
4,5,…,n
n-3
所以,输出语句共执行次数为((n-2)+(n-3)+…+1)+((n-3)+(n-4)+…+1)+…+1
=(n-1)(n-2)/2+(n-2)(n-3)/2+…+1
=(((n-1)2+(n-2)2+(n-3)2+…+12)-((n-1)+(n-2)+(n-3)+….+1))/2
=((n-1)n(2n-1)/6-n(n-1)/2)/2
=(n(n-1)(2n-4))/12=n(n-1)(n-2)/6
(2)ceil(log2n);
六、
为什么在单循环链表中设置尾指针比设置头指针更好?
答:
尾指针是指向终端结点的指针,用它来表示单循环链表可以使得查找链表的开始结点和终端结点都很方便,设一带头结点的单循环链表,其尾指针为rear,则开始结点和终端结点的位置分别是rear->next->next和rear,查找时间都是O
(1)。
若用头指针来表示该链表,则查找终端结点的时间为O(n)。
七、
何时选用顺序表、何时选用链表作为线性表的存储结构为宜?
在实际应用中,应根据具体问题的要求和性质来选择顺序表或链表作为线性表的存储结构,通常有以下几方面的考虑:
1.基于空间的考虑。
当要求存储的线性表长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序表;反之,当线性表长度变化大,难以估计其存储规模时,采用动态链表作为存储结构为好。
2.基于时间的考虑。
若线性表的操作主要是进行查找,很少做插入和删除操作时,采用顺序表做存储结构为宜;反之,若需要对线性表进行频繁地插入或删除等的操作时,宜采用链表做存储结构。
并且,若链表的插入和删除主要发生在表的首尾两端,则采用尾指针表示的单循环链表为宜。
八、
指出以下算法中的错误和低效之处,并把它改写为一个既正确又高效的算法。
for(count=1;countfor(j=a.Length;j>=I+1;j--)a.elem[j-1]=a.elem[j];a.length--;}rreturnOK;}//DeleteK更正:for(j=I+k;j<=a.Length;j++)a.elem[j-k]=a.elem[j];a.Length=a.Length–k;九、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十、假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。已知s为指向链表中某个结点指针,试编写算法在链表中删除指针s所指结点的前驱结点。voidDelprior(Links){p=q=s;while(p->next!=s){q=p;p=p->next;}q->next=s;delete(p);}十一、已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。试写一算法将这两个链表连接在一起,请分析你的算法的时间复杂度算法如下:LinkListLink(LinkListL1,LinkListL2){//将两个单链表连接在一起ListNode*p,*q;p=L1;q=L2;while(p->next)p=p->next;//查找终端结点p->next=q->next;//将L2的开始结点链接在L1之后returnL1;}本算法的主要操作时间花费在查找L1的终端结点上,与L2的长度无关,所以本算的法时间复杂度为:m+1=O(m)十二、已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。请分析你的算法的时间复杂度。要解这样的问题,我们首先想到的是拿链表中的元素一个个地与max和min比较,然后删除这个结点,其实因为已知其是有序链表,所以我们只要找到大于min的结点的直接前趋结点,再找到小于max的结点,然后一并把中间的全部摘掉就可以了。算法如下:voidDeleteList(LinkListL,DataTypemin,DataTypemax){ListNode*p,*q,*r;p=L->next;while(p&&p->data<=min){//找比min大的前一个元素位置q=p;p=p->next;}while(p&&p->data{r=p;p=p->next;free?;//释放这个结点空间}q->next=p;//把断点链上} 十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。答:(1)4132;(2)4213;(3)4231。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。答案:设用变量I表示栈的编号。类型定义:typedefstruct{ElemTypev[m];//栈空间向量inttop[2];//栈顶指针向量}DuStack;栈空条件:s0栈:s->top[0]=-1s1栈:s->top[1]=m栈满条件:s->top[0]+1=s->top[1](此时向量空间全占满)。(1)插入voidpush(DuStack*s,ElemTypex,intI)//当两个栈共享空间时,再由I指定的栈中插入新元素x{if(s->top[0]+1==s->top[1]){printf(“OVERFLOW”);return;}switch(I){case0:s->top[0]++
更正:
for(j=I+k;j<=a.Length;j++)a.elem[j-k]=a.elem[j];
a.Length=a.Length–k;
九、
假设某个单向循环链表的长度大于1,且表中既无头结点也无头指针。
voidDelprior(Links){
p=q=s;
while(p->next!
=s){
q=p;
p=p->next;
q->next=s;
delete(p);
十、
十一、
已知L1和L2分别指向两个单链表的头结点,且已知其长度分别为m和n。
算法如下:
LinkListLink(LinkListL1,LinkListL2)
//将两个单链表连接在一起
ListNode*p,*q;
p=L1;
q=L2;
while(p->next)p=p->next;//查找终端结点
p->next=q->next;//将L2的开始结点链接在L1之后
returnL1;
本算法的主要操作时间花费在查找L1的终端结点上,与L2的长度无关,所以本算的法时间复杂度为:
m+1=O(m)
十二、
已知单链表L是一个递增有序表,试写一高效算法,删除表中值大于min且小于max的结点(若表中有这样的结点),同时释放被删结点的空间,这里min和max是两个给定的参数。
要解这样的问题,我们首先想到的是拿链表中的元素一个个地与max和min比较,然后删除这个结点,其实因为已知其是有序链表,所以我们只要找到大于min的结点的直接前趋结点,再找到小于max的结点,然后一并把中间的全部摘掉就可以了。
算法如下:
voidDeleteList(LinkListL,DataTypemin,DataTypemax)
ListNode*p,*q,*r;
p=L->next;
while(p&&p->data<=min)
{//找比min大的前一个元素位置
while(p&&p->data{r=p;p=p->next;free?;//释放这个结点空间}q->next=p;//把断点链上} 十三、若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:(1)能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的输出序列;(2)能由输出受限的双端队列得到,但不能由输入受限的双端队列得到的输出序列;(3)既不能由输入受限的双端队列得到,也不能由输出受限的双端队列得到的输出序列。答:(1)4132;(2)4213;(3)4231。十四、设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。试设计这两个栈的插入和删除算法。答案:设用变量I表示栈的编号。类型定义:typedefstruct{ElemTypev[m];//栈空间向量inttop[2];//栈顶指针向量}DuStack;栈空条件:s0栈:s->top[0]=-1s1栈:s->top[1]=m栈满条件:s->top[0]+1=s->top[1](此时向量空间全占满)。(1)插入voidpush(DuStack*s,ElemTypex,intI)//当两个栈共享空间时,再由I指定的栈中插入新元素x{if(s->top[0]+1==s->top[1]){printf(“OVERFLOW”);return;}switch(I){case0:s->top[0]++
r=p;
free?
;//释放这个结点空间
q->next=p;//把断点链上
十三、
若以1234作为双端队列的输入序列,试分别求出满足以下条件的输出序列:
(1)4132;
(2)4213;(3)4231。
十四、
设两个栈共享空间v[0..m-1],两栈的栈底分别设在向量的两端,且每个元素占一个分量。
答案:
设用变量I表示栈的编号。
类型定义:
typedefstruct
{ElemTypev[m];//栈空间向量
inttop[2];//栈顶指针向量
}DuStack;
栈空条件:
s0栈:
s->top[0]=-1
s1栈:
s->top[1]=m
栈满条件:
s->top[0]+1=s->top[1](此时向量空间全占满)。
(1)插入
voidpush(DuStack*s,ElemTypex,intI)//当两个栈共享空间时,再由I指定的栈中插入新元素x
{if(s->top[0]+1==s->top[1])
{printf(“OVERFLOW”);return;}
switch(I)
{case0:
s->top[0]++
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1