数据结构C语言版 严蔚敏

课程为B站的计算机学院的网课,参照教材,不同的地方和相关注释已经标明

课后习题答案

第一章 绪论

1.简述下列概念:数据、数据元素、数据项、数据对象、数据结构、逻辑结构、存储结构、抽象数据类型。

答案:

数据:是客观事物的符号表示,指所有能输入到计算机中并被计算机程序处理的符号的总称。如数学计算中用到的整数和实数,文本编辑所用到的字符串,多媒体程序处理的图形、图像、声音、动画等通过特殊编码定义后的数据。

数据元素:是数据的基本单位,在计算机中通常作为一个整体进行考虑和处理。在有些情况下,数据元素也称为元素、结点、记录等。数据元素用于完整地描述一个对象,如一个学生记录,树中棋盘的一个格局(状态)、图中的一个顶点等。

数据项:是组成数据元素的、有独立含义的、不可分割的最小单位。例如,学生基本信息表中的学号、姓名、性别等都是数据项。

数据对象是性质相同的数据元素的集合,是数据的一个子集。例如:整数数据对象是集合N={0,±1,±2,…},字母字符数据对象是集合C={‘A’,‘B’,…,‘Z’, ‘a’,‘b’,…,‘z’},学生基本信息表也可是一个数据对象。

数据结构是相互之间存在一种或多种特定关系的数据元素的集合。换句话说,数据结构是带“结构”的数据元素的集合,“结构”就是指数据元素之间存在的关系。

逻辑结构:从逻辑关系上描述数据,它与数据的存储无关,是独立于计算机的。因此,数据的逻辑结构可以看作是从具体问题抽象出来的数学模型。

存储结构:数据对象在计算机中的存储表示,也称为物理结构

抽象数据类型由用户定义的,表示应用问题的数学模型,以及定义在这个模型上的一组操作的总称。具体包括三部分:数据对象、数据对象上关系的集合和对数据对象的基本操作的集合。

2.试举一个数据结构的例子,叙述其逻辑结构和存储结构两方面的含义和相互关系。

答案:

例如有一张学生基本信息表,包括学生的学号、姓名、性别、籍贯、专业等。每个学生基本信息记录对应一个数据元素,学生记录按顺序号排列,形成了学生基本信息记录的线性序列。对于整个表来说,只有一个开始结点(它的前面无记录)和一个终端结点(它的后面无记录),其他的结点则各有一个也只有一个直接前趋和直接后继。学生记录之间的这种关系就确定了学生表的逻辑结构,即线性结构。

这些学生记录在计算机中的存储表示就是存储结构。如果用连续的存储单元(如用数组表示)来存放这些记录,则称为顺序存储结构;如果存储单元不连续,而是随机存放各个记录,然后用指针进行链接,则称为链式存储结构。

即相同的逻辑结构,可以对应不同的存储结构。

3.简述逻辑结构的四种基本关系并画出它们的关系图。

答案:

(1)集合结构

数据元素之间除了“属于同一集合”的关系外,别无其他关系。例如,确定一名学生是否为班级成员,只需将班级看做一个集合结构。

(2)线性结构

数据元素之间存在一对一的关系。例如,将学生信息数据按照其入学报到的时间先后顺序进行排列,将组成一个线性结构。

(3)树结构

数据元素之间存在一对多的关系。例如,在班级的管理体系中,班长管理多个组长,每位组长管理多名组员,从而构成树形结构。

(4)图结构或网状结构

数据元素之间存在多对多的关系。例如,多位同学之间的朋友关系,任何两位同学都可以是朋友,从而构成图形结构或网状结构。

其中树结构和图结构都属于非线性结构。

 四类基本逻辑结构关系图

4存储结构由哪两种基本的存储方法实现?

答案:

(1)顺序存储结构

顺序存储结构是借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系,通常借助程序设计语言的数组类型来描述。

(2)链式存储结构

顺序存储结构要求所有的元素依次存放在一片连续的存储空间中,而链式存储结构,无需占用一整块存储空间。但为了表示结点之间的关系,需要给每个结点附加指针字段,用于存放后继元素的存储地址。所以链式存储结构通常借助于程序设计语言的指针类型来描述。

5选择题

(1)在数据结构中,从逻辑上可以把数据结构分成(   )。

A动态结构和静态结构     B紧凑结构和非紧凑结构

C.线性结构和非线性结构   D内部结构和外部结构

答案:C

(2)与数据元素本身的形式、内容、相对位置、个数无关的是数据的(   )。

A存储结构               B存储实现

C逻辑结构               D运算实现

答案:C

(3)通常要求同一逻辑结构中的所有数据元素具有相同的特性,这意味着(   )。

   A数据具有同一特点

B.不仅数据元素所包含的数据项的个数要相同,而且对应数据项的类型要一致

C每个数据元素都一样

D数据元素所包含的数据项的个数要相等

答案:B

(4)以下说法正确的是(   )。

A数据元素是数据的最小单位

B数据项是数据的基本单位

C数据结构是带有结构的各数据项的集合

D.一些表面上很不相同的数据可以有相同的逻辑结构

答案:D

解释:数据元素是数据的基本单位,数据项是数据的最小单位,数据结构是带有结构的各数据元素的集合。

(5)算法的时间复杂度取决于(    )。

A.问题的规模          B.待处理数据的初态

C.计算机的配置            D.A和B

答案:D

解释:算法的时间复杂度不仅与问题的规模有关,还与问题的其他因素有关。如某些排序的算法,其执行时间与待排序记录的初始状态有关。为此,有时会对算法有最好、最坏以及平均时间复杂度的评价。

(6)以下数据结构中,(  )是非线性数据结构

A.树        B.字符串       C.队列           D.栈

答案:A

字符串为什么是线性结构

