(1)线性结构中数据元素之间是( )关系。
A.一对多 B.多对多 C.多对一 D.一对一
答:D
(2)数据结构中与所使用的计算机无关的是数据的( )结构。
A.存储 B.物理 C.逻辑 D.物理和存储
答:C
(3)算法分析的目的是( )。
A.找出数据结构的合理性 B.研究算法中的输入和输出的关系
C.分析算法的效率以求改进 D.分析算法的易懂性和文档性
答:C
(4)算法分析的两个主要方面是( )。
A.空间复杂性和时间复杂性 B.正确性和简明性
C.可读性和文档性 D.数据复杂性和程序复杂性
答:A
(5)计算机算法指的是( )。
A.计算方法 B. 排序方法 C.求解问题的有限运算序列 D.调度方法
答:C
(6)计算机算法必须具备输入、输出和( )等5个特性。
A.可行性、可移植性和可扩充性 B.可行性、确定性和有穷性
C.确定性、有穷性和稳定性 D.易读性、稳定性和安全性
答:B
(1)数据结构包括数据的 ① 、数据的 ② 和数据的 ③ 这三个方面的内容。
答:①逻辑结构 ②存储结构 ③运算
(2)数据结构按逻辑结构可分为两大类,它们分别是 ① 和 ② 。
答:①线性结构 ②非线性结构
(3)数据结构被形式地定义为(D,R),其中D是 ① 的有限集合,R是D上的 ② 有限集合。
答:①数据元素 ②关系
(4)在线性结构中,第一个结点 ① 前驱结点,其余每个结点有且只有1个前驱结点;最后一个结点 ② 后继结点,其余每个结点有且只有1个后继结点。
答:①没有 ②没有
(5)在树形结构中,树根结点没有 ① 结点,其余每个结点有且只有 ② 个前驱结点;叶子结点没有 ③ 结点,其余每个结点的后继结点数可以是 ④ 。
答:①前驱 ②1 ③后继 ④任意多个
(6)在图形结构中,每个结点的前驱结点数和后继结点数可以是( )。
答:任意多个
(7)数据的存储结构主要有四种,它们分别是 ① 、 ② 、 ③ 和 ④ 存储结构。
答:①顺序 ②链式 ③索引 ④哈希
(8)一个算法的效率可分为 ① 效率和 ② 效率。
答:①时间 ②空间
(1)数据结构和数据类型两个概念之间有区别吗?
答:简单地说,数据结构定义了一组按某些关系结合在一起的数组元素的集合。数据类型不仅定义了一组数据元素,而且还在其上定义了一组操作。
(2)简述线性结构、树形结构和图形结构的不同点。
答:线性结构反映结点间的逻辑关系是一对一的,树形线性结构反映结点间的逻辑关系是一对多的,图在结构反映结点间的逻辑关系是多对多的。
(3)设有采用二元组表示的数据逻辑结构S=(D,R),其中D={a,b,…,i},R={(a,b),(a,c),(c,d),(c,f),(f,h),(d,e),(f,g),(h,i)},问相对于关系R,哪些结点是开始结点,哪些结点是终端结点?
答:该逻辑结构为树形结构,其中a结点没有前驱结点,称为根结点,b、e、g、i结点没有后继结点,是终端结点,也称为叶子结点。
(4)以下各函数是算法中语句的执行频度,n为问题规模,给出对应的时间复杂度:
T1(n)=nlog2n-1000log2n
T2(n)= -1000log2n
T3(n)=n2-1000log2n
T4(n)=2nlog2n-1000log2n
答:T1(n)=O(nlog2n),T2(n)=O( ),T3(n)=O(n2),T4(n)=O(nlog2n)。
(5)分析下面程序段中循环语句的执行次数。
int j=0,s=0,n=100;
do
{ j=j+1;
s=s+10*j;
} while (j
答:j=0,第1次循环:j=1,s=10。第2次循环:j=2,s=30。第3次循环:j=3,s=60。第4次循环:j=4,s=100。while条件不再满足。所以,其中循环语句的执行次数为4。
(6)执行下面的语句时,语句s++的执行次数为多少?
int s=0;
for (i=1;i
for (j=n;j>=i;j--)
s++;
答:语句s的执行次数。
(7)设n为问题规模,求以下算法的时间复杂度。
void fun1(int n)
{ int x=0,i;
for (i=1;i<=n;i++)
for (j=i+1;j<=n;j++)
x++;
}
答:其中x++语句属基本运算语句, =O(n2)。
(8)设n为问题规模,是一个正偶数,试计算以下算法结束时m的值,并给出该算法的时间复杂度。
void fun2(int n)
{ int m=0;
for (i=1;i<=n;i++)
for (j=2*i;j<=n;j++)
m++;
}
答:由于内循环j的取值范围,所以i≤n/2,则,该程序段的时间复杂度为O(n2)。
有一个整型数组a,其中含有n个元素,设计尽可能好的算法求其中的最大元素和次大元素,并采用相关数据测试。
解:maxs算法用于返回数组a[0..n-1]中的最大元素值max1和次大元素值max2,max1和max2设计为引用类型。对应的程序如下:
#include
void maxs(int a[],int n,int &max1,int &max2)
{ int i;
max1=max2=a[0];
for (i=1;i
if (a[i]>max1)
{ max2=max1;
max1=a[i];
}
else if (a[i]>max2)
max2=a[i];
}
void main()
{ int a[]={1,4,10,6,8,3,5,7,9,2};
int n=10;
int max1,max2;
maxs(a,n,max1,max2);
printf("最大元素值=%d,次大元素值=%d\n",max1,max2);
}
(1)数据在计算机存储器内表示时,物理地址与逻辑地址相对顺序相同并且是连续的,称之为( )。
A.存储结构 B.逻辑结构 C.顺序存储结构 D.链式存储结构
答:C
(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
(3) 向一个有127个元素的顺序表中插入一个新元素并保持原来顺序不变,平均要移动( )个元素。
A.8 B.63.5 C.63 D.7
答:B
(4)链式存储结构所占存储空间( )。
A.分两部分,一部分存放结点值,另一部分存放表示结点间关系的指针
B.只有一部分,存放结点值
C.只有一部分,存储表示结点间关系的指针
D.分两部分,一部分存放结点值,另一部分存放结点所占单元数
答:A
(5)线性表若采用链式存储结构时,要求内存中可用存储单元的地址( )。
A.必须是连续的 B.部分地址必须是连续的
C.一定是不连续的 D.连续或不连续都可以
答:D
(6)一个线性表在( )情况下适用于采用链式存储结构。
A.需经常修改其中的结点值 B.需不断对其进行删除插入
C.其中含有大量的结点 D.其中结点结构复杂
答:B
(7)单链表的存储密度( )
A.大于1 B.等于1 C.小于1 D.不能确定
答:C
(1)在顺序表中插入或删除一个元素时,需要平均移动( ① )元素,具体移动的元素个数与( ② )有关。
答:①表中一半 ②表长和该元素在表中的位置
(2)向一个长度为n的顺序表的第i个元素(1≤i≤n+1)之前插入一个元素时,需向后移动( )个元素。
答:n-i+1
(3)向一个长度为n的顺序表中删除第i个元素(1≤i≤n)时,需向前移动( )个元素。
答:n-i
(4)在顺序表中访问任意一个元素的时间复杂度均为( ① ),因此顺序表也称为( ② )的数据结构。
答:①O(1) ②随机存取
(5)顺序表中逻辑上相邻的元素的物理位置( ① )相邻。单链表中逻辑上相邻的元素的物理位置( ② )相邻。
答:①一定 ②不一定
(6)在带头结点的单链表中,除头结点外,任一结点的存储位置由( )指示。
答:其前驱结点的链域的值
(7)在含有n个数据结点的单链表中要删除已知结点*p,需找到它的( ① ),其时间复杂度为( ② )。
答:①前驱结点的地址 ②O(n)
(8)含有n(n>1)个结点的循环双向链表中,为空的指针域数为( )。
答:0
(1)试比较顺序存储结构和链式存储结构的优缺点。在什么情况下用顺序表比链表好?
答:顺序存储结构中,相邻数据元素的存放地址也相邻,并要求内存中可用存储单元的地址必须是连续的。其优点是存储密度大,存储空间利用率高;缺点是插入或删除元素时不方便。
链式存储结构中,相邻数据元素可随意存放,但所占存储空间分两部分,一部分存放结点值,另一部分存放表示结点间关系的指针。其优点是插入或删除元素时很方便,使用灵活;缺点是存储密度小,存储空间利用率低。
顺序表适宜于做查找这样的静态操作;链表宜于做插入、删除这样的动态操作。若线性表的长度变化不大,且其主要操作是查找,则采用顺序表;若线性表的长度变化较大,且其主要操作是插入、删除操作,则采用链表。
(2)对于表长为n的顺序表,在任何位置上插入或删除一个元素的概率相等时,插入一个元素所需要移动的元素的平均个数为多少?删除一个元素所需要移动的平均个数为多少?
答:插入一个元素所需要移动的元素的平均个数为(n-1)/2,删除一个元素所需要移动的平均个数为n/2。
(3)在链表中设置头结点的作用是什么?
答:在链表中设置头结点后,不管链表是否为空表,头结点指针均不空,并使得对链表的操作(如插入和删除)在各种情况下统一,从而简化了算法的实现过程。
(4)对于双链表和单链表,在两个结点之间插入一个新结点时需修改的指针各为多少个?
答:对于双链表,在两个结点之间插入一个新结点时,需修改前驱结点的next域、后继结点的prior域和新插入结点的next、prior域。所以共修改4个指针。
对于单链表,在两个结点之间插入一个新结点时,需修改前一结点的next域,新插入结点的next域。所以共修改两个指针。
(5)某含有n(n>1)结点的线性表中,最常用的操作是在尾结点之后插入一个结点和删除第一个结点,则采用以下哪种存储方式最节省运算时间。
①单链表;
②仅有头指针不带头结点的循环单链表;
③双链表;
④仅有尾指针的循环单链表。
答:在单链表中,删除第一个结点的时间复杂度为O(1)。插入结点需找到前驱结点,所以在尾结点之后插入一个结点,需找到尾结点,对应的时间复杂度为O(n)。
在仅有头指针不带头结点的循环单链表中,删除第一个结点的时间复杂度O(n),因为删除第一个结点后还要将其改为循环单链表;在尾结点之后插入一个结点的时间复杂度也为O(n)。
在双链表中,删除第一个结点的时间复杂度为O(1);在尾结点之后插入一个结点,也需找到尾结点,对应的时间复杂度为O(n)。
在仅有尾指针的循环单链表中,通过该尾指针可以直接找到第一个结点,所以删除第一个结点的时间复杂度为O(1);在尾结点之后插入一个结点也就是在尾指针所指结点之后插入一个结点,时间复杂度也为O(1)。因此④最节省运算时间。
(1)设计一个高效算法,将顺序表的所有元素逆置,要求算法空间复杂度为O(1)。
解:遍历顺序表L的前半部分元素,对于元素L.data[i](0≤i<L.length/2),将其与后半部分对应元素L.data[L.length-i-1]进行交换。对应的算法如下:
void reverse(SqList &L)
{ int i;
ElemType x;
for (i=0;i
{ x=L.data[i]; //L.data[i]与L.data[L.length-i-1]交换
L.data[i]=L.data[L.length-i-1];
L.data[L.length-i-1]=x;
}
}
本算法的时间复杂度为O(n)。
(2)设计一个算法从顺序表中删除重复的元素,并使剩余元素间的相对次序保持不变。
解:对于顺序表L,用i从1开始遍历其元素,设L.data[0..j](j的初值为0)中没有重复的元素。检测L.data[i](j<i
void delsame(SqList &L) //L为引用型参数
{ int i,j=0,k;
for (i=1;i
{ k=0;
while (k<=j && L.data[k]!=L.data[i])
k++;
if (k>j) //表示L.data[i]和L.data[0..j]中所有元素都不相同
{ j++;
L.data[j]=L.data[i];
}
}
L.length=j+1; //顺序表长度置新值
}
本算法的时间复杂度为O(n2),空间复杂度为O(1)。
(3)设计一个算法从有序顺序表中删除重复的元素,并使剩余元素间的相对次序保持不变。
解:在有序顺序表L中,所有重复的元素应是相邻存放的,用k保存不重复出现的元素个数,先将不重复的有序区看成是L.data[0..0],置e=L.data[0],用i从1开始遍历L的所有元素:当L.data[i]≠e时,将它放在L.data[k]中,k增1,置e=L.data[i],最后将L的length置为k。对应的算法如下:
void delsame1(SqList &L) //L为引用型参数
{ int i,k=1; //k保存不重复的元素个数
ElemType e;
e=L.data[0];
for (i=1;i
{ if (L.data[i]!=e) //只保存不重复的元素
{ L.data[k]=L.data[i];
k++;
e=L.data[i];
}
}
L.length=k; //顺序表长度置新值
}
本算法是一个高效算法,其时间复杂度为O(n),空间复杂度为O(1)。如果每次遇到重复的元素,都通过移动其后所有元素来删除它,这样的时间复杂度会变成O(n2)。
(4)设计一个算法删除单链表L中第一个值为x的结点。
解:用p、q遍历整个单链表,p指向*q的前驱结点,q用于查找第一个值为x的结点,当找到后将*q结点删除,返回1;否则返回0。对应的算法如下:
int delx(SLink *&L,ElemType x)
{ SLink *p=L,*q=p->next; //p指向*q的前驱结点
while (q!=NULL && q->data!=x)
{ p=q;
q=q->next;
}
if (q!=NULL) //找到值为x的结点
{ p->next=q->next;
free(q);
return 1;
}
else return 0; //未找到值为x的结点
}
(5)设计一个算法判定单链表L是否是递增的。
解:判定链表L从第2个结点开始的每个结点的值是否比其前驱的值大。若有一个不成立,则整个链表便不是递增的;否则是递增的。对应的算法如下:
int increase(SLink *L)
{ SLink *pre=L->next,*p; //pre指向第一个数据结点
p=pre->next; //p指向*pre结点的后继结点
while (p!=NULL)
{ if (p->data>=pre->data) //若正序则继续判断下一个结点
{ pre=p; //pre、p同步后移
p=p->next;
}
else return 0;
}
return 1;
}
(6)有一个整数元素建立的单链表A,设计一个算法,将其拆分成两个单链表A和B,使得A单链表中含有所有的偶数结点,B单链表中所有的奇数结点,且保持原来的相对次序。
解:采用重新单链表的方法,由于要保持相对次序,所以采用尾插法建立新表A、B。用p遍历原单链表A的所有数据结点,若为偶数结点,将其链到A中,若为奇数结点,将其链到B中。对应的算法如下:
void Split(SLink *&A,SLink *&B)
{ SLink *p=A->next,*ra,*rb;
ra=A;
B=(SLink *)malloc(sizeof(SLink)); //建立头结点
rb=B; //r总是指向B链表的尾结点
while (p!=NULL)
{ if (p->data%2==0) //偶数结点
{ ra->next=p; //将*p结点链到A中
ra=p;
p=p->next;
}
else //奇数结点
{ rb->next=p; //将*p结点链到B中
rb=p;
p=p->next;
}
}
ra->next=rb->next=NULL;
}
本算法的时间复杂度为O(n),空间复杂度为O(1)。
(7)有一个有序单链表(从小到大排列),表头指针为L,设计一个算法向该单链表中插入一个元素为x的结点,使插入后该链表仍然有序。
解:先建立一个待插入的结点,然后依次与链表中的各结点的数据域比较大小,找到插入该结点的位置,最后插入该结点。对应的算法如下:
void inorderList(SLink *&L,ElemType x)
{ SLink *s,*p,*q;
s=(SLink *)malloc(sizeof(SLink)); //建立一个待插入的结点
s->data=x;s->next=NULL;
if (L==NULL || x
{ s->next=L; //把*s结点插入到头结点之后
L=s;
}
else
{ q=L; //寻找插入位置,p指向待比较的结点,q指向p的前驱结点
p=q->next;
while (p!=NULL && x>p->data) //若x小于p所指结点的data域值
if (x>p->data)
{ q=p;
p=p->next;
}
s->next=p; //将s结点插入到*q和*p之间
q->next=s;
}
}
(8)有一个单链表L,其中可能出现值域重复的结点,设计一个算法删除值域重复的结点。并分析算法的时间复杂度。
解:用p遍历单链表,用r遍历*p结点之后的结点,q始终指向*r结点的直接前驱结点,若r->data==p->data,则删除*r结点,否则q、r同步后移一个结点。对应的算法如下:
void dels1(SLink *&L)
{ SLink *p=L->next,*q,*r,*t;
while (p!=NULL)
{ q=p;
r=q->next;
while (r!=NULL)
{ if (r->data==p->data) //r指向被删结点
{ t=r->next;
q->next=t;
free(r);
r=t;
}
else
{ q=r;
r=r->next;
}
}
p=p->next;
}
}
本算法的时间复杂度为O(n2)。
(9)有一个递增有序单链表(允许出现值域重复的结点),设计一个算法删除值域重复的结点。并分析算法的时间复杂度。
解:由于是有序表,所以相同值域的结点都是相邻的。用p遍历递增单链表,若*p结点的值域等于其后结点的值域,则删除后者。对应的算法如下:
void dels(SLink *&L)
{ SLink *p=L->next,*q;
while (p->next!=NULL)
{ if (p->data==p->next->data) //找到重复值的结点
{ q=p->next; //q指向这个重复值的结点
p->next=q->next; //删除*q结点
free(q);
}
else p=p->next;
}
}
本算法的时间复杂度为O(n)。
(10)有一个双链表L,设计一个算法查找第一个元素值为x的结点,将其与后继结点进行交换。
解:先找到第一个元素值为x的结点*p,q指向其后继结点,本题是将*p结点移到*q结点之后,实现过程是:删除*p结点,再将其插入到*q结点之后。对应的算法如下:
int swap(DLink *L,ElemType x)
{ DLink *p=L->next,*q;
while (p!=NULL && p->data!=x)
p=p->next;
if (p==NULL) //未找到值为x的结点
return 0;
else //找到值为x的结点*p
{ q=p->next; //q指向*p的后继结点
if (q!=NULL) //*p结点不是尾结点
{ p->prior->next=q; //先删除*p结点
q->prior=p->prior;
p->next=q->next; //将*p结点插入到*q结点之后
if (q->next!=NULL)
q->next->prior=p;
q->next=p;
p->prior=q;
return 1;
}
else //*p结点是尾结点
return 0; //无法与后继结点交换,返回0
}
}
(11)对于有n(n≥1)个数据结点的循环单链表L,设计一个算法将所有结点逆置。
解:采用头插法重建循环单链表L的思路,先建立一个空的循环单链表,用p遍历所有数据结点,每次将*p结点插入到前端。对应的算法如下:
void Reverse(SLink *&L)
{ SLink *p=L->next,*q;
L->next=L; //建立一个空循环单链表
while (p!=L)
{ q=p->next;
p->next=L->next; //将*p结点插入到前端
L->next=p;
p=q;
}
}
有两个整数集合采用有序单链表存储,设计尽可能高效的算法求两个集合的并集、交集和差集。并用相关数据进行测试。
#include
#include "SLink.h"
void Union(SLink *L1,SLink *L2,SLink *&L3) //求并集
{ SLink *p,*q,*s,*tc;
L3=(SLink *)malloc(sizeof(SLink));
tc=L3;
p=L1->next;
q=L2->next;
while (p!=NULL && q!=NULL)
{ if (p->data
{ s=(SLink *)malloc(sizeof(SLink));
s->data=p->data;
tc->next=s;
tc=s;
p=p->next;
}
else if (p->data>q->data)
{ s=(SLink *)malloc(sizeof(SLink));
s->data=q->data;
tc->next=s;
tc=s;
q=q->next;
}
else
{ s=(SLink *)malloc(sizeof(SLink));
s->data=p->data;
tc->next=s;
tc=s;
p=p->next;
q=q->next;
}
}
while (p!=NULL)
{ s=(SLink *)malloc(sizeof(SLink));
s->data=p->data;
tc->next=s;
tc=s;
p=p->next;
}
while (q!=NULL)
{ s=(SLink *)malloc(sizeof(SLink));
s->data=q->data;
tc->next=s;
tc=s;
q=q->next;
}
tc->next=NULL;
}
void InterSection(SLink *L1,SLink *L2,SLink *&L3) //求交集
{ SLink *p,*q,*s,*tc;
L3=(SLink *)malloc(sizeof(SLink));
tc=L3;
p=L1->next;
q=L2->next;
while (p!=NULL && q!=NULL)
{ if (p->data
p=p->next;
else if (p->data>q->data)
q=q->next;
else //p->data=q->data
{ s=(SLink *)malloc(sizeof(SLink));
s->data=p->data;
tc->next=s;
tc=s;
p=p->next;
q=q->next;
}
}
tc->next=NULL;
}
void Subs(SLink *L1,SLink *L2,SLink *&L3) //求差集
{ SLink *p,*q,*s,*tc;
L3=(SLink *)malloc(sizeof(SLink));
tc=L3;
p=L1->next;
q=L2->next;
while (p!=NULL && q!=NULL)
{ if (p->data
{ s=(SLink *)malloc(sizeof(SLink));
s->data=p->data;
tc->next=s;
tc=s;
p=p->next;
}
else if (p->data>q->data)
q=q->next;
else //p->data=q->data
{ p=p->next;
q=q->next;
}
}
while (p!=NULL)
{ s=(SLink *)malloc(sizeof(SLink));
s->data=p->data;
tc->next=s;
tc=s;
p=p->next;
}
tc->next=NULL;
}
void main()
{ SLink *A,*B,*C,*D,*E;
ElemType a[]={1,3,6,8,10,20};
CreateListR(A,a,6); //尾插法建表
printf("集合A:");DispList(A);
ElemType b[]={2,5,6,10,16,20,30};
CreateListR(B,b,7); //尾插法建表
printf("集合B:");DispList(B);
printf("求A、B并集C\n");
Union(A,B,C); //求A、B并集C
printf("集合C:");DispList(C);
printf("求A、B交集C\n");
InterSection(A,B,D); //求A、B并集D
printf("集合D:");DispList(D);
printf("求A、B差集E\n");
Subs(A,B,E); //求A、B差集E
printf("集合E:");DispList(E);
DestroyList(A);
DestroyList(B);
DestroyList(C);
DestroyList(D);
DestroyList(E);
}
(1)栈中元素的进出原则是( )。
A.先进先出 B.后进先出 C.栈空则进 D.栈满则出
答:B
(2)设一个栈的进栈序列是A、B、C、D(即元素A~D依次通过该栈),则借助该栈所得到的输出序列不可能是( )。
A.A,B,C,D B.D,C,B,A C.A,C,D,B D.D,A,B,C
答:D
(3)一个栈的进栈序列是a、b、c、d、e,则栈的不可能的输出序列是( )。
A.edcba B.decba C.dceab D.abcde
答:C
(4)已知一个栈的进栈序列是1,2,3,…,n,其输出序列的第一个元素是i(1≤i≤n)则第j(1≤j≤n)个出栈元素是( )。
A.i B.n-i C.j-i+1 D.不确定
答:D
(5)设顺序栈st的栈顶指针top的初始时为-1,栈空间大小为MaxSize,则判定st栈为栈空的条件为( )。
A.st.top==-1 B.st.top!=-1
C.st.top!=MaxSize D.st.top==MaxSize
答:A
(6)设顺序栈st的栈顶指针top的初始时为-1,栈空间大小为MaxSize,则判定st栈为栈满的条件是 。
A.st.top!=-1 B.st.top==-1
C.st.top!=MaxSize-1 D.st.top==MaxSize-1
答:D
(7)队列中元素的进出原则是( )。
A.先进先出 B.后进先出 C.栈空则进 D.栈满则出
答:A
(8)元素A、B、C、D顺序连续进入队列qu后,队头元素是( ① ),队尾元素是( ② )。
A.A B.B
C.C D.D
答:①A ②D。
(9)一个队列的入列序列为1234,则队列可能的输出序列是( )。
A.4321 B.1234
C.1432 D.3241
答:B
(10)循环队列qu(队头指针front指向队首元素的前一位置,队尾指针rear指向队尾元素的位置)的队满条件是( )。
A. (qu.rear+1)%MaxSize==(qu.front+1)%MaxSize
B. (qu.rear+1)%MaxSize==qu.front+1
C.(qu.rear+1)%MaxSize==qu.front
A.qu.rear==qu.front
答:C
(11)循环队列qu(队头指针front指向队首元素的前一位置,队尾指针rear指向队尾元素的位置)的队空条件是( )。
A. (qu.rear+1)%MaxSize==(qu.front+1)%MaxSize
B. (qu.rear+1)%MaxSize==qu.front+1
C.(qu.rear+1)%MaxSize==qu.front
D.qu.rear==qu.front
答:D
(12)设循环队列中数组的下标是0~N-1,其头尾指针分别为f和r(队头指针f指向队首元素的前一位置,队尾指针r指向队尾元素的位置),则其元素个数为( )。
A.r-f B.r-f-1
C.(r-f)%N+1 D.(r-f+N)%N
答:D
(13)设有4个数据元素a、b、c和d,对其分别进行栈操作或队操作。在进栈或进队操作时,按a、b、c、d次序每次进入一个元素。假设栈或队的初始状态都是空。现要进行的栈操作是进栈两次,出栈一次,再进栈两次,出栈一次;这时,第一次出栈得到的元素是( ① ),第二次出栈得到的元素是( ② );类似地,考虑对这4个数据元素进行的队操作是进队两次,出队一次,再进队两次,出队一次;这时,第一次出队得到的元素是( ③ ),第二次出队得到的元素是( ④ )。经操作后,最后在栈中或队中的元素还有( ⑤ )个。
①~④:A.a B.b C.c D.d
⑤: A.1 B.2 C.3 D.0
答:①B ②D ③A ④B ⑤B
(14)设栈S和队列Q的初始状态为空,元素e1~e6依次通过栈S,一个元素出后即进队列Q,若6个元素出队的序列是e2、e4、e3、e6、e5、e1,则栈S的容量至少应该是( )。
A.5 B.4 C.3 D.2
答:C
(1)栈是一种特殊的线性表,允许插入和删除运算的一端称为( ① )。不允许插入和删除运算的一端称为( ② )。
答:①栈顶 ②栈底
(2)一个栈的输入序列是12345,的输出序列为12345,其进栈出栈的操作为( )。
答:1进栈,1出栈,2进栈,2出栈,3进栈,3出栈,4进栈,4出栈,5进栈,5出栈。
(3)有5个元素,其进栈次序为A、B、C、D、E,在各种可能的出栈次序中,以元素C、D最先出栈(即C第一个且D第二个出栈)的次序有( )。
答:CDBAE、CDEBA、CDBEA。
(4)顺序栈用data[0..n-1]存储数据,栈顶指针为top,其初始值为0,则元素x进栈的操作是( )。
答:data[top]=x; top++;
(5)顺序栈用data[0..n-1]存储数据,栈顶指针为top,其初始值为0,则出栈元素x的操作是( )。
答:top--; x=data[top];
(6)( )是被限定为只能在表的一端进行插入运算,在表的另一端进行删除运算的线性表。
答:队列
(7)设有数组A[0..m]作为循环队列的存储空间,front为队头指针(它指向队首元素的前一位置),rear为队尾指针(它指向队尾元素的位置),则元素x执行入队的操作是( )。
答:rear=(rear+1)%(m+1); A[rear]=x;
(8)设有数组A[0..m]作为循环队列的存储空间,front为队头指针(它指向队首元素的前一位置),rear为队尾指针(它指向队尾元素的位置),则元素出队并保存到x中的操作是( )。
答:front=(front+1)%(m+1); x=A[rear];
(1)简要说明线性表、栈与队的异同点。
答:相同点:都属地线性结构,都可以用顺序存储或链表存储;栈和队列是两种特殊的线性表,即受限的线性表,只是对插入、删除运算加以限制。
不同点:①运算规则不同,线性表为随机存取,而栈是只允许在一端进行插入、删除运算,因而是后进先出表LIFO;队列是只允许在一端进行插入、另一端进行删除运算,因而是先进先出表FIFO。②用途不同,栈用于子程调用和保护现场等,队列用于多道作业处理、指令寄存及其他运算等等。
(2)顺序队的“假溢出”是怎样产生的?如何知道循环队列是空还是满?
答:一般的一维数组队列的尾指针已经到了数组的上界,不能再有进队操作,但其实数组中还有空位置,这就叫“假溢出”。
采用循环队列是解决假溢出的途径。另外,解决循环队列是空还是满的办法如下:
① 设置一个布尔变量以区别队满还是队空;
② 浪费一个元素的空间,用于区别队满还是队空。
③ 使用一个计数器记录队列中元素个数(即队列长度)。
通常采用法②,让队头指针front指向队首元素的前一位置,队尾指针rear指向队尾元素的位置,这样判断循环队列队空标志是:front=rear,队满标志是:(rear+1)%MaxSize=front。
(1)假设采用顺序栈存储结构,设计一个算法,利用栈的基本运算返回指定栈中栈底元素,要求仍保持栈中元素不变。这里只能使用栈st的基本运算来完成,不能直接用st.data[0]来得到栈底元素。
解:假定采用顺序栈结构。先退栈st中所有元素,利用一个临时栈tmpst存放从st栈中退栈的元素,最后的一个元素即为所求,然后将临时栈tmpst中的元素逐一出栈并进栈到st中,这样恢复st栈中原来的元素。对应算法如下:
int GetBottom(SqStack st,ElemType &x)
{ ElemType e;
SqStack tmpst; //定义临时栈
InitStack(tmpst); //初始化临时栈
if (StackEmpty(st)) //空栈返回0
return 0;
while (!StackEmpty(st)) //临时栈tmpst中包含st栈中逆转元素
{ Pop(st,x);
Push(tmpst,x);
}
while (!StackEmpty(tmpst)) //恢复st栈中原来的内容
{ Pop(tmpst,e);
Push(st,e);
}
return 1; //返回1表示成功
}
(2)设计一个算法,采用一个顺序栈逆向输出单链表L中所有元素。
解:本题并不需要改变单链表L的结构。设置一个顺序栈st,先遍历单链表并将所有元素进栈,然后栈不空循环并输出栈中所有元素。对应算法如下:
void ReverseDisp(SLink *L)
{ ElemType x;
struct node
{ ElemType data[MaxSize];
int top;
} st; //定义一个顺序栈
st.top=-1;
SLink *p=L->next;
while (p!=NULL) //遍历单链表,将所有元素进栈
{ st.top++;
st.data[st.top]=p->data;
p=p->next;
}
while (st.top!=-1) //栈不空循环,输出栈中所有元素
{ x=st.data[st.top];
st.top--;
printf("%d ",x);
}
printf("\n");
}
(3)设计一个循环队列,用front和rear分别作为队头和队尾指针,另外用一个标志tag标识队列可能空(0)或可能满(1),这样加上front==rear可以作为队空或队满的条件。要求设计队列的相关基本运算算法。
解:设计的队列的类型如下:
typedef struct
{ ElemType data[MaxSize];
int front,rear; //队头和队尾指针
int tag; //为0表示队空,为1时表示不空
} QueueType;
初始时tag=0,进行成功的插入操作后tag=1,进行成功的删除操作后tag=0;因为只有在插入操作后队列才有可能满,只有在删除操作后队列才有可能空,因此,这样的队列的基本要素如下:
初始时:tag=0,front=rear
队空条件:qu.front==qu.rear && qu.tag==0
队满条件:qu.front==qu.rear && qu.tag==1
对应的算法如下:
//-----初始化队列算法-----
void InitQueue1(QueueType &qu)
{ qu.front=qu.rear=0;
qu.tag=0; //为0表示队空可能为空
}
//-----判队空算法-----
int QueueEmpty1(QueueType qu)
{
return(qu.front==qu.rear && qu.tag==0);
}
//-----判队满算法-----
int QueueFull1(QueueType qu)
{
return(qu.tag==1 && qu.front==qu.rear);
}
//-----进队算法-----
int enQueue1(QueueType &qu,ElemType x)
{ if (QueueFull1(qu)==1) //队满
return 0;
qu.rear=(qu.rear+1)%MaxSize;
qu.data[qu.rear]=x;
qu.tag=1; //至少有一个元素,可能满
return 1;
}
//-----出队算法-----
int deQueue1(QueueType &qu,ElemType &x)//出队
{ if (QueueEmpty1(qu)==1) //队空
return 0;
qu.front=(qu.front+1)%MaxSize;
x=qu.data[qu.front];
qu.tag=0; //出队一个元素,可能空
return 1;
}
(4)假设用一个循环单链表表示队列,并且只设一个指针rear指向队尾结点,但不设头指针,设计出相应的队初始化、进队、出队和判队空的算法。
解:假设链队是不带头结点的循环单链表,其示意图如图3.1所示。队空条件:rear==NULL;进队操作:在*rear结点之后插入结点并让rear指向该结点;出队操作:删除*rear结点之后的一个结点。对应的算法如下:
图3.1 不带头结点的循环单链表表示队列
typedef struct node
{ ElemType data;
struct node *next;
} QNode; //链队结点类型
//-----初始化队列-----
void InitQueue(QNode *&rear)
{
rear=NULL;
}
//-----进队算法-----
void EnQueue(QNode *&rear,ElemType x)
{ QNode *s;
s=(QNode *)malloc(sizeof(QNode)); //创建新结点
s->data=x;
if (rear==NULL) //原链队为空
{ s->next=s; //构成循环链表
rear=s;
}
else
{ s->next=rear->next; //将*s结点插入到*rear结点之后
rear->next=s;
rear=s; //让rear指向这个新插入的结点
}
}
//-----出队算法-----
int DeQueue(QNode *&rear,ElemType &x)
{ QNode *q;
if (rear==NULL) //队空
return 0;
else if (rear->next==rear) //原队中只有一个结点
{ x=rear->data;
free(rear);
rear=NULL;
}
else //原队中有两个或以上的结点
{ q=rear->next;
x=q->data;
rear->next=q->next;
free(q);
}
return 1;
}
//-----判队空算法-----
int QueueEmpty(QNode *rear)
{
return(rear==NULL);
}
假设以I和O字符分别表示进栈和出栈操作,栈的初态和终栈均为空,进栈和出栈的操作序列可表示为仅由I和O组成的序列。如IOIIOIOO序列是合法的,而IOOIOIIO序列是不合法的。设计一个算法判定所给的操作序列是否合法。若合法返回1;否则返回0。(假设被判定的操作序列已存入一维数组中)。并用相关数据进行测试。
解:采用一个链栈来判断操作序列是否合法,其中str为存放操作序列的字符数组,n为该数组的元素个数(这里的ElemType类型设定为char)。对应的算法如下:
#include
#include
typedef struct node
{ char data;
struct node *next;
} LStack; //链栈结点类型
void InitStack(LStack *&ls) //初始化栈运算算法
{
ls=NULL;
}
void DestroyStack(LStack *&ls) //销毁栈运算算法
{ LStack *pre=ls,*p;
if (pre==NULL) return; //考虑空栈的情况
p=pre->next;
while (p!=NULL)
{ free(pre); //释放*pre结点
pre=p;p=p->next; //pre、p同步后移
}
free(pre); //释放尾结点
}
void Push(LStack *&ls,char x) //进栈运算算法
{ LStack *p;
p=(LStack *)malloc(sizeof(LStack));
p->data=x; //创建结点*p用于存放x
p->next=ls; //插入*p结点作为栈顶结点
ls=p;
}
int Pop(LStack *&ls,char &x) //出栈运算算法
{ LStack *p;
if (ls==NULL) //栈空,下溢出返回0
return 0;
else //栈不空时出栈元素x并返回1
{ p=ls; //p指向栈顶结点
x=p->data; //取栈顶元素x
ls=p->next; //删除结点*p
free(p); //释放*p结点
return 1;
}
}
int StackEmpty(LStack *ls) //判断栈空运算算法
{ if (ls==NULL) return 1;
else return 0;
}
int judge(char str[],int n) //判断str序列的合法性
{ int i,tag; char x;
LStack *ls;
InitStack(ls); //链栈ls初始化
for (i=0;i
{ if (str[i]=='I') //为I时进栈
Push(ls,str[i]);
else if (str[i]=='O') //为O时出栈
{ if (StackEmpty(ls))
{ DestroyStack(ls);
return 0; //栈空时返回0
}
else Pop(ls,x);
}
else
{ DestroyStack(ls);
return 0; //其他值无效退出
}
}
tag=StackEmpty(ls);
DestroyStack(ls);
return tag; //栈为空时返回1,否则返回0
}
void main()
{ char str1[]="IOIIOIOO";
char str2[]="IOOIOIIO";
char str3[]="IIIOIOIO";
char str4[]="IIIOOIOO";
printf("各序列判断结果如下:\n");
printf(" %s序列%s合法的\n",str1,judge(str1,8)?"是":"不是");
printf(" %s序列%s合法的\n",str2,judge(str2,8)?"是":"不是");
printf(" %s序列%s合法的\n",str3,judge(str3,8)?"是":"不是");
printf(" %s序列%s合法的\n",str4,judge(str4,8)?"是":"不是");
}
(1)串是一种特殊的线性表,其特殊性体现在( )。
A.可以顺序存储 B.数据元素是一个字符
C.可以链式存储 D.数据元素可以是多个字符
答:B
(2)以下( )是"abcd321ABCD"串的子串。
A. abcd B.321AB C."abcABC" D."21AB"
答:D
(3)两个串相等必有串长度相等且( )。
A.串的各位置字符任意 B.串中各对应位置字符均相等
C.两个串含有相同的字符 D.两个所含字符任意
答:B
(1)空串是指( ① ),空白串是指( ② )。
答:①不包含任何字符(长度为0)的串 ②由一个或多个空格(仅由空格符)组成的串
(2)对于带头结点的链串s,串为空的条件是( )。
答:s->next==NULL。
(3)对于一个含有n个字符的链串s,查找第i个元素的算法的时间复杂度为( )。
答:O(n)
(1)设s为一个长度为n的串,其中的字符各不相同,则s中的互异的非平凡子串(非空且不同于s本身)的个数是多少?
答:由串s的特性可知,1个字符的子串有n个,2个字符的子串有n-1个,3个字符的子串有n-2个,…,n-2个字符的子串有3个,n-1个字符的子串有2个。所以,非平凡子串的个数=n+(n-1)+(n-2)+…+2=。
(2)若s1和s2为串,给出使s1//s2=s2//s1成立的所有可能的条件(其中,“//”表示两个串连接运算符)。
答:所有可能的条件为:
(1)s1和s2为空串
(2)s1或s2其中之一为空串
(3)s1和s2为相同的串
(4)若两串长度不等,则长串由整数个短串组成。
(1)设计一个算法RepChar(s,x,y),将顺序串s中所有字符x替换成字符y。要求空间复杂度为O(1)。
解:因要求算法空间复杂度为O(1),所以只能对串s直接替换。从头开始遍历串s,一旦找到字符x便将其替换成y。对应算法如下:
void RepStr(SqString &s,char x,char y)
{ int i;
for (i=0;i
if (s.data[i]==x)
s.data[i]=y;
}
(2)设计一个算法,判断链串s中所有元素是否为递增排列的。
解:用p和q指向链串s的两个连续结点,p先指向开始结点,当q->data≥p->data时,p和q同步后移一个结点;否则返回0。当所有元素是递增排列时返回1。对应算法如下:
int increase(LinkString *s)
{ LinkString *p=s->next,*q;
if (p!=NULL)
{ while (p->next!=NULL)
{ q=p->next; //q指向*p的后续结点
if (q->data>=p->data)
p=q;
else //逆序时返回0
return 0;
}
}
return 1;
}
(3)假设以链式结构存储一个串s,设计一个算法求串s中最长等值子串。
解:假设串用带头结点的单链表存储串s。用max存放最大平台长度,扫描串s,计算一个平台的长度n,若n大于max,则置max为n。对应的算法如下:
int maxlength(LinkString *s)
{ int n,max=0;
LinkString *p=s->next,*q;
while (p!=NULL)
{ n=1;
q=p;p=p->next;
while (p!=NULL && p->data==q->data)
{ n++;
p=p->next;
}
if (n>max) max=n;
}
return max;
}
两个非空串s和t采用顺序存储结构存储,设计一个算法求这两个串最大公共子串,并用相关数据进行测试。
解:采用Index算法思路设计由顺序串s、t产生最大公共子串str。对应的程序如下:
#include
#include "SqString.h" //包含顺序串的基本运算函数
SqString maxcomstr(SqString s,SqString t)
{ SqString str;
int midx=0,mlen=0,tlen,i=0,j,k; //用(midx,mlen)保存最大公共子串
while (i
{ j=0; //用j扫描串t
while (j
{ if (s.data[i]==t.data[j]) //找一子串,在s中下标为i,长度为tlen
{ tlen=1;
for (k=1;i+k
&& s.data[i+k]==t.data[j+k];k++)
tlen++;
if (tlen>mlen) //将较大长度者赋给midx与mlen
{ midx=i;
mlen=tlen;
}
j+=tlen; //继续扫描t中第j+tlen字符之后的字符
}
else j++;
}
i++; //继续扫描s中第i字符之后的字符
}
for (i=0;i
str.data[i]=s.data[midx+i];
str.length=mlen;
return str; //返回最大公共子串
}
void main()
{ SqString s,t,str;
Assign(s,"aababcabcdabcde");
Assign(t,"aabcd");
printf("s:");DispStr(s);
printf("t:");DispStr(t);
printf("求s、t的最大公共子串str\n");
str=maxcomstr(s,t);
printf("str:");DispStr(str);
}
(1)假设有60行70列的二维数组a[1..60,1..70](数组下标从1开始)以列序为主序顺序存储,其基地址为10000,每个元素占2个存储单元,那么第32行第58列的元素a[32,58]的存储地址为( )。
A.16902 B.16904 C.14454 D.以上都不对
答:A
(2)对特殊矩阵采用压缩存储的目的主要是为了( )。
A.表达变得简单 B.对矩阵元素的存取变得简单
C.去掉矩阵中的多余元素 D.减少不必要的存储空间
答:D
(3)一个n×n的对称矩阵,如果采用压缩存储以行或列为主序放入内存,则压缩存储的容量是( )。
A. n2 B. n2/2 C. n(n+1)/2 D. (n+1)2/2
答:C
(4)设矩阵a是一个对称矩阵,为了节省存储,将其下三角部分按行序存放在一维数组b[1..n(n-1)/2]中(数组下标均从1开始),对下三角部分中任一元素ai,j(i≤j)在一维数组b中下标k的值是( )。
A.i(i-1)/2+j-1 B.i(i-1)/2+j C.i(i+1)/2+j-1 D.i(i+1)/2+j
答:B
(5)有一个二维数组A,行下标的范围是0~8,列下标的范围是1~5,每个数组元素用相邻的4个字节存储。存储器按字节编址。假设存储数组元素A[0,1]的第一个字节的地址是0。存储数组A的最后一个元素的第一个字节的地址是( ① )。若按行存储,则A[3,5]和A[5,3]的第一个字节的地址分别是( ② )和( ③ )。若按列存储,则A[7,1]和A[2,4]的第一个字节的地址分别是( ④ )和( ⑤ )。
A.28 B.44 C.76 D.92 E.108
F.116 G.132 H.176 I.184 J.188
答:①H ②C ③E ④A ⑤F
(6)有一个二维数组A,行下标的范围是1~6,列下标的范围是0~7,每个数组元素用相邻的6个字节存储,存储器按字节编址。那么,这个数组的体积是( ① )个字节。假设存储数组元素A[1,0]的第一个字节的地址是0,则存储数组A的最后一个元素的第一个字节的地址是( ② )。若按行存储,则A[2,4]的第一个字节的地址是( ③ )。若按列存储,则A[5,7]的第一个字节的地址是( ④ )。
A.12 B.66 C.72 D.96 E.114 F.120
G.156 H.234 I.276 J.282 K.283 L.288
答:①L ②J ③C ④I
(7)稀疏矩阵一般的压缩存储方法有两种,即( )。
A.二维数组和三维数组 B.三元组和散列
C.三元组和十字链表 D.散列和十字链表
答:C
(1)三维数组A[c1..d1,c2..d2,c3..d3](c1≤d1,c2≤d2,c3≤d3)共含有( )个元素。
答:(d1-c1+1)×(d2-c2+1)×(d3-c3+1)。
(2)已知二维数组A[m][n]采用行序为主方式存储,每个元素占k个存储单元,并且第一个元素的存储地址是LOC(A[0][0]),则A[i][j]的地址是( )。
答:LOC(A[0][0])+(n×i+j)×k。
(3)二维数组A[10][20]采用列序为主方式存储,每个元素占一个存储单元,并且A[0][0]的存储地址是200,则A[6][12]的地址是( )。
答:326
(4)二维数组A[10..20][5..10]采用行序为主方式存储,每个元素占4个存储单元,并且A[10][5]的存储地址是1000,则A[18][9]的地址是( )。
答:1208
(5)有一个10阶对称矩阵A,采用压缩存储方式(以行序为主存储下三角部分,且A[0][0]存放在B[1]中),则A[8][5]在B中的地址是( )。
答:42
(6)设n阶下三角矩阵A[1..n][1..n]已压缩到一维数组S[1..n(n+1)/2]中,若按行序为主存储,则A[i][j]对应的S中的存储位置是( )。
答:i(i-1)/2+j。
(7)稀疏矩阵的三元组表示中,每个结点对应于稀疏矩阵的一个非零元素,它包含有三个数据项,分别表示该元素的( )。
答:行下标、列下标和元素值
(1)假定数组A[0..n-1]的n个元素中有多个零元素,设计一个算法将A中所有的非零元素依次移到A的前端。
解:从前向后找为零的元素A[i],从后向前找非零的元素A[j],将A[i]和A[j]进行交换。对应的算法如下:
void move(ElemType A[],int n)
{ int i=0,j=n-1;
ElemType tmp;
while (i
{ while (A[i]!=0) i++;
while (A[j]==0) j--;
if (i
{ tmp=A[i];
A[i]=A[j];
A[j]=tmp;
}
}
}
(2)已知一个n×n矩阵B按行优先存于一个一维数组A[0..n(n-1)]中,试给出一个就地算法将原矩阵转置后仍存于数组A中。
解:矩阵转置是将矩阵中第i行第j列的元素与第j行第i列的元素互换位置。因此先应确定矩阵与一维数组的映射关系:bi,j在一维数组A中的下标为i~n+j,bj,i在一维数组A中的下标为j×n+i。对应的算法如下:
void trans(ElemType A[], int n)
{ int i, j;
ElemType tmp;
for (i=0;i
for (j=0;j
{ tmp=A[i*n+j];
A[i*n+j]=A[j*n+i];
A[j*n+i]=tmp;
}
}
(3)如果矩阵A中存在这样的一个元素A[i][j]满足条件:A[i][j]是第i行中值最小的元素,且又是第j列中值最大的元素,则称之为该矩阵的一个马鞍点。设计一个算法求出m×n的矩阵A的所有马鞍点。
解:先求出每行的最小值元素,放入min[m]之中,再求出每列的最大值元素,放入max[n]之中,若某元素既在min[i]中,又在max[j]中,则该元素A[i][j]便是马鞍点,找出所有这样的元素,即找到了所有马鞍点。实现程序如下:
#include
#define m 3
#define n 4
void minmax(int A[m][n])
{ int i, j, have=0;
int min[m], max[n];
for (i=0;i
{ min[i]=A[i][0];
for (j=1;j
if (A[i][j]
min[i]=A[i][j];
}
for (j=0;j
{ max[j]=A[0][j];
for (i=1;i
if (A[i][j]>max[j])
max[j]=A[i][j];
}
for (i=0;i
for (j=0;j
if (min[i]==max[j])
{ printf("(%d, %d):%d\n", i, j, A[i][j]);//显示马鞍点
have=1;
}
if (!have)
printf("没有鞍点\n");
}
void main()
{ int a[m][n];
int i, j;
for (i=0;i
for (j=0;j
scanf("%d", &a[i][j]);
minmax(a); //调用minmax()找马鞍点
}
(4)设有二维数组A[m][n],其元素为整数,每行每列都按从小到大有序,试给出一个算法求数组中值为x的元素的行号i和列号j。设值x在A中存在,要求比较次数不多于m+n次。
解:由于算法要求比较次数不多于m+n次,因此不能按行扫描数组的每一元素,否则比较次数在最坏情况下可达m×n次。根据该数组的特点可从矩阵右上角按副对角线方向向左下角查找,算法如下:
int find(int a[M][N],int x,int m,int n)
{ int i=0,j=n-1,flag=0;
while (i
if (a[i][j]!=x)
if (a[i][j]>x) j--; //修改列号
else i++; //修改行号
else //a[i][j]==x
{ flag=1;
break;
}
return flag;
}
采用一维动态数组模拟二维数组的操作,即将一个二维数组的元素存放在一个一维数组中,一维数组的空间根据二维数组的大小动态分配。设计一个算法实现两个二维数组的相加运算。并用相关数据进行测试。
解:对应的程序如下:
#include
#include
typedef int ElemType;
typedef struct
{ ElemType *p; //存放二维数组元素
int m,n; //存放二维数组的行数和列数
} Mat2;
void InitMat(Mat2 &M,int x,int y) //初始化二维数组M
{ M.m=x; M.n=y;
M.p=(ElemType *)malloc(x*y*sizeof(ElemType));
}
int Setij(Mat2 &M,int i,int j,ElemType x) //置二维数组(i,j)位置值为x
{ int k;
if (i>=0 && i
{ k=i*M.n+j;
M.p[k]=x;
return 1; //成功赋值返回1
}
else return 0; //参数错误返回0
}
int Getij(Mat2 M,int i,int j,ElemType &x) //取二维数组(i,j)位置值并赋给x
{ int k;
if (i>=0 && i
{ k=i*M.n+j;
x=M.p[k];
return 1; //成功取值返回1
}
else return 0; //参数错误返回0
}
void DispMat(Mat2 M) //输出二维数组
{ int i,j,k;
for (i=0;i
{ for (j=0;j
{ k=i*M.n+j;
printf("%3d",M.p[k]);
}
printf("\n");
}
}
int AddMat(Mat2 A,Mat2 B,Mat2 &C) //两个二维数组相加运算
{ int i,j;
ElemType x,y;
if (A.m!=B.m || A.n!=B.n)
return 0; //两数组行列数不同返回0
for (i=0;i
for (j=0;j
{
Getij(A,i,j,x);
Getij(B,i,j,y);
Setij(C,i,j,x+y);
}
return 1;
}
void DestroyMat(Mat2 M) //销毁二维数组M
{
free(M.p);
}
void main()
{ Mat2 A,B,C;
InitMat(A,2,3);
Setij(A,0,0,1); Setij(A,0,1,1); Setij(A,0,2,1);
Setij(A,1,0,1); Setij(A,1,1,1); Setij(A,1,2,1);
printf("A:\n"); DispMat(A);
InitMat(B,2,3);
Setij(B,0,0,2); Setij(B,0,1,2); Setij(B,0,2,2);
Setij(B,1,0,2); Setij(B,1,1,2); Setij(B,1,2,2);
printf("B:\n"); DispMat(B);
InitMat(C,2,3);
printf("C=A+B\n");
AddMat(A,B,C);
printf("C:\n"); DispMat(C);
DestroyMat(A);
DestroyMat(B);
DestroyMat(C);
}
(1)树最适合用来表示( )。
A.有序数据元素 B.无序数据元素
C.元素之间具有层次关系的数据 D.元素之间无联系的数据
答:C
(2)树是结点的有限集合,它( ① )根结点,记为T。其余的结点分成为m(m≥0)个( ② )的集合T1、T2、…、Tm,每个集合又都是树,此时结点T称为Ti的双亲结点,Ti称为T的子树(1≤i≤m)。一个结点的子树个数为该结点的( ③ )。
①:A.有0个或1个 B.有0个或多个 C.有且只有1个 D.有1个或1个以上
②:A.互不相交 B.允许相交 C.允许叶结点相交 D.允许树枝结点相交
③:A.权 B.维数 C.次数(或度) D.序
答:①A ②A ③C
(3)二叉树( ① )。在完全的二叉树中,若一个结点没有( ② ),则它必定是叶子结点。每棵树都能唯一地转换成与它对应的二叉树,由一棵树转换成的二叉树中,一个结点N的左孩子是N在原树里对应结点的( ③ ),而N的右孩子是它在原树里对应结点的( ④ )。
①: A.是特殊的树 B.不是树的特殊形式
C.是两棵树的总称 D.有是只有二个根结点的树形结构
②: A.左子结点 B.右子结点
C.左子结点或者没有右子结点 D.兄弟
③~④:A.左孩子结点 B.最右子结点 C.最邻近的右兄弟
D.最邻近的左兄弟 E.最左的兄弟 F.最右的兄弟
答:①B ②A ③A ④C
(4)把一棵树转换为二叉树后,这棵二叉树的形态是( )。
A.唯一的 B.有多种
C.有多种,但根结点都没有左孩子 D.有多种,但根结点都没有右孩子
答:A
(5)假定一棵度为3的树中结点数为50,则其最小高度为( )。
A. 3 B. 4 C. 5 D. 6
答:C
(6)若一棵度为7的树有7个度为2的结点,有6个度为3的结点,有5个度为4的结点,有4个度为5的结点,有3个度为6的结点,有2个度为7的结点,该树一共有( )个叶子结点。
A. 35 B. 28 C. 77 D. 78
答:D
(7)下列叙述中,正确的是( )。
A.二叉树就是度为2的树 B.二叉树中不存在度大于2的结点
C.二叉树是有序树 D. 二叉树中每个结点的度均为2
答:B
(8)深度为5的二叉树至多有( )个结点。
A. 16 B. 32 C.31 D.10
答:C
(9)对一个满二叉树,有m个叶子结点,n个结点,深度为h,则( )。
A. n=h+m B. h+m=2n C. m=h-1 D. n=2h-1
答:D
(10)完全二叉树中,编号为i的结点的层次是( )。
A. i B. log2i C. log2(i+1) D. C. log2i+1
答:D
(11)一棵完全二叉树上有1001个结点,其中叶子结点的个数是( )。
A. 250 B. 501 C. 254 D. 505
答:B
(12)一棵有124个叶结点的完全二叉树,最多有( )个结点。
A. 247 B. 248 C. 249 D. 250
答:B
(13)若唯一确定一棵二叉树,只需要知道该二叉树的( )。
A. 先序序列 B. 中序序列
C. 中序和后序序列 D. 先序和后序序列
答:C
(14)一棵二叉树的先序遍历序列和其后序遍历序列正好相反,则该二叉树一定是( )。
A.空树或只有一个结点 B.哈夫曼树
C.完全二叉树 D.高度等于其结点数
答:D
(15)一棵二叉树的后序遍历序列为D、A、B、E、C,中序遍历序列为D、E、B、A、C,则先序遍历序列为( )。
A.A、C、B、E、D B.D、E、C、B、A
C.D、E、A、B、C D.C、E、D、B、A
答:D
(16)在线索化二叉树中,p所指结点没有左子树的充要条件是( )。
A.p->lchild == NULL B.p->ltag==1
C.p->ltag==1且p->lchild==NULL D.以上都不对
答:B
(17)根据使用频率为5个字符设计的哈夫曼编码不可能是( )。
A. 111,110,10,01,00 B. 000,001,010,011,1
C. 100,11,10,1,0 D. 001,000,01,11,10
答:C
(1)由3个结点所构成的二叉树有( )种形态。
答:5
(2)一棵深度为6的满二叉树有( ① )个分支结点和( ② )个叶子结点。
答:①31 ②32
(3)设一棵完全二叉树有700个结点,则共有( )个叶子结点。
答:350
(4)设一棵完全二叉树具有1000个结点,则此完全二叉树有( ① )个叶子结点,有( ② )个度为2的结点,有( ③ )个单分支结点。
答:①500 ②499 ③1
(5)一棵二叉树的第i(i≥1)层最多有( )个结点。
答:2i-1
(6)深度为h的完全二叉树至少有( ① )个结点,至多有( ② )个结点,若按自上而下,从左到右次序给结点编号(从1开始),则编号最小的叶子结点的编号是( ③ )。
答:① 2h-1 ② 2h-1 ③ 2h-1。
(7)对含有n个结点的二叉树进行中序递归遍历,该算法平均空间复杂度为( )。
答:O(n)
(8)用5个权值{3,2,4,5,1}构造的哈夫曼(Huffman)树的带权路径长度是( )。
答:33
(1)一棵度为2的树与一棵二叉树有何区别?
答:度为2的树从形式上看与二叉树很相似,但它的子树是无序的,而二叉树是有序的,即在一般树中若某结点只有一个孩子,就无需区分其左右次序,而在二叉树中即使是一个孩子也有左右之分。另外,一棵度为2的树至少有3个结点,而一棵二叉树的结点个数可以为0。
(2)试求含有n0个叶子结点的完全二叉树的总结点数。
答:由二叉树的性质可知,n2=n0-1,在完全二叉树中,度为1的结点数n1至多为1,所以具有n0个叶子结点的完全二叉树结点数是n0+(n0-1)+1=2n0或2n0-1。
(3)某二叉树的结点数据采用顺序存储结构如下:
① 画出该二叉树;
② 写出结点值为D的双亲结点及左、右子树。
③ 将此二叉树还原为森林。
答:① 该二叉树如图8.21所示。
图8.21 一棵二叉树 图8.2 二叉树还原成的森林
② 结点值为D的结点的双亲结点为结点值为A的结点,其左子树为以C为根结点的子树,其右子树为空。
③ 由此二叉树还原成的森林如图8.2所示。
(4)证明完全二叉树的每棵子树也都是完全二叉树。
证明:让T是一棵完全二叉树,S是它的一棵子树。由子树定义可知,S是由T中某个结点及其所有子孙构成的。由于T是完全二叉树,T的所有叶子结点都在两个相邻的层次上,因此,S的所有叶子结点都在两个相邻的层次上。类似的,如果在S中存在不位于底层上结点层,那么,该层也不位于T的底层上,它必须在T中所有底层叶子结点的右边,因此它也必须在S中所有底层叶子结点的右边。从而得到S是完全二叉树。
(5)一棵二叉树的先序、中序和后序序列分别如下,其中有一部分未显示出来。试求出空格处的内容,并画出该二叉树。
先序序列: B F ICEH G
中序序列:D KFIA EJC
后序序列: K FBHJ G A
答:由这些显示部分推出二叉树如图8.3所示。则先序序列为ABDFKICEHJG;中序序列为DBKFIAHEJCG;后序序列为DKIFBHJEGCA。
图8.3 一棵二叉树
(6)如果一棵哈夫曼树T有n0个叶子结点,那么,树T有多少个结点?要求给出求解过程。
答:一棵哈夫曼树中只有度为2和0的结点,没有度为1的结点,由非空二叉树的性质1可知,n0=n2+1,即n2=n0-1,则总结点数n=n0+n2=2n0-1。
(1)已知一棵二叉树按顺序方式存储在数组A[1..n]中。设计一个算法求出离下标分别为i和j(0<i、j≤n)的两个结点的最近的公共祖先结点的值。
解:由二叉树顺序存储结构特点,可得到以下求离i和j的两个结点最近的公共祖先结点的算法:
ElemType Ancestor(ElemType A[],int i,int j)
{ int p=i,q=j;
while (p!=q)
if (p>q) p=p/2;
else q=q/2;
return A[p];
}
(2)已知一棵二叉树采用顺序方式存储在数组A[1..n]中。设计一个先序遍历的递归算法。
解:先序遍历树中结点的递归算法如下:
void PreOrder1(ElemType A[],int i,int n)
{ if (i
if (A[i]!='#') //不为空结点时
{ printf("%c ",A[i]); //访问根结点
PreOrder1(A,2*i,n); //遍历左子树
PreOrder1(A,2*i+1,n); //遍历右子树
}
}
(3)假设二叉树采用二叉链存储结构。设计一个算法求一棵非空二叉树中的最大结点值。
解:求一个二叉树中的最大结点值的递归模型如下:
f(bt)=bt->data 只有一个结点时
f(bt)=Max{f(bt->lchild),f(bt->rchild),bt->data} 其他情况
对应的算法如下:
ElemType MaxNode(BTNode *bt)
{ ElemType max,max1,max2;
if (bt->lchild==NULL && bt->rchild==NUL)
return bt->data;
else
{ max1=MaxNode(bt->lchild); //求左子树的最大结点值
max2=MaxNode(bt->rchild); //求右子树的最大结点值
max=bt->data;
if (max1>max) max=max1;
if (max2>max) max=max2; //求最大值
return(max); //返回最大值
}
}
(4)假设含有n个结点的二叉树采用二叉链存储结构。设计一个算法输出中序遍历序列中的第k(1≤i≤n)个结点值。
解:对应的算法如下:
int i=0; //i为全局变量
void Trav(BTNode *bt,int k)
{ if (bt!=NULL)
{ Trav(bt->lchild,k); //遍历左子树
i++;
if (i==k)
{ printf("%c",bt->data);
return;
}
Trav(bt->rchild,k); //遍历右子树
}
}
(5)假设二叉树采用二叉链存储结构,设计一个算法Level()求二叉树中结点值为x的结点的层数。
解:对应的递归算法如下:
int Level(BTNode *bt,ElemType x,int h) //调用h对应的实参置初值1
{ int l;
if (bt==NULL)
return 0;
else if (bt->data==x)
return h;
else
{ l=Level(bt->lchild,x,h+1); //在左子树中查找
if (l!=0)
return l;
else //在左子树中未找到,再在右子树中查找
return(Level(bt->rchild,x,h+1));
}
}
假设一棵二叉树采用二叉链存储结构,其中所有结点值均不相同。设计一个算法求从根结点到值为x的结点的路径。并用相关数据进行测试。
解:采用递归和层次遍历两种求解方法,对应的程序如下:
#include
#include "BTree.h"
void Path1(BTNode *bt,ElemType x,ElemType path[],int pathlen)
{ int i;
if (bt!=NULL)
{ if (bt->data==x) //找到值为x的结点
{ printf("从根结点到%c结点的路径: ",bt->data);
for (i=0;i
printf("%c→",path[i]);
printf("%c\n",bt->data);
return;
}
else
{ path[pathlen]=bt->data; //将当前结点放入路径中
pathlen++; //path中元素个数增1
Path1(bt->lchild,x,path,pathlen); //递归遍历左子树
Path1(bt->rchild,x,path,pathlen); //递归遍历右子树
}
}
}
void Path2(BTNode *bt,ElemType x)
{ BTNode *p;
ElemType path[MaxSize];
int i,d;
struct
{ BTNode *s; //存放结点指针
int parent; //存放其双亲结点在qu中的下标
} qu[MaxSize]; //qu存放队中元素
int front=-1,rear=-1; //队头队尾指针
rear++;
qu[rear].s=bt; //根结点进队
qu[rear].parent=-1; //根结点没有双亲,其parent置为-1
while (front!=rear) //队不空循环
{ front++;
p=qu[front].s; //出队一个结点*p,它在qu中的下标为front
if (p->data==x) //找到值为x的结点
{ printf("从根结点到%c结点的路径: ",p->data);
d=0; path[d]=p->data;
i=qu[front].parent;
while (i!=-1)
{ d++; path[d]=qu[i].s->data;
i=qu[i].parent;
}
printf("%c",path[d]);
for (i=d-1;i>=0;i--)
printf("→%c",path[i]);
printf("\n");
}
if (p->lchild!=NULL) //*p有左孩子,将左孩子进队
{ rear++;
qu[rear].s=p->lchild;
qu[rear].parent=front; //左孩子的双亲为qu[front]结点
}
if (p->rchild!=NULL) //*p有右孩子,将右孩子进队
{ rear++;
qu[rear].s=p->rchild;
qu[rear].parent=front; //右孩子的双亲为qu[front]结点
}
}
}
void main()
{ BTNode *bt;
ElemType path[MaxSize],x='I';
CreateBTree(bt,"A(B(D,E(G,H)),C(,F(I)))"); //建立图6.14的二叉链
printf("bt括号表示法:"); DispBTree(bt); printf("\n");
printf("解法1:\n");
Path1(bt,x,path,0);
printf("解法2:\n");
Path2(bt,x);
}
(1)在一个图中,所有顶点的度数之和等于图的边数的( )倍。
A.1/2 B.1 C.2 D.4
答:C
(2)在一个有向图中,所有顶点的入度之和等于所有顶点的出度之和的( )倍。
A.1/2 B.1 C.2 D.4
答:B
(3)有8个顶点的无向图最多有( )条边。
A.14 B.28 C.56 D.112
答:B
(4)有8个顶点的无向连通图最少有( )条边。
A.5 B.6 C.7 D.8
答:C
(5)有8个顶点的有向完全图有( )条边。
A.14 B.28 C.56 D.112
答:C
(6)n个顶点的强连通图中至少有( )条边。
A. n B. n-1 C. 2n D. n(n-1)
答:A
(7)若一个图的邻接矩阵是对称矩阵,则该图一定是( )。
A.有向图 B.无向图 C.连通图 D.无向图或有向图
答:D
(8)设无向图G=(V, E)和G'=(V', E' ),如果G'是G的生成树,则下面的说法中错误的是( )。
A.G'为G的子图 B.G'为G的连通分量
C.G'为G的极小连通子图且V=V' D.G'是G的一个无环子图
答:B
(9)用邻接表表示图进行广度优先遍历时,通常是采用( )来实现算法的。
A.栈 B.队列 C.树 D.图
答:B
(10)用邻接表表示图进行深度优先遍历时,通常是采用( )来实现算法的。
A.栈 B.队列 C.树 D.图
答:A
(11)图的广度优先遍历算法中用到辅助队列,每个顶点最多进队( )次。
A. 1 B. 2 C. 3 D. 不确定
答:A
(12)一个无向图中包含k个连通分量,若按深度优先搜索方法访问所有结点,则必须调用( )次深度优先遍历算法。
A. k B. 1 C. k-1 D. k+1
答:A
(13)已知一个图的邻接表如图7.1所示,根据算法,则从顶点0出发按深度优先遍历的结点序列是( )。
图7.1 一个邻接表
A.0 1 3 2 B.0 2 3 1 C.0 3 2 1 D.0 1 2 3
答:D
(14)已知一个图的邻接表如图7.1所示,根据算法,则从顶点0出发按广度优先遍历的结点序列是( )。
A.0 1 3 2 B.0 2 3 1 C.0 3 2 1 D.0 1 2 3
答:D
(15)深度优先遍历类似于二叉树的( )。
A.先序遍历 B.中序遍历 C.后序遍历 D.层次遍历
答:A
(16)广度优先遍历类似于二叉树的( )。
A.先序遍历 B.中序遍历 C.后序遍历 D.层次遍历
答:D
(17)最小生成树指的是( )。
A.由连通网所得到的边数最少的生成树
B.由连通网所得到的顶点数相对较少的生成树
C.连通网中所有生成树中权值之和为最小的生成树
D.连通网的极小连通子图
答:C
(18)任何一个无向连通图的最小生成树( )。
A.只有一棵 B.一棵或多棵 C.一定有多棵 D.可能不存在
答:B
(19)用Prim和Kruskal两种算法构造图的最小生成树,所得到的最小生成树( )。
A.是相同的 B.是不同的
C.可能相同,也可能不同 D.以上都不对
答:C
(20)对于有n个顶点e条边的有向图,求最短路径的Dijkstra算法的时间复杂度为( )。
A.O(n) B.O(n+e) C.O(n2) D.O(ne)
答:C
(21)对于有n个顶点e条边的有向图,求最短路径的Floyd算法的时间复杂度为( )。
A.O(n) B.O(ne) C.O(n2) D.O(n3)
答:D
(22)若一个有向图中的顶点不能排成一个拓扑序列,则可断定该有向图 。
A.是个有根有向图 B.是个强连通图
C.含有多个入度为0的顶点 D.含有顶点数目大于1的强连通分量
答:D
(23)关键路径是事件结点网络中( )。
A. 从源点到汇点的最长路径 B. 从源点到汇点的最短路径
C. 最长的回路 D. 最短的回路
答:A
(1)n个顶点的连通图至少( )条边。
答:n-1
(2)在图的邻接矩阵和邻接表表示中,( ① )表示一定是唯一的,而( ② )表示可能不唯一。
答:①邻接矩阵 ②邻接表
(3)具有10个顶点的无向图中,边数最多为( )。
答:45
(4)在有n个顶点的有向图中,每个顶点的度最大可达( )。
答:2(n-1)。
(5)n个顶点e条边的图,若采用邻接矩阵存储,则空间复杂度为( )。
答:O(n2)
(6)n个顶点e条边的有向图,若采用邻接表存储,则空间复杂度为( )。
答:O(n+e)
(7)n个顶点e条边的有向图采用邻接矩阵存储,深度优先遍历算法的时间复杂度为( ① );若采用邻接表存储时,该算法的时间复杂度为( ② )。
答:①O(n2) ②O(n+e)
(8)n个顶点e条边的有向图采用邻接矩阵存储,广度优先遍历算法的时间复杂度为( ① );若采用邻接表存储时,该算法的时间复杂度为( ② )。
答:①O(n2) ②O(n+e)
(9)一个连通图的生成树是该图的一个( )。
答:极小连通子图
(10)用普里姆(Prim)算法求具有n个顶点e条边的图的最小生成树的时间复杂度为( ① );用克鲁斯卡尔(Kruskal)算法的时间复杂度是( ② )。若要求一个稀疏图G的最小生成树,最好用( ③ )算法来求解;若要求一个稠密图G的最小生成树,最好用( ④ )算法来求解。
答:①O(n2) ②O(elog2e) ③Kruskal ④Prim
(1)从占用的存储空间来看,对于稠密图和稀疏图,采用邻接矩阵和邻接表哪个更好些?
答:设图的顶点个数和边数分别为n和e。邻接矩阵的存储空间大小为O(n2),与e无关,因此适合于稠密图的存储。邻接表的存储空间大小为O(n+e)(有向图)或O(n+2e)(无向图),与e有关,因此适合于稀疏图的存储。
(2)对于图7.2所示的带权有向图,求从顶点0到其他各顶点的最短路径。
图7.2 一个有向带权图
答:求解过程如下:
S dist path
0 1 2 3 4 5 6 0 1 2 3 4 5 6
{0} 0 ∞ ∞ ∞ 11 ∞ 7 0 -1 -1 -1 0 -1 0
{0,6} 0 22 ∞ 13 11 ∞ 7 0 6 -1 6 0 -1 0
{0,6,4} 0 22 ∞ 13 11 ∞ 7 0 6 -1 6 0 -1 0
{0,6,4,3} 0 22 ∞ 13 11 16 7 0 6 -1 6 0 3 0
{0,6,4,3,5} 0 22 25 13 11 16 7 0 6 5 6 0 3 0
{0,6,4,3,5,1} 0 22 25 13 11 16 7 0 6 5 6 0 3 0
{0,6,4,3,5,1,2} 0 22 25 13 11 16 7 0 6 5 6 0 3 0
求解结果如下:
从0到1的最短路径长度为:22 路径为:0,6,1
从0到2的最短路径长度为:25 路径为:0,6,3,5,2
从0到3的最短路径长度为:13 路径为:0,6,3
从0到4的最短路径长度为:11 路径为:0,4
从0到5的最短路径长度为:16 路径为:0,6,3,5
从0到6的最短路径长度为:7 路径为:0,6
(3)某带权有向图及其邻接表表示如图7.3所示,给出深度优先遍历序列,将该图作为AOE网,给出C的最早开始时间及活动FC的最迟开始时间。
图7.3 有向图及其单邻接表
答:深度优先遍历序列为:A,B,C,E,G,D,F。
求最早开始时间和最迟开始时间的过程如下:
ve(A)=0,ve(D)=3,ve(F)=ve(D)+3=6,ve(B)=1,
ve(C)=MAX{ve(B)=3,ve(A)+2,ve(F)+3}=7,
ve(E)=MAX{ve(B)+1,ve(C)+2}=9,
ve(G)=MAX{ve(E)+1,ve(F)+5}=11,
vl(G)=11,vl(E)=vl(G)-1=10,
vl(C)=vl(E)-2=8,vl(B)=MIN{vl(E)-1,vl(C)-3}=5,
vl(F)=MIN{vl(G)-5,vl(C)-1}=6,vl(D)=vl(F)-3=3,
vl(A)=MIN{vl(B)-1,vl(C)-2,vl(D)-3}= 0;
则:l(FC)=vl(C)-1=7
所以,事件C的最早开始时间为7,活动FC的最迟开始时间为7。
(4)对于如图7.4所示的有向图G,给出它的4个不同的拓扑有序序列。
答:该图的4个不同的拓扑有序序列是:12345678,12354678,12347856,12347568(实际上不止4个)。
图7.4 一个有向图
(1)假设图G采用邻接矩阵存储,给出图的深度优先遍历算法,并分析算法的时间复杂度。
解:基于图的邻接矩阵表示的深度优先遍历算法如下:
int visited[MAXVEX];
void DFS1(MatGraph g,int v)
{ int i;
visited[v]=1;
printf("%3d",v);
for (i=0;i
if (g.edges[v][i]!=0 && g.edges[v][i]!=INF && visited[i]==0)
DFS1(g,i); //对于未访问过的顶点i进行递归
}
(2)假设以邻接表作为图的存储结构,设计一个算法求出无向图G的连通分量个数。
解:采用DFS遍历的算法如下:
int getnum(AdjGraph *G) //求图G的连通分量
{ int i, n=0, visited[MAXVEX];
for (i=0;i<G->n;i++)
visited[i]=0;
for (i=0;i<G->n;i++)
if (visited[i]==0)
{ n++;
DFS(G,i); //对图g从顶点i开始进行深度优先遍历
}
return n;
}
(3)假设以邻接表作为图的存储结构,分别写出基于DFS和BFS遍历的算法来判别图G中顶点i和顶点j(i≠j)之间是否有路径。
解:先置全局变量visited[]为0,然后从顶点i开始进行某种遍历,遍历之后,若visited[j]=0,说明顶点i与顶点j之间没有路径;否则说明它们之间存在路径。
基于DFS遍历的算法如下:
int visited[MAXVEX]; //全局变量
int DFSTrave(AdjGraph *G,int i,int j)
{ int k;
for (k=0;k
visited[k]=0;
DFS(G,i); //从顶点i开始进行深度优先遍历
if (visited[j]==0) return 0;
else return 1;
}
基于BFS遍历的算法如下:
int visited[MAXVEX]; //全局变量
int BFSTrave(AdjGraph *G,int i,int j)
{ int k;
for (k=0;k
visited[k]=0;
BFS(G,i); //需将BFS算法中的visited[]定义改为全局变量
if (visited[j]==0) return 0;
else return 1;
}
假设一个带权有向图采用邻接表存储。设计一个算法输出从顶点u到顶点v并经过顶点k的所有路径及其长度。并用相关数据进行测试。
解:采用基于深度优先遍历的递归算法求解,对应的程序如下:
#include
#include "AdjGraph.h" //包含邻接表的基本运算函数
int visited[MAXVEX];
int Inpath(int k,int path[],int d) //判断path是否含有顶点k
{ int i;
for (i=0;i<=d;i++)
if (path[i]==k)
return 1;
return 0;
}
void FindallPath(AdjGraph *G,int u,int v,int k,int path[],int d,int plength)
//输出G中从u→v经过k的所有路径及其长度
{ ArcNode *p;
int w,i;
visited[u]=1;
d++; path[d]=u; //顶点u加入到路径中
if (u==v && d>=1 && Inpath(k,path,d)) //找到一条包含k的路径
{ printf(" 路径:%d",path[0]);
for (i=1;i<=d;i++) //输出找到一条路径并返回
printf("→%d",path[i]);
printf(":\t长度为%d\n",plength);
}
p=G->adjlist[u].firstarc; //p指向u的第一个相邻点
while (p!=NULL)
{ w=p->adjvex; //相邻点的编号为w
if (visited[w]==0)
{ plength+=p->weight; //增加路径长度
FindallPath(G,w,v,k,path,d,plength); //递归调用
plength-=p->weight; //回退时减去相应长度
}
p=p->nextarc; //p指向下一个相邻点
}
visited[u]=0; //回溯找所有简单路径
}
void main()
{ AdjGraph *G;
int n=6,e=11,i;
int u=0,v=4,k=2;
int path[MAXVEX],d=-1;
int A[][MAXVEX]={
{0,50,10,INF,INF,INF},
{INF,0,15,50,10,INF},
{20,INF,0,15,INF,INF},
{INF,20,INF,0,35,INF},
{INF,INF,INF,30,0,INF},
{INF,INF,INF,3,INF,0}};
CreateGraph(G,A,n,e); //建立图7.30的邻接表
printf("图G的存储结构:\n"); DispGraph(G);
for (i=0;i
visited[i]=0;
printf("从顶点%d到%d并经过顶点%d的所有路径及其长度:\n",u,v,k);
FindallPath(G,u,v,k,path,d,0);
DestroyGraph(G);
}
(1)要进行顺序查找,则线性表( ① );要进行二分查找,则线性表( ② );要进行散列查找,则线性表( ③ )。某顺序表中有90000个元素,已按关键项的值的升序排列。现假定对各个元素进行查找的概率是相同的,并且各个元素的关键项的值皆不相同。当用顺序查找法查找时,平均比较次数约为( ④ ),最大比较次数为( ⑤ )。
①②: A.必须以顺序方式存储 B.必须以链表方式存储
C.必须以散列方式存储
D.既可以以顺序方式,也可以以链表方式存储
E.必须以顺序方式存储且数据元素已按值递增或递减的次序排好
F.必须以链表方式存储且数据元素已按值递增或递减的次序排好
④⑤: A.25000 B.30000 C.45000 D.90000
答:①D ②E ③C ④C ⑤D
(2)链表适用于( )查找
A.顺序 B.二分法 C.顺序,也能二分法 D.随机
答:A
(3)折半查找有序表(4,6,10,12,20,30,50,70,88,100)。若查找表中元素58,则它将依次与表中( )比较大小,查找结果是失败。
A.20,70,30,50 B.30,88,70,50 C.20,50 D.30,88,50
答:A
(4)对22个记录的有序表作折半查找,当查找失败时,至少需要比较( )次关键字。
A.3 B.4 C.5 D.6
答:C
(5)有一个长度为12的有序表,按折半查找法对该表进行查找,在表内各元素等概率情况下查找成功所需的平均比较次数为( )。
A. 35/12 B. 37/12 C. 39/12 D. 43/12
答:B
(6)折半查找与二叉排序树的时间性能( )。
A.相同 B.完全不同 C.有时不相同 D.数量级都是O(log2n)
答:C
(7)用n个关键字构造一棵二叉排序树,其最低高度为( )。
A. n/2 B. n C. log2n D. log2n+1
答:D
(8)在二叉排序树中,关键字最小的结点,它的( )。
A.左指针一定为空 B.右指针一定为空
C.左、右指针均为空 D.左、右指针均不为空
答:A
(9)在二叉排序树中,每个结点的关键码值( ① ),( ② )一棵二叉排序,即可得到排序序列。同一个结点集合,可用不同的二叉排序树表示,人们把平均检索长度最短的二叉排序树称作最佳二叉排序,最佳二叉排序树在结构上的特点是( ③ )。
①:A.比左子树所有结点的关键码值大,比右子树所有结点的关键码值小
B.比左子树所有结点的关键码值小,比右子树所有结点的关键码值大
C.比左右子树的所有结点的关键码值都大
D.与左子树所有结点的关键码值和右子树所有结点的关键码值无必然的大小关系
②:A.先序遍历 B.中序遍历 C.后序遍历 D.层次遍历
③:A.除最下两层可以不满外,其余都是充满的
B.除最下一层可以不满外,其余都是充满的
C.每个结点的左右子树的高度之差的绝对值不大于1
D.最下层的叶子结点必须在最左边
答:①A ②B ③B
(10)哈希法存储的基本思想是根据( ① )来决定( ② ),碰撞(冲突)指的是( ③ ),处理碰撞的两类主要方法是( ④ )。
①②: A.存储地址 B.元素的符号 C.元素个数 D.关键字值
E.非关键字属性 F.平均查找长度 G.装填因子 H.哈希表空间
③:A.两个元素具有相同序号
B.两个元素的关键字值不同,而非关键字属性相同
C.不同关键字值对应到相同的存储地址
D.装填因子过大
E.数据元素过多
④:A.线性探测法和双散列函数法 B.建溢出区法和不建溢出区法
C.除余法和折叠法 D.拉链法和开放地址法
答:①D ②A ③C ④D
(11)假定有k个关键字互为同义词,若用线性探测法把这k个关键字存入哈希表中,至少要进行( )次探测。
A. k-1 B. k C. k+1 D. k(k+1)/2
答:D
(12)哈希表在查找成功时的平均查找长度( )。
A.与处理冲突方法有关,而与装填因子α无关
B.与处理冲突方法无关,而与装填因子α有关
C.与处理冲突方法有关和装填因子α都有关
D.与处理冲突方法无关,也与装填因子α无关
答:C
(13)在一棵平衡二叉树中,每个结点的平衡因子的取值范围是( )。
A. -1~1 B. -2~2 C. 1~2 D. 0~1
答:A
(14)具有5层结点的AVL树至少有( )个结点。
A. 10 B. 12 C. 15 D. 17
答:B
(15)下述叙述中( )是不成立的。
A. m阶B-树中的每个分支结点的子树个数都小于或等于m
B. m阶B-树中的每个分支结点的子树个数都大于或等于m/2
C. m阶B-树中的任何一个结点的子树高度都相等
D. m阶B-树具有k个子树的非叶子结点含有k-1个关键字
答:B
(16)下列叙述中,不符合m阶B树定义要求的是( )。
A. 根结点最多有m棵子树 B. 所有叶子结点都在同一层上
C. 各结点内关键字均升序或降序排列 D. 叶子结点之间通过指针链接
答:D
(17)高度为5的3阶B-树至少有( )个结点。
A. 32 B. 31 C. 64 D. 108
答:B
(18)下面关于B-树和B+树的叙述中,不正确的是( )。
A. B-树和B+树都能有效地支持顺序查找 B. B-树和B+树都能有效地支持随机查找
C. B-树和B+树都是平衡的多分树 D. B-树和B+树都可用于文件索引结构
答:A
(1)顺序查找含n个元素的顺序表,若查找成功,则比较关键字的次数最多为( ① )次;若查找不成功,则比较关键字的次数为( ② )次。
答:①n ②n
(2)假设在有序顺序表A[1..20]上进行折半查找,比较1次查找成功的记录数为( ① ),比较2次查找成功的记录数为( ② ),比较3次查找成功的记录数为( ③ ),比较4次查找成功的记录数为( ④ ),比较5次查找成功的记录数为( ⑤ ),等概率情况下成功查找的平均查找长度约为( ⑥ )。
答:①1 ②2 ③4 ④8 ⑤5 ⑥3.7。
(3)已知有序表为{12,18,24,35,47,50,62,83,90,115,134},当用折半法查找90时,需进行( ① )次关键字比较可确定成功;查找47时需进行( ② )次关键字比较可确定成功;查找100时,需进行( ③ )次关键字比较才能确定失败。
答:①2 ②4 ③3。
(4)在分块查找方法中,首先查找( ① ),然后再查找相应的( ② )。
答:①索引表 ②数据表。
(5)高度为5(除叶子层之外)的3阶B-树至少有( )个结点。
答:31
(6)按关键字13、24、37、90、53的次序建立一棵平衡二叉树,则该平衡二叉树的高度是( ① ),根结点关键字为( ② ),其左子树中的关键字是( ③ ),其右子树中的关键字是( ④ )。
答:①3 ②24 ③{13} ④{37,53,90}
(7)在哈希表中,装填因子α的值越大,则( ① );α的值越小,则( ② )。
答:①存取元素时发生冲突的可能性就越大 ②存取元素时发生冲突的可能性就越小
(1)简述顺序查找法、折半查找法和分块查找法对被查找的表中元素的要求。对长度为n的表来说,三种查找法在查找成功时的平均查找长度各是多少?
答:三种方法对查找的要求分别如下。
① 顺序查找法:表中元素可以任意次序存放。
② 折半查找法:表中元素必须按关键字递增或递减排列,且最好采用顺序存储结构。
③ 分块查找法:表中元素每块内的元素可以任意次序存放,但块与块之间必须以关键字的大小递增(或递减)排列,即前一块内所有元素的关键字都不能大(或小)于后一块内任何元素的关键字。
三种方法的平均查找长度分别如下。
① 顺序查找法:查找成功的平均查找长度为(n+1)/2。
② 折半查找法:查找成功的平均查找长度为log2(n+1)-1。
③ 分块查找法:若用顺序查找确定所在的块,平均查找长度为: (+s)+1;若用二分查找确定所在块,平均查找长度为log2(+1)+。其中,s为每块含有的元素个数。
(2)折半查找适不适合链表结构的序列,为什么?用折半查找的查找速度必然比线性查找的速度快,这种说法对吗?
答:不适合。虽然有序的单链表的结点是按从小到大(或从大到小)顺序排列,但因其存储结构为单链表,查找结点时只能从头指针开始逐步搜索,故不能进行折半查找。
折半查找的速度在一般情况下是快些,但在特殊情况下未必快。例如所查数据位于首位时,则线性查找快;而二分查找则慢得多。
(3)给定关键字序列为{3,5,7,9,11,13,15,17},回答以下问题:
① 按表中元素的顺序依次插入一棵初始值为空的二叉排序树。画出插入完成后的二叉排序树,并求其在等概率情况下查找成功的平均查找长度。
② 按表中元素顺序构造一棵平衡二叉树,并求其在等概率情况下查找成功的平均查找长度,与(1)比较,可得出什么结论?
答:① 按输入顺序构造的二叉排序树如图8.1所示。在等概率情况下查找成功的平均查找长度为:
ASLsucc==4.5
② 构造的一棵平衡二叉树如图8.2所示。在等概率情况下查找成功的平均查找长度:
ASLsucc==2.75
由此可见在同样序列的查找中,平衡二叉树比二叉排序树的平均查找长度要小,查找效率要高。
图8.1 一棵二叉排序树 图8.2 一棵平衡二叉树
(4)输入一个正整数序列{40,28,6,72,100,3,54,1,80,91,38},建立一棵二叉排序树,然后删除结点72,分别画出该二叉树及删除结点72后的二叉树。
答:构造的二叉排序树如图8.3所示。为了删除结点72,在其左子树中找到最大结点54(只有一个结点),用其代替结点72。删除之后的二叉排序树如图8.4所示。
图8.3 二叉排序树 图8.4 删除72后的二叉排序树
(5)在如图8.5所示的AVL树中,画出依次插入关键字为6和10的两个结点后的AVL树。
图8.5 一棵AVL树
答:插入关键字为6和10的两个结点,其调整过程如图8.6所示。
图8.6 插入两个结点,AVL树调整过程
(6)对一个固定的数据集,用比较两个元素大小的方法在一个给定的序列中查找某个元素的时间复杂度下限是什么?如果要求时间复杂度更小,你采用什么方法?此方法的时间复杂度是多少?
答:查找某个元素的时间复杂度下限,如果理解为最短查找时间,则当关键字值与表头元素相同时,比较1次即可。要想降低时间复杂度,可以改用Hash查找法。此方法对表内每个元素的比较次数都是O(1)。
(7)设有一组关键字{19,01,23,14,55,20,84,27,68,11,10,77},采用哈希函数:
H(key)=key % 13
采用开放地址法的线性探测法解决冲突,试在0~18的哈希地址空间中对该关键字序列构造哈希表,并求成功和不成功情况下的平均查找长度。
解:依题意,m=19,线性探测法计算下一地址计算公式为:
d1=H(key)
dj+1=(dj+1) % m; j=1, 2, ...
其计算函数如下:
H(19)=19 mod 13=6
H(01)=01 mod 13=1
H(23)=23 mod 13=10
H(14)=14 mod 13=1 冲突
H(14)=(1+1) mod 19 =2
H(55)=55 mod 13=3
H(20)=20 mod 13=7
H(84)=84 mod 13=6 冲突
H(84)=(6+1) mod 19=7 仍冲突
H(84)=(7+1) mod 19=8
H(27)=27 mod 13=1 冲突
H(27)=(1+1) mod 19=2 冲突
H(27)=(2+1) mod 19=3 仍冲突
H(27)=(3+1) mod 19=4
H(68)=68 mod 13=3 冲突
H(68)=(3+1) mod 19=4 仍冲突
H(68)=(4+1) mod 19=5
H(11)=11 mod 13=11
H(10)=10 mod 13=10 冲突
H(10)=(10+1) mod 19=11 仍冲突
H(10)=(11+1) mod 19=12
H(77)=77 mod 13=12 冲突
H(77)=(12+1) mod 19=13
因此,构建的哈希表如表8.1所示。
表8.1 哈希表
ASL成功=(1+2+1+4+3+1+1+3+1+1+3+2)/12=1.92
ASL不成功=(1+9+8+7+6+5+4+3+2+1+5+4+3+2+1+1+1+1+1)/19=3.42
(8)线性表的关键字集合{87,25,310,08,27,132,68,95,187,123,70,63,47},共有13个元素,已知哈希函数为:
H(k) = k mod 13
采用拉链法处理冲突。设计出这种链表结构,并计算该表的成功和不成功情况下的平均查找长度。
解:依题意,得到:
H(87)=87 mod 13=9
H(25)=25 mod 13=12
H(310)=310 mod 13=11
H(08)=08 mod 13=8
H(27)=27 mod 13=1
H(132)=132 mod 13=2
H(68)=68 mod 13=3
H(95)=95 mod 13=4
H(187)=187 mod 13=5
H(123)=123 mod 13=6
H(70)=70 mod 13=5
H(63)=63 mod 13=11
H(47)=47 mod 13=8
采用拉链法处理冲突的哈希表如图8.7所示。成功查找的平均查找长度:
ASL成功=(1×10+2×3)/10=1.6
ASL不成功=(1×7+2×3)/13=1
图8.7 采用拉链法处理冲突的哈希表
(1)对含有n个元素的整型数组A,设计一个较优的算法同时找最大元素和最小元素。
解:通过一趟扫描并比较,可以找出最大元素max和最小元素min。对应的算法如下:
void MaxMin(int A[],int n,int &min,int &max)
{ int i;
min=max=A[0];
for (i=1;i
if (R[i]
min=R[i];
else if (R[i]>max)
max=R[i];
}
(2)设计二分查找的递归算法。
解:对应的递归算法如下:
int BinSearch1(SqType R[],KeyType k,int low,int high)
{ int mid;
if (low>high)
return(-1);
else
{ mid=(low+high)/2;
if (k==R[mid].key)
return(mid);
else if (k>R[mid].key)
return(BinSearch1(R,k,mid+1,high));//在左子树中递归查找
else
return(BinSearch1(R,k,low,mid-1)); //在右子树中递归查找
}
}
(3)假设二叉排序树bt的各元素值均不相同,设计一个算法按递增次序输出所有结点值。
解:按中序序列遍历二叉排序树即按递增次序遍历,对应值的算法如下:
void incrorder(BSTNode *bt)
{ if (bt!=NULL)
{ incrorder(bt->lchild);
printf("%d ",bt->data);
incrorder(bt->rchild);
}
}
(4)设计一个递归算法,从大到小输出二叉排序树中所有其值不小于k的关键字。
解:由二又排序树的性质可知,右子树中所有结点值大于根结点值,左子树中所有结点值小于根结点值。为了从大到小输出,要先遍历右子树,再访问根结点,后遍历左子树。对应的算法如下:
void Output(BSTNode *bt,KeyType k)
{ if (bt!=NULL)
{ Output(bt->rchild,k);
if (bt->key>=k)
printf("%d ",bt->key);
Output(bt->lchild,k);
}
}
(5)假设二叉排序树中所有结点关键字不同,设计一个算法,求出指定关键字的结点所在的层次。
解:设二叉排序树采用二叉链存储结构。采用二叉排序树非递归查找算法,用h保存查找层次。对应的算法如下:
int level(BSTNode *bt, KeyType k)
{ int h=0;
if (bt!=NULL)
{ h++;
while (bt->data!=k)
{ if (k
bt=bt->lchild; //在左子树中查找
else
bt=bt->rchild; //在右子树中查找
h++; //层数增1
}
return h;
}
}
假设二叉树的数据域为int类型,由其括号表示建立对应的二叉链存储结构,设计一个算法判断该二叉树是否为一棵二叉排序树。并用相关数据进行测试。
解:结合第6章的二叉树基本运算算法和二叉排序树的特点设计对应的程序如下:
#include
#include
#define MaxSize 100
typedef int ElemType; //二叉树的data域设为int类型
typedef struct tnode
{ ElemType data; //数据域
struct tnode *lchild,*rchild; //指针域
} BTNode; //二叉树结点类型
BTNode *pre=NULL; //pre为全局变量,保存当前结点中序前驱结点地址,初值为NULL
int judgeBST(BTNode *bt) //判断二叉树bt是否是一棵二叉排序树
{ int b1,b2;
if (bt==NULL) //空树是BST
return 1;
else
{ b1=judgeBST(bt->lchild);
if (b1==0) //左子树不是BST,整个二叉树也不是BST
return 0;
if (pre!=NULL && pre->data>=bt->data)
return 0;
pre=bt; //pre指向当前访问的结点
b2=judgeBST(bt->rchild); //判断右子树是否为BST
return b2; //返回右子树的判断结果
}
}
void CreateBTree(BTNode * &bt,char *str) //由str建立二叉链bt
{ BTNode *St[MaxSize],*p=NULL;
ElemType d;
int top=-1,k,j=0;
char ch;
bt=NULL; //建立的二叉树初始时为空
ch=str[j];
while (ch!='\0') //str未扫描完时循环
{ switch(ch)
{
case '(':top++;St[top]=p; //为'(',其后结点为栈顶结点的左孩子
k=1; break;
case ')':top--;break; //为')',当前子树结束
case ',':k=2; break; //为',',其后结点为栈顶结点的右孩子
default: //为数字符,提取数字并建相应的结点
d=0;
while (str[j]>='0' && str[j]<='9')
{ //str[j]为数字符,将数字串转换成数值d
d*=10;
d+=str[j]-'0';
j++;
}
j--; //后退一个字符
p=(BTNode *)malloc(sizeof(BTNode));
p->data=d; p->lchild=p->rchild=NULL;
if (bt==NULL) //*p为二叉树的根结点
bt=p;
else //已建立二叉树根结点
{ switch(k)
{
case 1:St[top]->lchild=p;break;
case 2:St[top]->rchild=p;break;
}
}
break;
}
j++;
ch=str[j];
}
}
void DestroyBTree(BTNode *&bt) //销毁二叉树
{ if (bt!=NULL)
{ DestroyBTree(bt->lchild);
DestroyBTree(bt->rchild);
free(bt);
}
}
void DispBTree(BTNode *bt) //输出二叉树
{ if (bt!=NULL)
{ printf("%d",bt->data);
if (bt->lchild!=NULL || bt->rchild!=NULL)
{ printf("("); //有子树时输入'('
DispBTree(bt->lchild); //递归处理左子树
if (bt->rchild!=NULL) //有右子树时输入'.'
printf(",");
DispBTree(bt->rchild); //递归处理右子树
printf(")"); //子树输出完毕,再输入一个')'
}
}
}
void main()
{ BTNode *bt;
char a[]="25(18(2(,4(,11))),46(39(32),53(,74(67(60)))))";
CreateBTree(bt,a);
printf("二叉树:"); DispBTree(bt); printf("\n");
if (judgeBST(bt))
printf("该二叉树是一棵BST\n");
else
printf("该二叉树不是一棵BST\n");
DestroyBTree(bt);
}
(1)内排序方法中,从未排序序列中依次取出元素与已排序序列中的元素进行比较,将其放入已排序序列的正确位置上的方法,称为( )。
A.希尔排序 B.冒泡排序 C.直接插入排序 D.简单选择排序
答:C
(2)对有n个记录的表进行直接插入排序,在最坏情况下需进行( )次关键字比较。
A.n-1 B.n+1 C.n/2 D.n(n-1)/2
答:D
(3)在下列算法中,( )算法可能出现下列情况:在最后一趟开始之前,所有的元素都不在其最终的位置上。
A.堆排序 B.冒泡排序 C.直接插入排序 D.快速排序
答:C
(4)对数据序列{15,9,7,8,20,-1,4}进行排序,进行一趟后数据的排序变为{9,15,7,8,20,-1,4},则采用的是( )算法。
A.简单选择排序 B.冒泡排序 C.直接插入排序 D.堆排序
答:C
(5)数据序列{5,4,15,10,3,1,9,6,2}是某排序方法第一趟后的结果,该排序算法可能是( )。
A.冒泡排序 B.二路归并排序 C. 堆排序 D.简单选择排序
答:B
(6)从未排序序列中挑选元素,并将其依次插入已排序序列的一端的方法,称为( )。
A.希尔排序 B.归并排序 C.直接插入排序 D.简单选择排序
答:D
(7)在以下排序方法中,关键字比较的次数与元素的初始排列次序无关的是( )。
A.希尔排序 B.冒泡排序 C.插入排序 D.简单选择排序
答:D
(8)对n个不同的关键字进行递增冒泡排序,在下列哪种情况下比较的次数最多( )。
A.元素无序 B.元素递增有序 C.元素递减有序 D.都一样
答:C
(9)快速排序在下列哪种情况下最易发挥其长处( )。
A.被排序的数据中含有多个相同排序码 B.被排序的数据已基本有序
C.被排序的数据随机分布 D.被排序的数据中最大值和最小值相差悬殊
答:C
(10)序列{5,2,4,1,8,6,7,3}是第一趟递增排序后的结果,则采用的排序方法可能是( )。
A.快速排序 B.冒泡排序 C.堆排序 D.直接插入排序
答:D
(11)序列{3,2,4,1,5,6,8,7}是第一趟递增排序后的结果,则采用的排序方法可能是( )。
A.快速排序 B.冒泡排序 C.堆排序 D.简单选择排序
答:A
(12)以下关于快速排序的叙述中正确的是( )。
A.快速排序在所有排序方法中为最快,而且所需辅助空间也最少
B.在快速排序中,不可以用队列替代栈
C.快速排序的空间复杂度为O(n)
D.快速排序在待排序的数据随机分布时效率最高
答:D
(13)堆排序是一种( )排序。
A.插入 B.选择 C.交换 D.归并
答:B
(14)以下序列不是堆的是( )。
A.{100,85,98,77,80,60,82,40,20,10,66} B.{100,98,85,82,80,77,66,60,40,20,10}
C.{10,20,40,60,66,77,80,82,85,98,100} D.{100,85,40,77,80,60,66,98,82,10,20}
答:D
(15)有一组数据{15,9,7,8,20,-1,7,4},用堆排序的筛选方法建立的初始堆为( )。
A.{-1,4,8,9,20,7,15,7} B.{-1,7,15,7,4,8,20,9}
C.{-1,4,7,8,20,15,7,9} D.以上都不对
答:C
(16)下述几种排序方法中,要求辅助内存最大的是( )。
A.插入排序 B.快速排序 C.归并排序 D.选择排序
答:C
(17)以下排序方法中,( )不需要进行关键字的比较。
A.快速排序 B.归并排序 C.基数排序 D.堆排序
答:C
(18)采用败者树进行k路平衡归并的外排序算法,其总的归并效率与k( )。
A.有关 B.无关
答:B
(1)大多数内排序算法都有两个基本的操作:( ① )和( ② )。
答:①比较 ②移动
(2)对含有n个元素的数序进行直接插入排序,在最好情况下移动元素的个数是( ① ),关键字比较的次数是( ② )。
答:①0 ②n-1。
(3)对于n个记录的顺序表进行冒泡排序,在最坏的情况下的时间复杂度是( ① ),若对其进行快速排序,在最坏的情况下的时间复杂度是( ② )。
答:①O(n2) ②O(n2)
(4)在直接插入和简单选择排序中,若初始数据基本正序,则选用( ① ),若初始数据基本反序,则选用( ② )。
答:①直接插入 ②简单选择排序
(5)每趟通过基准间接比较两个元素,若出现逆序排列时就交换它们的位置,一趟排序后将基准元素放在最终位置上。此种排序方法叫做( )。
答:快速排序
(6)在堆排序和快速排序中,若初始记录接近正序或反序,则选用( ① ),若初始记录基本无序,则最好选用( ② )。
答:①堆排序 ②快速排序
(7)对于n个记录的顺序表进行二路归并排序时,平均时间复杂度是( ① ),空间复杂度是( ② )。
答:①O(nlog2n) ②O(n)
(8)对于n个记录的表进行二路归并排序,整个归并排序需进行( )趟。
答:log2n
(9)在一个大根堆中,元素值最小的结点是( )。
答:某个叶子结点。
(10)已知关键字序列k1k2…kn构成一个小根堆,则最小关键字是( ① ),并且在该序列对应的完全二叉树中,从根结点到叶子结点的路径上关键字组成的序列具有( ② )的特点。
答:①k1 ②递增。
(11)外排序有两个基本阶段,第一阶段是 ① ,第二阶段是 ② 。
答:①生成初始归并段 ②对这些初始归并段采用某种归并方法,进行多遍归并。
(1)简要叙述如何选择好的内排序方法。
答:没有哪一种内排序方法是绝对好的。每一种排序方法都有其优缺点,适合于不同的环境。因此,在实际应用中,应根据具体情况做选择。首先考虑排序对稳定性的要求,若要求稳定,则只能在稳定方法中选取,否则可以在所有方法中选取;其次要考虑待排序结点数n的大小,若n较大,则可在改进方法中选取,否则在简单方法中选取;然后再考虑其他因素。下面给出综合考虑了以上几个方面所得出的大致结论:
① 当待排序的结点数n较大,关键字的值分布比较随机,并且对排序稳定性不作要求时,宜选用快速排序法。
② 当待排序的结点数n较大,内存空间又允许,并且要求排序稳定时,宜采用归并排序法。
③ 当待排序的结点数n较大,关键字的值的分布可能出现升序或者逆序的情况,并且对排序稳定性不作要求时,宜采用堆排序方法或者归并排序方法。
④ 当待排序的结点数n较小,关键字的值基本有序(升序)或者分布比较随机,并且有排序稳定性要求时,宜采用插入排序法。
⑤ 当待排序的结点数n较小,对排序稳定性不作要求时,宜采用选择排序方法,若关键字的值不接近逆序,亦可采用直接插入排序法。
⑥ 已知两个有序表,若要将它们组合成一个新的有序表,最好的方法是归并排序方法。
(2)给出关键字序列{4,5,1,2,8,6,7,3,10,9}的希尔排序过程。
答:希尔排序过程如图9.1所示。
图9.1 希尔排序各趟排序结果
(3)一个有n个整数的数组R[1..n],其中所有元素是有序的,将其看成是一棵完全二叉树,该树构成一个堆吗?若不是,请给一个反例,若是,请说明理由。
答:该数组一定构成一个堆,递增有序数组构成一个小根堆,递减有序数组构成一个大根堆。
以递增有序数组为例,假设数组元素为k1、k2、…、kn是递增有序的,从中看出下标越大的元素值也越大,对于任一元素ki,有ki
(4)已知序列{75,23,98,44,57,12,29,64,38,82},给出采用冒泡排序法对该序列作升序排序时的每一趟的结果。
答:采用冒泡排序法排序的各趟的结果如下:
初始序列: 75 23 98 44 57 12 29 64 38 82
i=0(归位元素:12):12 75 23 98 44 57 29 38 64 82
i=1(归位元素:23):12 23 75 29 98 44 57 38 64 82
i=2(归位元素:29):12 23 29 75 38 98 44 57 64 82
i=3(归位元素:38):12 23 29 38 75 44 98 57 64 82
i=4(归位元素:44):12 23 29 38 44 75 57 98 64 82
i=5(归位元素:57):12 23 29 38 44 57 75 64 98 82
i=6(归位元素:64):12 23 29 38 44 57 64 75 82 98
(5)已知序列{75,23,98,44,57,12,29,64,38,82},给出采用快速排序法对该序列作升序排序时的每一趟的结果。
答:采用快速排序法排序的各趟的结果如下:
初始序列: 75 23 98 44 57 12 29 64 38 82
区间:0-9 基准:75: 38 23 64 44 57 12 29 75 98 82
区间:0-6 基准:38: 29 23 12 38 57 44 64 75 98 82
区间:0-2 基准:29: 12 23 29 38 57 44 64 75 98 82
区间:0-1 基准:12: 12 23 29 38 57 44 64 75 98 82
区间:4-6 基准:57: 12 23 29 38 44 57 64 75 98 82
区间:8-9 基准:98: 12 23 29 38 44 57 64 75 82 98
(6)已知序列{75,23,98,44,57,12,29,64,38,82},给出采用简单选择排序法对该序列作升序排序时的每一趟的结果。
答:采用直接选择法排序的各趟的结果如下:
初始序列: 75 23 98 44 57 12 29 64 38 82
i=0 归位元素:12 12 23 98 44 57 75 29 64 38 82
i=1 归位元素:23 12 23 98 44 57 75 29 64 38 82
i=2 归位元素:29 12 23 29 44 57 75 98 64 38 82
i=3 归位元素:38 12 23 29 38 57 75 98 64 44 82
i=4 归位元素:44 12 23 29 38 44 75 98 64 57 82
i=5 归位元素:57 12 23 29 38 44 57 98 64 75 82
i=6 归位元素:64 12 23 29 38 44 57 64 98 75 82
i=7 归位元素:75 12 23 29 38 44 57 64 75 98 82
i=8 归位元素:82 12 23 29 38 44 57 64 75 82 98
(7)已知序列{75,23,98,44,57,12,29,64,38,82},给出采用堆排序法对该序列升序排序时的每一趟的结果。
答:采用堆排序法排序的各趟的结果如下:
初始序列: 75 23 98 44 57 12 29 64 38 82
初始堆: 98 82 75 64 57 12 29 44 38 23
归位元素:98 调整成堆[1..9]: 82 64 75 44 57 12 29 23 38 98
归位元素:82 调整成堆[1..8]: 75 64 38 44 57 12 29 23 82 98
归位元素:75 调整成堆[1..7]: 64 57 38 44 23 12 29 75 82 98
归位元素:64 调整成堆[1..6]: 57 44 38 29 23 12 64 75 82 98
归位元素:57 调整成堆[1..5]: 44 29 38 12 23 57 64 75 82 98
归位元素:44 调整成堆[1..4]: 38 29 23 12 44 57 64 75 82 98
归位元素:38 调整成堆[1..3]: 29 12 23 38 44 57 64 75 82 98
归位元素:29 调整成堆[1..2]: 23 12 29 38 44 57 64 75 82 98
归位元素:23 调整成堆[1..1]: 12 23 29 38 44 57 64 75 82 98
(8)已知序列{75,23,98,44,57,12,29,64,38,82},给出采用归并排序法对该序列作升序排序时的每一趟的结果。
答:采用归并排序法排序的各趟的结果如下:
初始序列: 75 23 98 44 57 12 29 64 38 82
length=1: 23 75 44 98 12 57 29 64 38 82
length=2: 23 44 75 98 12 29 57 64 38 82
length=4: 12 23 29 44 57 64 75 98 38 82
length=8: 12 23 29 38 44 57 64 75 82 98
(9)已知序列{503,187,512,161,908,170,897,275,653,462},给出采用基数排序法对该序列作升序排序时的每一趟的结果。
答:采用基数排序法排序的各趟的结果如下:
初始序列: 503 187 512 161 908 170 897 275 653 462
按个位排序结果: 170 161 512 462 503 653 275 187 897 908
按十位排序结果: 503 908 512 653 161 462 170 275 187 897
按百位排序结果: 161 170 187 275 462 503 512 653 897 908
(10)如果在106个记录中找前10个最小的记录,你认为采用什么样的排序方法所需的关键字比较次数最少?为什么?
答:采用堆排序方法,建立初始堆时间为4n,每次选取一个最小记录后再筛选的时间为log2n,找前10个最小的记录的时间=4n+9log2n。而冒泡排序和简单选择排序需10n的时间。而直接插入排序、希尔排序和二路归并排序等必须全部排好序后才能找前10个最小的记录,显然不能采用。
(11)设有11个长度(即包含记录个数)不同的初始归并段,它们所包含的记录个数为{25,40,16,38,77,64,53,88,9,48,98}。试根据它们做4路平衡归并,要求:
① 指出总的归并趟数;
② 构造最佳归并树;
③ 根据最佳归并树计算每一趟及总的读记录数。
答:① 总的归并趟数=log411=2。
② m=11,k=4,(m-1) MOD (k-1)=1≠0,需要附加k-1-(m-1) MOD (k-1)=2个长度为0的虚归并段,最佳归并树如图9.2所示。
图9.2 最佳归并树
③ 根据最佳归并树计算每一趟及总的读记录数:
第1趟的读记录数=9+16=25
第2趟的读记录数=25+25+38+40+48+53+64+77=370
第3趟的读记录数=128+88+242+98=556
总的读记录数=25+370+556+951。
(1)设计一个直接插入算法:设元素为R[0..n-1],其中R[i-1..n-1]为有序区,R[0..i]为无序区,对于元素R[i],将其关键字与有序区元素(从头开始)进行比较,找到一个刚好大于R[i].key的元素R[j],将R[i..j-1]元素前移,然后将原R[i]插入到R[j-1]处。要求给出每趟结束后的结果。
解:对应的算法如下:
void InsertSort3(SqType R[],int n)
{ int i,j,k;
SqType tmp;
for (i=n-2;i>=0;i--)
{ tmp=R[i];
j=i+1;
while (j
j++; //在有序区找到一个刚大于tmp.key的位置R[j]
for (k=i;k
R[k]=R[k+1];
R[j-1]=tmp; //在j-1位置处插入tmp
printf(" i=%d: ",i);
for (k=0;k
printf("%d ",R[k].key);
printf("\n");
}
}
(2)有一种简单的排序算法,叫做计数排序。这种排序算法对一个待排序的表(用数组表示)进行排序,并将排序结果存放到另一个新的表中。必须注意的是,表中所有待排序的关键字互不相同,计数排序算法针对表中的每个元素,扫描待排序的表一趟,统计表中有多少个元素的关键字比该元素的关键字小。假设对某一个元素,统计出数值为c,那么这个元素在新的有序表中的合适的存放位置即为c。
① 设计实现计数排序的算法。
② 对于有n个元素的表,比较次数是多少?
③ 与简单选择排序相比,这种方法是否更好?为什么?
解:① 对应的计数排序的算法如下:
void CountSort(SqType A[],SqType B[],int n)
{ int i,j,count;
for (i=0;i
{ count=0; //统计A中小于A[i].key的元素个数
for (j=0;j
count++;
B[count]=A[i];
}
}
② 对于有n个元素的表,每个元素都要与n个元素(含自身)进行比较,关键字比较的总次数是n2。
③ 简单选择排序比这种计数排序好,因为对有n个元素的数据表进行简单排序只需进行1+2+…+(n-1)=n(n-1)/2次比较,且可在原地进行排序。
(3)设计一个算法,判断一个数据序列是否构成一个大根堆。
解:当数据个数n为偶数时,最后一个分支结点(编号为n/2)只有左孩子(编号为n),其余分支结点均为双分支结点;当n为奇数时,所有分支结点均为双分支结点。对每个分支结点进行判断,只有一个分支结点不满足小根堆的定义,返回0;如果所有分支结点均满足小根堆的定义,返回1。对应的算法如下:
int IsHeap(SqType R[],int n)
{ int i;
if (n%2==0) //n为偶数时,最后一个分支结点(编号为n/2)只有左孩子(编号为n)
{ if (R[n/2].key>R[n].key)
return(0);
for (i=n/2-1;i>=1;i--) //判断所有双分支结点
if (R[i].key>R[2*i].key || R[i].key>R[2*i+1].key)
return(0);
}
else //n为奇数时,所有分支结点均为双分支结点
{ for (i=n/2;i>=1;i--) //判断所有双分支结点
if (R[i].key>R[2*i].key || R[i].key>R[2*i+1].key)
return(0);
}
return(1);
}
有一个整型数组A[0..n-1],前m(0<m<n)个元素是递增有序的,后n-m个元素也是递增有序的,设计满足以下条件的算法使A中所有元素均递增有序,并用相关数据进行测试。
(1)要求算法的时间复杂度为O(n),设计相应的算法Sort1(A,m,n)。
(2)要求算法的空间复杂度为O(1),设计相应的算法Sort2(A,m,n)。
解:Sort1算法采用二路归并思想,Sort2算法采用直接插入思想。对应的程序如下:
#include
#include
#define MaxSize 100
void Sort1(int A[],int m,int n)
//将A[0..m-1]和A[m..n-1]两个相邻的有序表归并为一个有序表A[0..n-1]
{ int *A1;
int i=0,j=m,k=0;
A1=(int *)malloc(n*sizeof(int)); //动态分配空间
while (i<=m-1 && j<=n-1) //在第1子表和第2子表均未扫描完时循环
if (A[i]<=A[j]) //将第1子表中的记录放入A1中
{ A1[k]=A[i];
i++;k++;
}
else //将第2子表中的记录放入A1中
{ A1[k]=A[j];
j++;k++;
}
while (i<=m-1) //将第1子表余下部分复制到A1
{ A1[k]=A[i];
i++;k++;
}
while (j<=n-1) //将第2子表余下部分复制到A1
{ A1[k]=A[j];
j++;k++;
}
for (i=0;i
A[i]=A1[i];
free(A1);
}
void Sort2(int A[],int m,int n) //将A[m..n-1]的元素有序插入到前端
{ int i,j;
int tmp;
for (i=m;i
{ tmp=A[i];
j=i-1; //从右向左在有序区A[0..i-1]中找A[i]的插入位置
while (j>=0 && A[j]>tmp)
{ A[j+1]=A[j]; //将大于tmp的元素后移
j--; //继续向前比较
}
A[j+1]=tmp; //在j+1处插入A[i]
}
}
void Disp(int A[],int n)
{ int i;
for (i=0;i
printf("%3d",A[i]);
printf("\n");
}
void main()
{ int A[]={1,5,7,9,2,3,4,5,8,10,12,15};
int B[]={1,5,7,9,2,3,4,5,8,10,12,15};
int n=12,m=4;
printf("Sort1算法执行前:"); Disp(A,n);
printf("m=%d,n=%d\n",m,n);
Sort1(A,m,n);
printf("Sort1的执行结果:"); Disp(A,n);
printf("Sort2算法执行前:"); Disp(B,n);
printf("m=%d,n=%d\n",m,n);
Sort2(B,m,n);
printf("Sort2的执行结果:"); Disp(B,n);
}
本文来源:https://www.2haoxitong.net/k/doc/dd1568d5f46527d3250ce09f.html
文档为doc格式