A.O(logn) B.O(n) C.O(nlogn) D.O(n2)
分析:
答案是A,此题考查的是算法时间复杂度的计算。
可以放在第二章,实际这内容贯穿每一章内容中算法的度量。
(二)综合题
1.(09年)已知一个带有表头结点的单链表结点结构为(data,link),假设该链表只给出了头指针list。
在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。
若查找成功,算法输出该结点的data值,并返回1;否则,只返回0。
要求:
(1)描述算法的基本设计思想;
(2)描述算法的详细实现步骤;
(3)根据设计思想和实现步骤,采用程序设计语言描述算法(使用C或C++或JAVA语言实现),关键之处给出简要注释。
分析:
此题考查线性表的链式存储,主要是线性表的基本操作的应用。
做此题时要把握算法的效率。
(1)算法基本思想如下:
从头到尾遍历单链表,并用指针p指向当前结点的前k个结点。
当遍历到链表的最后一个结点时,指针p所指向的结点即为所查找的结点。
(2)详细实现步骤:
增加两个指针变量和一个整型变量,从链表头向后遍历,其中指针p1指向当前遍历的结点,指针p指向p1所指向结点的前k个结点,如果p1之前没有k个结点,那么p指向表头结点。
用整型变量i表示当前遍历了多少结点,当i>k时,指针p随着每次遍历,也向前移动一个结点。
当遍历完成时,p或者指向表头结点,或者指向链表中倒数第k个位置上的结点。
(3)算法描述:
intlocate(Linklistlist,intk)
{
p1=list->link;
p=list;
i=1;
while(p1)
{
p1=p1->link;
i++;
if(i>k)p=p->next;//如果i>k,则p也后移
}
if(p==list)return0;//链表没有k个结点
else
{
printf(“%\n”,p->data);
return1;
}
}
2.(10年)设将n(n,1)个整数存放到一维数组R中,试设计一个在时间和空间两方面尽可能有效的算法,将R中保有的序列循环左移P(0﹤P﹤n)个位置,即将R中的数据由(X0X1……Xn-1)变换为(XpXp+1……Xn-1 X0 X1……Xp-1)要求:
(1)给出算法的基本设计思想。
(2)根据设计思想,采用C或C++或JAVA语言表述算法,关键之处给出注释。
(3)说明你所设计算法的时间复杂度和空间复杂度
分析:
此题考查的是数组的操作,线性表的顺序存储的核心就是数组,因此此题实质上是考查的线性表的顺序存储的操作及其应用。
做此题时要考虑算法的时间和空间复杂度。
解法一:
(1)算法的基本设计思想:
可以将这个问题看做是把数组ab转化成数组ba(a代表数组的前p个元素,b代表数组中余下的n-p个元素),先将a逆置得到a-1b,再将b逆置得到a-1b-1,最后将整个a-1b-1逆置得到(a-1b-1)-1=ba。
设reverse函数执行将数组元素逆置的操作,对abcdefgh向左循环移动3(p=3)个位置的过程如下:
reverse(0,p-1)得到cbadefgh;
reverse(p,n-1)得到cbahgfde;
reverse(0,n-1)得到defghabc。
注:
reverse中,两个参数分别表示数组中待转换元素的始末位置。
(2)算法描述:
voidreverse(intR[],intlow,inthigh)
{//将数组中从low到high的元素逆置
inttemp;
for(i=0;i<=(high-low)/2;i++)
{
temp=R[low+i];
R[l0ow+i]=R[high-i];
R[high-i]=temp;
}
}
voidconverse(intR[],intn,intp)
{
reverse(R,0,p-1);
reverse(R,p,n-1);
reverse(R,0,n-1);
}
(3)上述算法中三个reverse函数的时间复杂度分别是O(p/2)、O((n-p)/2)、O(n/2),故所设计算法的时间复杂度为O(n),空间复杂度为O
(1)。
解法二:
算法思想:
创建大小为p的辅助数组S,将R中前p个整数依次暂存在S中,同时将R中后n-p个整数左移,然后将S中暂存的p个数依次放回到R中的后续单元。
时间复杂度为O(n),空间复杂度为O(p)。
3.(11年)一个长度为L(L>=1)的生序列S,处在第┌L/2┐个位置的数称为S的中位数,例如,若序列S1=(11,13,15,17,19),则S1的中位数是15,两个序列的中位数是含它们所有元素的升序序列的中位数。
例如,若S2=(2,4,6,8,20),则S1和S2的中位数是11。
现在有两个等长升序序列A和B,试设计一个在时间和空间方面都尽可能高效的算法,找出两个序列A和B的中位数。
要求:
(1)给出算法的基本设计思想。
(2)根据设计思想,采用C或C++或JAVA语言描述算法,关键之处给出注释。
解答:
此题考查线性表的顺序存储,主要是线性表的基本操作的应用。
做此题时要把握算法的效率。
(1)算法的基本设计思想如下:
分别求出序列A和B的中位数,设为a和b,求序列A和B的中位数过程如下:
1)若a=b,则a或b即为所求中位数,算法结束;
2)若a
3)若a>b,则舍弃序列A中较大的一半,同时舍弃序列B中较小的一半,要求舍弃的长度相等;
在保留的两个升序序列中,重复过程1)-3),直到两个序列中只含一个元素时为止,较小者即为所求的中位数。
(2)算法实现如下:
intM_search(intA[],intB[].intn)
{
ints1=0,d1=n-1,m1,s2=0,d2=n-1,m2;
//分别表示序列A和B的首位数、末尾数和中位数
While(s1!
=d1||s2!
=d2)
{
m1=(s1+d1)/2;
m2=(s2+d2)/2;
if(A[m1]==B[m2])returnA[m1];
elseif(A[m1
if(s1+d1)%2==0
{s1=m1;d2=m2;}
else{s1=m1+1;d2=m2;}
else
if(s1+d1)%2==0
{d1=m1;s2=m2;}
else{d1=m1+1;s2=m2;}
}
returnA[s1]
A[s1]:
B[s2];
}
(3)算法的时间复杂度为O(logn),空间负责杂度为O
(1)。
三、考查重点
1.线性结构的特点;
2.线性表在顺序存储及链式存储方式下的基本操作及其应用;
3.线性表的顺序存储及链式存储情况下,其不同和优缺点比较,及其各自适用的场合。
单链表中设置头指针、循环链表中设置尾指针而不设置头指针的各自好处;
4.能分析所写算法的时间和空间复杂度。
分析:
线性表一章在线性结构的学习乃至整个数据结构学科的学习中,其作用都是不可低估的。
线性表一章小的知识点比较少,一般会出一个综合题,并且容易和第三章、第九章和第十章的内容结合来考,注意对基本知识的理解,能够利用书上的理论解决具体问题。
学习过程中要注意多积累,多看、多写一些相关算法。
四、练习题
(一)选择题
1.下述哪一条是顺序存储结构的优点?
(A)
A.存储密度大B.插入运算方便
C.删除运算方便D.可方便地用于各种逻辑结构的存储表示
2.下面关于线性表的叙述中,错误的是哪一个?
(B)
A.线性表采用顺序存储,必须占用一片连续的存储单元。
B.线性表采用顺序存储,便于进行插入和删除操作。
C.线性表采用链接存储,不必占用一片连续的存储单元。
D.线性表采用链接存储,便于插入和删除操作。
3.线性表是具有n个(C)的有限序列(n>0)。
A.表元素B.字符C.数据元素D.数据项E.信息项
4.若某线性表最常用的操作是存取任一指定序号的元素和在最后进行插入和删除运算,则利用(A)存储方式最节省时间。
A.顺序表B.双链表C.带头结点的双循环链表D.单循环链表
5.某线性表中最常用的操作是在最后一个元素之后插入一个元素和删除第一个元素,则采用(D)存储方式最节省运算时间。
A.单链表B.仅有头指针的单循环链表
C.双链表D.仅有尾指针的单循环链表
6.若长度为n的线性表采用顺序存储结构,在其第i个位置插入一个新元素的算法的时间复杂度为(C)(1<=i<=n+1)。
A.O(0)B.O
(1)C.O(n)D.O(n2)
7.对于顺序存储的线性表,访问结点和增加、删除结点的时间复杂度为(C)。
A.O(n)O(n)B.O(n)O
(1)C.O
(1)O(n)D.O
(1)O
(1)
8.线性表(a1,a2,…,an)以链接方式存储时,访问第i位置元素的时间复杂性为(C)。
A.O(i)B.O
(1)C.O(n)D.O(i-1)
(二)综合题
1.掌握线性表中几个最基本的操作算法
(1)逆置操作
顺序表的就地逆置
voidreverse(SqList&A)
{
for(i=1,j=A.length;iA.elem[i]<->A.elem[j];//元素交换
}
链表的就地逆置
voidLinkList_reverse(Linklist&L)
{
p=L->next;q=p->next;
while(q)
{
r=q->next;
q->next=p;
p=q;q=r;
}
L->next->next=NULL;//修改第一个元素的指针值
L->next=p;//修改表头结点指针值
}
(2)线性表的合并
顺序表的合并:
顺序存储的线性表la,lb的关键字为整数,元素非递减有序,线性表空间足够大,试编写高效算法,将lb中元素合并到la中,使新的la的元素仍非递减有序。
分析:
从后往前插入,避免移动元素
voidunion(Sqlistla,Sqlistlb)
{
m=la.length;n=lb.length;
k=m+n;i=m;j=n;//循环指针赋值,从后往前比较
while(i>0&&j>0)
{
if(la.elem[i]>=lb.elem[j])
{la.elem[k]=la.elem[i];k--;i--;}
else
{la.elem[k]=lb.elem[j];k--;j--;}
}
if(j>0)//判断lb是否为空
{la.elem[k]=lb.elem[j];k--;j--;}
la.length=m+n;
}
链表的合并:
把元素递增排列的链表A和B合并为C,且C中元素递减排列,使用原结点空间。
分析:
本算法的思想是,按从小到大的顺序依次把A和B的元素插入新表的头部pc处,因为合并以后是逆序,因此采用头插法,最后处理A或B的剩余元素。
LinkListUnion(LinkListA,LinkListB,LinkListC)
{
pa=A->next;pb=B->next;C=A;A->next=null;
while(pa!
=null&&pb!
=null)
if(pa->data<=pb->data)
{
r=pa->next;pa->next=C->next;
C->next=pa;pa=r;
}
else
{
r=pb->next;pb->next=C->next;
C->next=pb;pb=r;
while(pa!
=null)
{r=pa->next;pa->next=C->next;C->next=pa;pa=r;}
while(pb!
=null)
{r=pb->next;pb->next=C->next;C->next=pb;pb=r;}
returnC;
}
(3)链表的拆分:
设L为一单链表的头指针,单链表的每个结点由一个整数域data和指针域next组成,整数在单链表中是无序的。
设计算法,将链表中结点分成一个奇数链和一个偶数链,算法中不得申请新的结点空间。
分析:
利用原链表空间,在原链表的分解过程中新建链表。
voiddiscreat(linklistL)
{
s=L->next;p=L;
p->next=NULL;q->next=NULL;
while(s!
=NULL)
{
r=s->next;
if(s->data%2!
=0)//奇数链链接
{s->next=p->next;p->next=s;}
else//偶数链链接
{s->next=q->next;q->next=s;}
s=r;
}
}
2.算法练习
(1)试编写在带头结点的单链表中删除最小值结点的高效算法。
voiddelete(linklistL)
{
p=L->next;pre=L;q=p;
while(p->next!
=NULL)//找最小值元素,pre指向最小值的前驱
{
if(p->next->datadata){pre=p;q=p->next;}
p=p->next;
}
pre->next=q->next;
free(q);
}
分析:
此算法的高效在于在循环过程中利用最小值结点的前驱指针进行比较,比较结束后直接保留了最小值结点的前驱,方便进行删除操作。
(2)设单链表的表头指针为h,结点结构由data和next两个域构成,其中data域为字符型。
写出算法dc(h,n),判断该链表的前n个字符是否中心对称。
例如xyx,xyyx都是中心对称。
分析:
判断链表中数据是否中心对称,通常使用栈。
将链表的前一半元素依次进栈。
在处理链表的后一半元素时,当访问到链表的一个元素后,就从栈中弹出一个元素,两元素比较,若相等,则将链表中下一元素与栈中再弹出元素比较,直至链表到尾。
这时若栈是空栈,则得出链表中心对称的结论;否则,当链表中一元素与栈中弹出元素不等时,结论为链表非中心对称,结束算法的执行。
intdc(Linklisth,intn)
{∥h是带头结点的n个元素单链表,链表中结点的数据域是字符。
chars[];inti=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)return1;∥链表中心对称
elsereturn0;∥链表不中心对称
}∥算法结束。
(3)已知两个单链表A和B,其头指针分别为heada和headb,编写一个过程从单链表A中删除自第i个元素起的共len个元素,然后将单链表A插入到单链表B的第j个元素之前。
分析:
在单链表中删除自第i个元素起的共len个元素,应从第1个元素起开始计数,记到第i个时开始数len个,然后将第i-1个元素的后继指针指向第i+len个结点,实现了在A链表中删除自第i个起的len个结点。
这时应继续查到A的尾结点,得到删除元素后的A链表。
再查B链表的第j个元素,将A链表插入之。
插入和删除中应注意前驱后继关系,不能使链表“断链”。
另外,算法中应判断i,len和j的合法性。
LinklistDelInsert(Linklistheada,Linklistheadb,inti,j,len)
{∥heada和headb均是带头结点的单链表。
if(i<1||len<1||j<1)
{printf(“参数错误\n”);exit(0);}∥参数错,退出算法。
p=heada;∥p为链表A的工作指针,初始化为A的头指针
k=0;∥计数
while(p!
=null&&k{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{k++;u=q,q=q->next;free(u);}∥删除结点,后移指针。
if(k{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{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表头结点。
}
第3章栈和队列
一、考研知识点
(一)栈和队列的基本概念
(二)栈和队列的顺序存储结构
(三)栈和队列的链式存储结构
(四)栈和队列的应用
二、考研真题
(一)选择题
1.(09年)为解决计算机和打印机之间速度不匹配的问题,通常设置一个打印数据缓冲区,主机将要输出的数据依次写入缓冲区,而打印机则依次从该缓冲区中取出数据。
该缓冲区的逻辑结构应该是()。
A.栈B.队列C.树D.图
分析:
答案是B,此题可以认为考查队列的特点,也可以看做是考查四种数据结构的特点。
2.(09年)设栈S和队列Q的初始状态均为空,元素abcdefg依次进入栈S。
若每个元素出栈后立即进入队列Q,且7个元素出队顺序是bdcfeag,则栈S的容量至少是()。
A.1B.2C.3D.4
分析:
答案是C,此题考查栈的入栈和出栈操作和队列的入队和出队操作。
3.(10年)若元素a,b,c,d,e,f依次进栈,允许进栈、退栈操作交替进行。
但不允许连续三次进行退栈工作,则不可能得到的出栈序列是( )。
A.dcebfa B.cbdaef C.dbcaef D.afedcb
分析:
答案是D,此题考查栈的入栈和出栈操作,但题目出的不是太严谨,严格说应该是CD两个答案。
4.(10年)某队列允许在其两端进行入队操作,但仅允许在一端进行出队操作,则不可能得到的顺序是(C)
A.bacde B.dbace C.dbcae D.ecbad
分析:
答案是C,此题考查队列的入队和出队操作。
5.(11年)元素a,b,c,d,e依次进入初始为空的栈中,若元素进栈后可停留、可进栈,直到所有元素都出栈,则在所有可能的出栈序列中,以元素d开头的序列个数是
A.3 B.4 C.5 D.6
分析:
答案是B,出栈序列必为d_c_b_a.e的顺序不定,在任意一个“_”上都有可能。
6.(11年)已知循环队列存储在一维数组A[0..n-1]中,且队列非空时front和rear分别指向队头元素和对位元素。
若初始时队列为空,且要求低1个进入队列的元素存储在[0]处,则初始时front和rear的值分别是
A.0,0 B.0,n-1 C.n-1,0 D.n-1,n-1
分析:
答案是B,插入元素时,front不变,rear+1,而插入第一个元素之后,队尾要指向尾元素,显然,rear初始应该为n-1,front为0
(二)综合题
10年考研综合题的第二题如果用解法二,可以认为考查了队列的基本操作,因为栈和队列本身是线性结构,所以考试时可以综合来考,和第2章的内容没有太明显的界限。
三、考查重点
1.栈和队列的特点,及其应用
2.栈的顺序存储和链式存储的入栈和出栈操作,以及判断栈的空和满的条件;
3.队列的顺序存储和链式存储的入队和出队操作,以及判断队列空和满的条件;
4.理解栈与递归的关系,利于对以后章节(树和图)的学习和复习。
分析:
此章内容是线性表的深化,如果线性表理解的好,这一章就比较容易。
这一章小的知识点比较多,一般容易出选择题,从09和10年的考研真题来看,主要是考查站和队列的特点及其应用、栈的入栈出栈操作和队列的入队出对操作、判断栈和队列的空与满的条件。
一般不会单独出综合题,如果出综合题会将栈和队列作为其他结构中一个小环节出题,比如和线性表结合,作为实现递归的工具和二叉树结合等。
四、练习题
(一)选择题
1.一个栈的输入序列为123…n,若输出序列的第一个元素是n,输出第i(1<=i<=n)个元素是(B)。
A.不确定B.n-i+1C.iD.n-i
2.若一个栈的输入序列为1,2,3,…,n,输出序列的第一个元素是i,则第j个输出元素是(D)。
A.i-j-1B.i-jC.j-i+1D.不确定的
3.输入序列为ABC,可以变为CBA时,经过的栈操作为(B)。
A.push,pop,push,pop,push,pop
B.push,push,push,pop,pop,pop
C.push,push,pop,pop,push,pop
D.push,pop,push,push,pop,pop
4.循环队列存储在数组A[0..m]中,则入队时的操作为(D)。
A.rear=rear+1B.rear=(rear+1)mod(m-1)
C.rear=(