字符串是线性结构是因为字符串中的每个字符都按照一定的顺序依次排列,字符之间的关系是相邻的。在字符串中,每个字符都只与其前后紧邻的字符有关系,而不会有跳跃的关系。因此,字符串可以用一个线性的数据结构来表示和存储,比如数组或链表。这种线性结构的特点使得我们可以通过索引或指针轻松地访问和操作字符串中的每个字符,同时也方便对字符串进行各种操作,比如查找、替换、连接等。所以字符串可以看作是一种线性结构。
 

6.试分析下面各程序段的时间复杂度。

(1)x=90; y=100; 

while(y>0)

if(x>100)

 {x=x-10;y--;}

else x++;

答案:O(1)

解释:程序的执行次数为常数阶。

(2)for (i=0; i<n; i++)

for (j=0; j<m; j++)

a[i][j]=0;

答案:O(m*n)

解释:语句a[i][j]=0;的执行次数为m*n。

(3)s=0;

     for i=0; i<n; i++)

for(j=0; j<n; j++)

         s+=B[i][j];

sum=s;

答案:O(n2)

解释:语句s+=B[i][j];的执行次数为n2。

(4)i=1;

     while(i<=n)

        i=i*3;

答案:O(log3n)

解释:语句i=i*3;的执行次数为 ëlog3nû

(5)x=0;

for(i=1; i<n; i++)

   for (j=1; j<=n-i; j++)

x++;

答案:O(n2)

解释:语句x++;的执行次数为n-1+n-2+……+1= n(n-1)/2。

(6)x=n; //n>1

y=0;

while(x≥(y+1)* (y+1))

    y++;

答案:O(

)

解释:语句y++;的执行次数为 ë

û。

第二章 线性表

1选择题

(1)顺序表中第一个元素的存储地址是100,每个元素的长度为2,则第5个元素的地址是(    )。

A110            B.108         C100          D120

答案:B

解释:顺序表中的数据连续存储,所以第5个元素的地址为:100+2*4=108

2在n个结点的顺序表中,算法的时间复杂度是O(1)的操作是(   )。

A.访问第i个结点(1≤i≤n)和求第i个结点的直接前驱(2≤i≤n)

B在第i个结点后插入一个新结点(1≤i≤n)

C删除第i个结点(1≤i≤n)

D将n个结点从小到大排序

答案:A

解释:在顺序表中插入一个结点的时间复杂度都是O(n2),排序的时间复杂度为O(n2)O(nlog2n)。顺序表是一种随机存取结构,访问第i个结点和求第i个结点的直接前驱都可以直接通过数组的下标直接定位,时间复杂度是O(1)

(3) 向一个有127个元素的顺序表中插入一个新元素并保持原来顺序不变,平均要移动  的元素个数为(   )。

A.8      B.63.5        C.63      D.7

答案:B

解释:平均要移动的元素个数为:n/2

(4)链接存储的存储结构所占存储空间(   )。

A.分两部分,一部分存放结点值,另一部分存放表示结点间关系的指针

B.只有一部分,存放结点值

C.只有一部分,存储表示结点间关系的指针

D.分两部分,一部分存放结点值,另一部分存放结点所占单元数

答案:A

(5)线性表若采用链式存储结构时,要求内存中可用存储单元的地址(   )。

A.必须是连续的        B.部分地址必须是连续的

C.一定是不连续的      D.连续或不连续都可以

答案:D

(6)线性表L在(   )情况下适用于使用链式结构实现。

A.需经常修改L中的结点值      B.需不断对L进行删除插入

C.L中含有大量的结点          D.L中结点结构复杂

答案:B

解释:链表最大的优点在于插入和删除时不需要移动数据,直接修改指针即可。

(7)单链表的存储密度(   )。

A.大于1        B.等于1      C.小于1    D.不能确定

答案:C

解释:存储密度是指一个结点数据本身所占的存储空间和整个结点所占的存储空间之比,假设单链表一个结点本身所占的空间为D,指针域所占的空间为N,则存储密度为:D/(D+N),一定小于1

(8)将两个各有n个元素的有序表归并成一个有序表,其最少的比较次数是(   )。

A.n            B.2n-1        C.2n        D.n-1

答案:A

解释:当第一个有序表中所有的元素都小于(或大于)第二个表中的元素,只需要用第二个表中的第一个元素依次与第一个表的元素比较,总计比较n次。

(9)在一个长度为n的顺序表中,在第i个元素(1≤i≤n+1)之前插入一个新元素时须向后移动(   )个元素。

A.n-i           B.n-i+1       C.n-i-1      D.I

答案:B

(10) 线性表L=(a1,a2,……an),下列说法正确的是(   )。

A.每个元素都有一个直接前驱和一个直接后继

B.线性表中至少有一个元素

C.表中诸元素的排列必须是由小到大或由大到小

D.除第一个和最后一个元素外,其余每个元素都有一个且仅有一个直接前驱和直接后继。

答案:D

(11) 创建一个包括n个结点的有序单链表的时间复杂度是(    )。

A.O(1)          B.O(n)            C.O(n2)          D.O(nlog2n)

答案:C

解释:单链表创建的时间复杂度是O(n),而要建立一个有序的单链表,则每生成一个新结点时需要和已有的结点进行比较,确定合适的插入位置,所以时间复杂度是O(n2)

(12) 以下说法错误的是(   )。                                               

A.求表长、定位这两种运算在采用顺序存储结构时实现的效率不比采用链式存储结构时实现的效率低

B.顺序存储的线性表可以随机存取

C.由于顺序存储要求连续的存储区域,所以在存储管理上不够灵活

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

答案:D

解释:链式存储结构和顺序存储结构各有优缺点,有不同的适用场合。

在数据结构和算法中,表长是指线性表中元素的个数,也可以理解为表的长度。线性表是一种最基本的数据结构,常见的有顺序表和链表。表长即表示这个线性表中元素的个数,可以通过计算或操作来获取表长的值。表长功能对于查询、插入、删除等操作都至关重要,因为在进行这些操作时需要根据表长的值来确定具体的位置和范围。
 

(13) 在单链表中,要将s所指结点插入到p所指结点之后,其语句应为(   )。

A.s->next=p+1; p->next=s;

B.(*p).next=s; (*s).next=(*p).next;

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

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

答案:D

正确答案是 D.s->next=p->next; p->next=s;

这是因为要将s所指结点插入到p所指结点之后,需要以下步骤:
1. 首先将 s结点的next指针 指向 p结点的next指针指向的结点,即s->next=p->next;
2. 然后将p结点的next指针指向s结点,即p->next=s;

因此,正确的语句应为D。

next指针通常是指向链表中下一个节点的指针,用来表示节点之间的连接关系。在链表数据结构中,每个节点都包含数据域和指针域。指针域通常包括两个指针,一个指向下一个节点(next指针),一个指向上一个节点(prev或prior指针,对应双向链表)。通过next指针,我们可以轻松地在链表中遍历节点,从一个节点跳转到下一个节点。

在单向链表中,next指针是唯一的指针域,用来指向下一个节点。而在双向链表中,每个节点有两个指针域,一个指向前一个节点,一个指向后一个节点。

总之,next指针在链表中起着连接节点、实现遍历和操作的重要作用。
 

 (14) 在双向链表存储结构中,删除p所指的结点时须修改指针(   )。

A.p->next->prior=p->prior; p->prior->next=p->next;

B.p->next=p->next->next; p->next->prior=p;

C.p->prior->next=p; p->prior=p->prior->prior;

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

正确答案是 A.p->next->prior=p->prior; p->prior->next=p->next;

在双向链表中,删除结点p需要修改其前驱结点和后继结点的指针:
1. 将p结点的前驱结点的后继指针指向p的后继结点,即p->prior->next=p->next;
2. 将p结点的后继结点的前驱指针指向p的前驱结点,即p->next->prior=p->prior;

这样就可以正确地删除结点p,并保持双向链表的正确连接。
 

答案:A

(15) 在双向循环链表中,在p指针所指的结点后插入q所指向的新结点,其修改指针的操作是(   )。

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

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

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

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

答案:C

2.算法设计

1将两个递增的有序链表合并为一个递增的有序链表。要求结果链表仍使用原来两个链表的存储空间, 不另外占用其它的存储空间。表中不允许有重复的数据。

[题目分析]

合并后的新表使用头指针Lc指向,pa和pb分别是链表LaLb的工作指针,初始化为相应链表的第一个结点,从第一个结点开始进行比较,当两个链表La和Lb均为到达表尾结点时,依次摘取其中较小者重新链接在Lc表的最后。如果两个表中的元素相等,只摘取La表中的元素,删除Lb表中的元素,这样确保合并后表中无重复的元素。当一个表到达表尾结点,为空时,将非空表的剩余元素直接链接在Lc表的最后。

[算法描述]

void MergeList(LinkList &La,LinkList &Lb,LinkList &Lc)

{//合并链表La和Lb,合并后的新表使用头指针Lc指向

  pa=La->next;  pb=Lb->next;   

   //pa和pb分别是链表LaLb的工作指针,初始化为相应链表的第一个结点

   Lc=pc=La;  //用La的头结点作为Lc的头结点

   while(pa && pb)

{if(pa->data<pb->data)

{pc->next=pa;

pc=pa;

pa=pa->next;}

     //取较小者La中的元素,将pa链接在pc的后面,pa指针后移

     else if(pa->data>pb->data) {pc->next=pb; pc=pb; pb=pb->next;}

      //取较小者Lb中的元素,将pb链接在pc的后面,pb指针后移

   else //相等时取La中的元素,删除Lb中的元素

{pc->next=pa;pc=pa;pa=pa->next;

      q=pb->next;delete pb ;pb =q;

}

     }

 pc->next=pa?pa:pb;    //插入剩余段

     delete Lb;            //释放Lb的头结点

3已知两个链表A和B分别表示两个集合,其元素递增排列。请设计算法求出A与B的交集,并存放于A链表中。

[题目分析]

只有同时出现在两集合中的元素才出现在结果表中,合并后的新表使用头指针Lc指向。pa和pb分别是链表LaLb的工作指针,初始化为相应链表的第一个结点,从第一个结点开始进行比较,当两个链表La和Lb均为到达表尾结点时,如果两个表中相等的元素时,摘取La表中的元素,删除Lb表中的元素;如果其中一个表中的元素较小时,删除此表中较小的元素,此表的工作指针后移。当链表La和Lb有一个到达表尾结点,为空时,依次删除另一个非空表中的所有元素。

[算法描述]

void Mix(LinkList& La, LinkList& Lb, LinkList& Lc)

{  pa=La->next;pb=Lb->next;

pa和pb分别是链表LaLb的工作指针,初始化为相应链表的第一个结点

Lc=pc=La; //La的头结点作为Lc的头结点

while(pa&&pb)

{ if(pa->data==pb->data)∥交集并入结果表中。

   { pc->next=pa;pc=pa;pa=pa->next;

     u=pb;pb=pb->next; delete u;}

  else if(pa->data<pb->data) {u=pa;pa=pa->next; delete u;}

else {u=pb; pb=pb->next; delete u;}

}

while(pa) {u=pa; pa=pa->next; delete u;}∥ 释放结点空间

while(pb) {u=pb; pb=pb->next; delete u;}∥释放结点空间

pc->next=null;∥置链表尾标记。

delete Lb;  //释放Lb的头结点

   }

4已知两个链表A和B分别表示两个集合,其元素递增排列。请设计算法求出两个集合A和B 的差集(即仅由在A中出现而不在B中出现的元素所构成的集合),并以同样的形式存储,同时返回该集合的元素个数。

[题目分析]

求两个集合A和B的差集是指在A中删除A和B中共有的元素,即删除链表中的相应结点,所以要保存待删除结点的前驱,使用指针pre指向前驱结点。pa和pb分别是链表LaLb的工作指针,初始化为相应链表的第一个结点,从第一个结点开始进行比较,当两个链表La和Lb均为到达表尾结点时,如果La表中的元素小于Lb表中的元素,pre置为La表的工作指针pa删除Lb表中的元素;如果其中一个表中的元素较小时,删除此表中较小的元素,此表的工作指针后移。当链表La和Lb有一个为空时,依次删除另一个非空表中的所有元素。

[算法描述]

void DifferenceLinkList& La, LinkList& Lb,int *n

{ ∥差集的结果存储于单链表La中,*n是结果集合中元素个数,调用时为0

pa=La->next; pb=Lb->next;     

∥pa和pb分别是链表LaLb的工作指针,初始化为相应链表的第一个结点

  pre=La;          ∥pre为La中pa所指结点的前驱结点的指针

  while(pa&&pb)

{if(pa->data<q->data){pre=pa;pa=pa->next;*n++;}

∥ A链表中当前结点指针后移

else if(pa->data>q->data)q=q->next;     ∥B链表中当前结点指针后移

    else {pre->next=pa->next;      ∥处理A,B中元素值相同的结点,应删除

          u=pa; pa=pa->next; delete u;}   ∥删除结点

}

}

5设计算法将一个带头结点的单链表A分解为两个具有相同结构的链表B、C,其中B表的结点为A表中值小于零的结点,而C表的结点为A表中值大于零的结点(链表A中的元素为非零整数,要求B、C表利用A表的结点)。

[题目分析]

B表的头结点使用原来A表的头结点,为C表新申请一个头结点。从A表的第一个结点开始,依次取其每个结点p,判断结点p的值是否小于0,利用前插法,将小于0的结点插入B表,大于等于0的结点插入C表。

[算法描述]

void DisCompose(LinkedList A)

{  B=A;

B->next= NULL;    ∥B表初始化

     C=new LNode;∥为C申请结点空间

     C->next=NULL;     ∥C初始化为空表

     p=A->next;        ∥p为工作指针

     while(p!= NULL)

     { r=p->next;      ∥暂存p的后继

       if(p->data<0)

        {p->next=B->next; B->next=p; }∥将小于0的结点链入B表,前插法

       else {p->next=C->next; C->next=p; }∥大于等于0的结点链入C表,前插法

       p=r;∥p指向新的待处理结点。

     }

}

6设计一个算法,通过一趟遍历在单链表中确定值最大的结点。

[题目分析]

假定第一个结点中数据具有最大值,依次与下一个元素比较,若其小于下一个元素,则设其下一个元素为最大值,反复进行比较,直到遍历完该链表。

[算法描述]

ElemType Max (LinkList L ){

    if(L->next==NULL) return NULL;

    pmax=L->next; //假定第一个结点中数据具有最大值

    p=L->next->next;

    while(p != NULL ){//如果下一个结点存在

        if(p->data > pmax->data) pmax=p;//如果p的值大于pmax的值,则重新赋值

        p=p->next;//遍历链表

    }

    return pmax->data;

7设计一个算法,通过遍历一趟,将链表中所有结点的链接方向逆转,仍利用原表的存储空间。

[题目分析]

从首元结点开始,逐个地把链表L的当前结点p插入新的链表头部。

[算法描述]

void  inverse(LinkList &L)

{// 逆置带头结点的单链表 L

    p=L->next;  L->next=NULL;

    while ( p) {

        q=p->next;    // q指向*p的后继

        p->next=L->next;

        L->next=p;       // *p插入在头结点之后

        p = q;

    }

}

8设计一个算法,删除递增有序链表中值大于mink且小于maxk的所有元素(mink和maxk是给定的两个参数,其值可以和表中的元素相同,也可以不同 )。

[题目分析]

分别查找第一个值>mink的结点和第一个值 ≥maxk的结点,再修改指针,删除值大于mink且小于maxk的所有元素。

[算法描述]

void delete(LinkList &L, int mink, int maxk) {

   p=L->next; //首元结点

   while (p && p->data<=mink)

      { pre=p;  p=p->next; } //查找第一个值>mink的结点

   if (p)

{while (p && p->data<maxk)  p=p->next;

                      // 查找第一个值 ≥maxk的结点

      q=pre->next;   pre->next=p;  // 修改指针

      while (q!=p)

         { s=q->next;  delete q;  q=s; } // 释放结点空间

   }//if

}

9已知p指向双向循环链表中的一个结点,其结点结构为data、prior、next三个域,写出算法change(p),交换p所指向的结点和它的前缀结点的顺序。

[题目分析]

知道双向循环链表中的一个结点,与前驱交换涉及到四个结点(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结束。

10已知长度为n的线性表A采用顺序存储结构,请写一时间复杂度为O(n)、空间复杂度为O(1)的算法,该算法删除线性表中所有值为item的数据元素。

[题目分析]

在顺序存储的线性表上删除元素,通常要涉及到一系列元素的移动(删第i个元素,第i+1至第n个元素要依次前移)。本题要求删除线性表中所有值为item的数据元素,并未要求元素间的相对位置不变。因此可以考虑设头尾两个指针(i=1,j=n),从两端向中间移动,凡遇到值item的数据元素时,直接将右端元素左移至值为item的数据元素位置。

[算法描述]

void  Delete(ElemType A[ ],int  n)

∥A是有n个元素的一维数组,本算法删除A中所有值为item的元素。

{i=1;j=n;∥设置数组低、高端指针(下标)。

 while(i<j)

   {while(i<j && A[i]!=item)i++;         ∥若值不为item,左移指针。

    if(i<j)while(i<j && A[j]==item)j--;∥若右端元素为item,指针左移

    if(i<j)A[i++]=A[j--];}

第三章 栈和队列

1选择题

(1)若让元素1,2,3,4,5依次进栈,则出栈次序不可能出现在(  )种情况。

A5,4,3,2,1   B.2,1,5,4,3    C43125    D2,3,5,4,1

答案:C

解释:栈是后进先出的线性表,不难发现C选项中元素1比元素2先出栈,违背了栈的后进先出原则,所以不可能出现C选项所示的情况。

(2)若已知一个栈的入栈序列是1,2,3,…,n,其输出序列为p1,p2,p3,…,pn,若p1=n,则pi为(  )。

A.i               B.n-i               C.n-i+1            D.不确定

答案:C

解释:栈是后进先出的线性表,一个栈的入栈序列是123,…,n,而输出序列的第一个元素为n,说明123,…,n一次性全部进栈,再进行输出,所以p1=np2=n-1,…,pi=n-i+1

(3)数组Q[n]用来表示一个循环队列,f为当前队列头元素的前一位置,r为队尾元素的位置,假定队列中元素的个数小于n,计算队列中元素个数的公式为(  )。

Ar-f             B.(n+f-r)%n       C.n+r-f           D.(n+r-f)%n

答案:D

解释:对于非循环队列,尾指针和头指针的差值便是队列的长度,而对于循环队列,差值可能为负数,所以需要将差值加上MAXSIZE(本题为n),然后与MAXSIZE(本题为n)求余,即(n+r-f)%n。

(4)链式栈结点为:(data,link),top指向栈顶.若想摘除栈顶结点,并将删除结点的值保存到x中,则应执行操作(  )。

Ax=top->data;top=top->link         B.top=top->link;x=top->link;   

C.x=top;top=top->link;                 D.x=top->link;

答案:A

解释:x=top->data将结点的值保存到x中,top=top->link栈顶指针指向栈顶下一结点,即摘除栈顶结点。

(5)设有一个递归算法如下

        int fact(int n) {  //n大于等于0

             if(n<=0) return 1;

             else return n*fact(n-1);        }

则计算fact(n)需要调用该函数的次数为(  )。 

A n+1              B n-1              C n                  D n+2

答案:A

解释:特殊值法。设n=0,易知仅调用一次fact(n)函数,故选A

(6)栈在 (  )中有所应用。

A递归调用       B函数调用      C表达式求值        D前三个选项都有

答案:D

解释:递归调用、函数调用、表达式求值均用到了栈的后进先出性质。

(7)为解决计算机主机与打印机间速度不匹配问题,通常设一个打印数据缓冲区。主机将要输出的数据依次写入该缓冲区,而打印机则依次从该缓冲区中取出数据。该缓冲区的逻辑结构应该是(  )。

A队列           B栈            C 线性表           D有序表

答案:A

解释:解决缓冲区问题应利用一种先进先出的线性表,而队列正是一种先进先出的线性表。

(8)设栈S和队列Q的初始状态为空,元素e1、e2、e3、e4、e5和e6依次进入栈S,一个元素出栈后即进入Q,若6个元素出队的序列是e2、e4、e3、e6、e5和e1,则栈S的容量至少应该是( )。

A2              B            C4                D 6

答案:B

解释:元素出队的序列是e2e4e3e6e5e1,可知元素入队的序列是e2e4e3e6e5e1,即元素出栈的序列也是e2e4e3e6e5e1,而元素e1e2e3e4e5e6依次进入栈,易知栈S中最多同时存在3个元素,故栈S的容量至少为3

(9)若一个栈以向量V[1..n]存储,初始栈顶指针top设为n+1,则元素x进栈的正确操作是(    )。

A.top++; V[top]=x;                     B.V[top]=x; top++;

C.top--; V[top]=x;                     D.V[top]=x; top--;

答案:C

解释:初始栈顶指针topn+1,说明元素从数组向量的高端地址进栈,又因为元素存储在向量空间V[1..n]中,所以进栈时top指针先下移变为n,之后将元素x存储在V[n]

(10)设计一个判别表达式中左,右括号是否配对出现的算法,采用( )数据结构最佳。

A.线性表的顺序存储结构              B队列    

C. 线性表的链式存储结构              D.

答案:D

解释:利用栈的后进先出原则。

(11)用链接方式存储的队列,在进行删除运算时( )。

A. 仅修改头指针                      B. 仅修改尾指针

C. 头、尾指针都要修改                D. 头、尾指针可能都要修改

答案:D

解释:一般情况下只修改头指针,但是,当删除的是队列中最后一个元素时,队尾指针也丢失了,因此需对队尾指针重新赋值。

(12)循环队列存储在数组A[0..m]中,则入队时的操作为( )。

A. rear=rear+1                       B. rear=(rear+1)%(m-1)

  C. rear=(rear+1)%m                   D. rear=(rear+1)%(m+1)

答案:D

解释:数组A[0..m]中共含有m+1个元素,故在求模运算时应除以m+1。

(13)最大容量为n的循环队列,队尾指针是rear,队头是front,则队空的条件是( )。

  A. (rear+1)%n==front                  B. rear==front                                                          

C.rear+1==front                      D. (rear-l)%n==front

答案:B

解释:最大容量为n的循环队列,队满条件是(rear+1)%n==front,队空条件是rear==front。

(14)栈和队列的共同点是( )。

A. 都是先进先出                       B. 都是先进后出  

C. 只允许在端点处插入和删除元素       D. 没有共同点

答案:C

解释:栈只允许在栈顶处进行插入和删除元素,队列只允许在队尾插入元素和在队头删除元素。

(15)一个递归算法必须包括( )。

A. 递归部分                           B. 终止条件和递归部分

C. 迭代部分                           D. 终止条件和迭代部分

答案:B

2.算法设计

(1)将编号为0和1的两个栈存放于一个数组空间V[m]中,栈底分别处于数组的两端。当第0号栈的栈顶指针top[0]等于-1时该栈为空,当第1号栈的栈顶指针top[1]等于m时该栈为空。两个栈均从两端向中间增长。试编写双栈初始化,判断栈空、栈满、进栈和出栈等算法的函数。双栈数据结构的定义如下:

Typedef struct

{int top[2],bot[2];                     //栈顶和栈底指针

 SElemType *V;                       //栈数组

 int m;                                       //栈最大可容纳元素个数

}DblStack

[题目分析]

两栈共享向量空间,将两栈栈底设在向量两端,初始时,左栈顶指针为-1,右栈顶为m。两栈顶指针相邻时为栈满。两栈顶相向、迎面增长,栈顶指针指向栈顶元素。

[算法描述]

(1) 栈初始化

int Init()

 {S.top[0]=-1;

  S.top[1]=m;

  return 1; //初始化成功

}

(2) 入栈操作:

int push(stk S ,int i,int x)

i为栈号,i=0表示左栈,i=1为右栈,x是入栈元素。入栈成功返回1,失败返回0

{if(i<0||i>1){ cout<<“栈号输入不对”<<endl;exit(0);}

if(S.top[1]-S.top[0]==1) {cout<<“栈已满”<<endl;return(0);}

switch(i)

 {case 0: S.V[++S.top[0]]=x; return(1); break;

case 1: S.V[--S.top[1]]=x; return(1);

}

}push

(3) 退栈操作

ElemType pop(stk S,int i)

∥退栈。i代表栈号,i=0时为左栈,i=1时为右栈。退栈成功时返回退栈元素

∥否则返回-1

{if(i<0 || i>1){cout<<“栈号输入错误”<<endlexit(0);}

 switch(i)

{case 0: if(S.top[0]==-1) {cout<<“栈空”<<endlreturn-1);}

else return(S.V[S.top[0]--]);

case 1: if(S.top[1]==m { cout<<“栈空”<<endl; return(-1);}

else return(S.V[S.top[1]++]);

   }switch     

}∥算法结束

(4) 判断栈空

int Empty();

{return (S.top[0]==-1 && S.top[1]==m);

}

[算法讨论

请注意算法中两栈入栈和退栈时的栈顶指针的计算。左栈是通常意义下的栈,而右栈入栈操作时,其栈顶指针左移(减1),退栈时,栈顶指针右移(加1)。

(2)回文是指正读反读均相同的字符序列,如“abba”和“abdba”均是回文,但“good”不是回文。试写一个算法判定给定的字符向量是否为回文。(提示:将一半字符入栈) 

[题目分析]

将字符串前一半入栈,然后,栈中元素和字符串后一半进行比较。即将第一个出栈元素和后一半串中第一个字符比较,若相等,则再出栈一个元素与后一个字符比较,……,直至栈空,结论为字符序列是回文。在出栈元素与串中字符比较不等时,结论字符序列不是回文。

[算法描述]

#define StackSize 100 //假定预分配的栈空间最多为100个元素

typedef char DataType;//假定栈元素的数据类型为字符

typedef struct

{DataType data[StackSize];

int top;

}SeqStack;

int IsHuiwen( char *t)

{//判断t字符向量是否为回文,若是,返回1,否则返回0

SeqStack s;

int i , len;

char temp;

InitStack( &s);

len=strlen(t); //求向量长度

for ( i=0; i<len/2; i++)//将一半字符入栈

Push( &s, t[i]);

while( !EmptyStack( &s))

{// 每弹出一个字符与相应字符比较

temp=Pop (&s);

if( temp!=S[i])  return 0 ;// 不等则返回0

else i++;

return 1 ; // 比较完毕均相等则返回 1

}

(3)设从键盘输入一整数的序列:a1, a2, a3,…,an,试编写算法实现:用栈结构存储输入的整数,当ai≠-1时,将ai进栈;当ai=-1时,输出栈顶整数并出栈。算法应对异常情况(入栈满等)给出相应的信息。

[算法描述]

#define maxsize 栈空间容量

void InOutS(int s[maxsize])

//s是元素为整数的栈,本算法进行入栈和退栈操作。

{int top=0;             //top为栈顶指针,定义top=0时为栈空。

 for(i=1; i<=n; i++)    //n个整数序列作处理。

    {cin>>x);    //从键盘读入整数序列。

     if(x!=-1)           // 读入的整数不等于-1时入栈。

    {if(top==maxsize-1){cout<<“栈满”<<endl;exit(0);}

else s[++top]=x; //x入栈。

     else   //读入的整数等于-1时退栈。

     {if(top==0){ cout<<“栈空”<<endl;exit(0);}

else cout<<“出栈元素是”<< s[top--]<<endl;}

}

}//算法结束。

(4)从键盘上输入一个后缀表达式,试编写算法计算表达式的值。规定:逆波兰表达式的长度不超过一行,以$符作为输入结束,操作数之间用空格分隔,操作符只可能有+、-、*、/四种运算。例如:234 34+2*$。

[题目分析]

逆波兰表达式(即后缀表达式)求值规则如下:设立运算数栈OPND,对表达式从左到右扫描(读入),当表达式中扫描到数时,压入OPND栈。当扫描到运算符时,从OPND退出两个数,进行相应运算,结果再压入OPND栈。这个过程一直进行到读出表达式结束符$,这时OPND栈中只有一个数,就是结果。

[算法描述]

float expr( )

//从键盘输入逆波兰表达式,以‘$’表示输入结束,本算法求逆波兰式表达式的值。

{float OPND[30];   // OPND是操作数栈。

init(OPND);       //两栈初始化。

float num=0.0;    //数字初始化。

cin>>x;//x是字符型变量。

while(x!=’$’)

    {switch

      {case‘0’<=x<=’9’:

while((x>=’0’&&x<=’9’)||x==’.’)  //拼数

if(x!=’.’)   //处理整数

{num=num*10+(ord(x)-ord(‘0’)); cin>>x;}

else           //处理小数部分。

{scale=10.0; cin>>x;

while(x>=’0’&&x<=’9’)

{num=num+(ord(x)-ord(‘0’)/scale;

scale=scale*10;  cin>>x; }

}//else

push(OPND,num); num=0.0;//数压入栈,下个数初始化

       case x=‘ ’:break;  //遇空格,继续读下一个字符。

       case x=‘+’:push(OPND,pop(OPND)+pop(OPND));break;

       case x=‘-’:x1=pop(OPND);x2=pop(OPND);push(OPND,x2-x1);break;

       case x=‘*’:push(OPND,pop(OPND)*pop(OPND));break;

       case x=‘/’:x1=pop(OPND);x2=pop(OPND);push(OPND,x2/x1);break;

       default:       //其它符号不作处理。

     }//结束switch

     cin>>x;//读入表达式中下一个字符。

   }//结束while(x!=‘$’)

cout<<“后缀表达式的值为”<<pop(OPND);

}//算法结束。

[算法讨论]假设输入的后缀表达式是正确的,未作错误检查。算法中拼数部分是核心。若遇到大于等于‘0’且小于等于‘9’的字符,认为是数。这种字符的序号减去字符‘0’的序号得出数。对于整数,每读入一个数字字符,前面得到的部分数要乘上10再加新读入的数得到新的部分数。当读到小数点,认为数的整数部分已完,要接着处理小数部分。小数部分的数要除以10(或10的幂数)变成十分位,百分位,千分位数等等,与前面部分数相加。在拼数过程中,若遇非数字字符,表示数已拼完,将数压入栈中,并且将变量num恢复为0,准备下一个数。这时对新读入的字符进入‘+’、‘-’、‘*’、‘/’及空格的判断,因此在结束处理数字字符的case后,不能加入break语句。

(5)假设以I和O分别表示入栈和出栈操作。栈的初态和终态均为空,入栈和出栈的操作序列可表示为仅由I和O组成的序列,称可以操作的序列为合法序列,否则称为非法序列。

①下面所示的序列中哪些是合法的?

   A. IOIIOIOO     B. IOOIOIIO      C. IIIOIOIO     D. IIIOOIOO

②通过对①的分析,写出一个算法,判定所给的操作序列是否合法。若合法,返回true,否则返回false(假定被判定的操作序列已存入一维数组中)。

答案:

A和D是合法序列,B和C 是非法序列。

设被判定的操作序列已存入一维数组A中。

int Judge(char A[])

        //判断字符数组A中的输入输出序列是否是合法序列。如是,返回true,否则返回false。

        {i=0;                //i为下标。

         j=k=0;              //j和k分别为I和字母O的的个数。

         while(A[i]!=‘\0’) //当未到字符数组尾就作。

           {switch(A[i])

             {case‘I’: j++; break; //入栈次数增1。

              case‘O’: k++; if(k>j){cout<<“序列非法”<<ednl;exit(0);}

              }

i++; //不论A[i]是‘I’或‘O’,指针i均后移。}

         if(j!=k) {cout<<“序列非法”<<endl;return(false);}

         else { cout<<“序列合法”<<endl;return(true);}

        }//算法结束。

     [算法讨论]在入栈出栈序列(即由‘I’和‘O’组成的字符串)的任一位置,入栈次数(‘I’的个数)都必须大于等于出栈次数(即‘O’的个数),否则视作非法序列,立即给出信息,退出算法。整个序列(即读到字符数组中字符串的结束标记‘\0’),入栈次数必须等于出栈次数(题目中要求栈的初态和终态都为空),否则视为非法序列。

(6)假设以带头结点的循环链表表示队列,并且只设一个指针指向队尾元素站点(注意不设头指针) ,试编写相应的置空队、判队空 、入队和出队等算法。

[题目分析]

置空队就是建立一个头节点,并把头尾指针都指向头节点,头节点是不存放数据的;判队空就是当头指针等于尾指针时,队空;入队时,将新的节点插入到链队列的尾部,同时将尾指针指向这个节点;出队时,删除的是队头节点,要注意队列的长度大于1还是等于1的情况,这个时候要注意尾指针的修改,如果等于1,则要删除尾指针指向的节点。

[算法描述]

//先定义链队结构:

typedef struct queuenode

{Datatype data;

struct queuenode *next;

}QueueNode; //以上是结点类型的定义

typedef struct

{queuenode *rear;

}LinkQueue; //只设一个指向队尾元素的指针

  1. 置空队

void InitQueue( LinkQueue *Q)
{ //
置空队:就是使头结点成为队尾元素
 QueueNode *s;

Q->rear = Q->rear->next;//将队尾指针指向头结点

while (Q->rear!=Q->rear->next)//当队列非空,将队中元素逐个出队

{s=Q->rear->next;

Q->rear->next=s->next;

delete s;

 }//回收结点空间

}

  1. 判队空 

int EmptyQueue( LinkQueue *Q)

{ //判队空。当头结点的next指针指向自己时为空队

 return Q->rear->next->next==Q->rear->next;

}

  1. 入队

void EnQueue( LinkQueue *Q, Datatype x)

{ //入队。也就是在尾结点处插入元素

QueueNode *p=new QueueNode;//申请新结点

p->data=x; p->next=Q->rear->next;//初始化新结点并链入

Q-rear->next=p; 

Q->rear=p;//将尾指针移至新结点

}

  1. 出队

Datatype DeQueue( LinkQueue *Q)

{//出队,把头结点之后的元素摘下

Datatype t;

QueueNode *p;

if(EmptyQueue( Q ))

Error("Queue underflow");

p=Q->rear->next->next; //p指向将要摘下的结点

x=p->data; //保存结点中数据

if (p==Q->rear)

{//当队列中只有一个结点时,p结点出队后,要将队尾指针指向头结点

 Q->rear = Q->rear->next;

Q->rear->next=p->next;

}

else 

Q->rear->next->next=p->next;//摘下结点p

delete p;//释放被删结点

return x;

}

(7)假设以数组Q[m]存放循环队列中的元素, 同时设置一个标志tag,以tag == 0和tag == 1来区别在队头指针(front)和队尾指针(rear)相等时,队列状态为“空”还是“满”。试编写与此结构相应的插入(enqueue)和删除(dlqueue)算法。

[算法描述

数据结构》(C语言版)是为“数据结构”课程编写的教材,也可作为学习数据结构及其算法的C程序设计的参数教材。学了数据结构后,许多以前写起来很繁杂的代码现在写起来很清晰明了. 本书的前半部分从抽象数据类型的角度讨论各种基本类型的数据结构及其应用;后半部分主要讨论查找和排序的各种实现方法及其综合分析比较。 全书采用类C语言作为数据结构和算法的描述语言。 本书概念表述谨,逻辑推理密,语言精炼,用词达意,并有配套出版的《数据结构题集》(C语言版),便于教学,又便于自学。 本书后附有光盘。光盘内容可在DOS环境下运行的以类C语言描述的“数据结构算法动态模拟辅助教学软件,以及在Windows环境下运行的以类PASCAL或类C两种语言描述的“数据结构算法动态模拟辅助教学软件”。内附 数据结构算法实现(蔚敏版配套实现程序) 目录: 第1章 绪论 1.1 什么是数据结构 1.2 基本概念和术语 1.3 抽象数据类型的表现与实现 1.4 算法和算法分析 第2章 线性表 2.1 线性表的类型定义 2.2 线性表的顺序表示和实现 2.3 线性表的链式表示和实现 2.4 一元多项式的表示及相加 第3章 栈和队列 3.1 栈 3.2 栈的应有和举例 3.3 栈与递归的实现 3.4 队列 3.5 离散事件模拟 第4章 串 4.1 串类型的定义 4.2 串的表示和实现 4.3 串的模式匹配算法 4.4 串操作应用举例 第5章 数组和广义表 5.1 数组的定义 5.2 数组的顺序表现和实现 5.3 矩阵的压缩存储 5.4 广义表的定义 5.5 广义表的储存结构 5.6 m元多项式的表示 5.7 广义表的递归算法第6章 树和二叉树 6.1 树的定义和基本术语 6.2 二叉树 6.2.1 二叉树的定义 6.2.2 二叉树的性质 6.2.3 二叉树的存储结构 6.3 遍历二叉树和线索二叉树 6.3.1 遍历二叉树 6.3.2 线索二叉树 6.4 树和森林 6.4.1 树的存储结构 6.4.2 森林与二叉树的转换 6.4.3 树和森林的遍历 6.5 树与等价问题 6.6 赫夫曼树及其应用 6.6.1 最优二叉树(赫夫曼树) 6.6.2 赫夫曼编码 6.7 回溯法与树的遍历 6.8 树的计数 第7章 图 7.1 图的定义和术语 7.2 图的存储结构 7.2.1 数组表示法 7.2.2 邻接表 7.2.3 十字链表 7.2.4 邻接多重表 7.3 图的遍历 7.3.1 深度优先搜索 7.3.2 广度优先搜索 7.4 图的连通性问题 7.4.1 无向图的连通分量和生成树 7.4.2 有向图的强连通分量 7.4.3 最小生成树 7.4.4 关节点和重连通分量 7.5 有向无环图及其应用 7.5.1 拓扑排序 7.5.2 关键路径 7.6 最短路径 7.6.1 从某个源点到其余各顶点的最短路径 7.6.2 每一对顶点之间的最短路径 第8章 动态存储管理 8.1 概述 8.2 可利用空间表及分配方法 8.3 边界标识法 8.3.1 可利用空间表的结构 8.3.2 分配算法 8.3.3 回收算法 8.4 伙伴系统 8.4.1 可利用空间表的结构 8.4.2 分配算法 8.4.3 回收算法 8.5 无用单元收集 8.6 存储紧缩 第9章 查找 9.1 静态查找表 9.1.1 顺序表的查找 9.1.2 有序表的查找 9.1.3 静态树表的查找 9.1.4 索引顺序表的查找 9.2 动态查找表 9.2.1 二叉排序树和平衡二叉树 9.2.2 B树和B+树 9.2.3 键树 9.3 哈希表 9.3.1 什么是哈希表 9.3.2 哈希函数的构造方法 9.3.3 处理冲突的方法 9.3.4 哈希表的查找及其分析 第10章 内部排序 10.1 概述 10.2 入排序 10.2.1 直接入排序 10.2.2 其他入排序 10.2.3 希尔排序 10.3 快速排序 10.4 选择排序 10.4.1 简单选择排序 10.4.2 树形选择排序 10.4.3 堆排序 10.5 归并排序 10.6 基数排序 10.6.1 多关键字的排序 10.6.2 链式基数排序 10.7 各种内部排序方法的比较讨论 第11章 外部排序 11.1 外存信息的存取 11.2 外部排序的方法 11.3 多路平衡归并的实现 11.4 置换一选择排序 11.5 最佳归并树 第12章 文件 12.1 有关文件的基本概念 12.2 顺序文件 12.3 索引文件 12.4 ISAM文件和VSAM文件 12.4.1 ISAM文件 12.4.2 VSAM文件 12.5 直接存取文件(散列文件) 12.6 多关键字文件 12.6.1 多重表文件 12.6.2 倒排文件 附录A 名词索引 附录B 函数索引 参考书目
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东北霸总loudlee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值