数据结构练习答案.docx
《数据结构练习答案.docx》由会员分享,可在线阅读,更多相关《数据结构练习答案.docx(20页珍藏版)》请在冰豆网上搜索。
数据结构练习答案
数据结构练习
1.填写下面表格,对以下几种排序方法进行比较:
排序方法
平均时间复杂度
最坏情况
空间复杂度
是否稳定
-
选择排序
直接选择排序
O(n2)
O(n2)
O
(1)
不稳定
堆排序
O(nlog2n)
O(nlog2n)
]
O
(1)
不稳定
插入排序
直接接插入排序
O(n2)
O(n2)
O
(1)
稳定
拆半接插入排序
"
O(nlog2n)
O(n2)
O
(1)
稳定
Shell排序
O(nlog2n)
O(nlog2n)
O
(1)
不稳定
]
冒泡排序
交换排序
O(n2)
O(n2)
O
(1)
稳定
快速排序
O(nlog2n)
O(n2)
|
O
(1)
不稳定
归并排序
2-路归并排序
O(nlog2n)
O(nlog2n)
O(n)
稳定
红黑树
|
O(nlog2n)
O(nlog2n)
O
(1)
不稳定
线性排序
计数排序
O(N+K)
O(N+K)
O(N+K)
稳定
桶排序
O(N)
O(N)
O(N)
稳定
基数排序
O(d(n+rd))
O(d(n+rd))
]
O(rd)
稳定
解释:
时间复杂度O(d(n+rd)):
其中分配为O(n);收集为O(rd)(r为基、d为“分配-收集”的趟数)
;
习题
一.填空题
1.进栈序列是1、2、3、4,进栈过程中可以出栈,则可能的出栈序列有个,不可能的出栈序列是。
2.'
3.具有N个元素的顺序存储的循环队列中,假定front和rear分别指向队头元素的前一位置和队尾元素的位置,则判断队空的和队满的条件分别是f=r和f=rmodm+1。
求此队列中元素个数的计算公式为:
((r+m)-f-1)modm+1。
入队运算:
r:
=rmodm+1。
出队运算:
f:
=fmodm+1。
4.单链表是非顺序线性的链式存储结构,链栈和链队分别是和的链式存储结构。
5.线性表的顺序存储中,元素之间的逻辑关系是通过元素存储地址次序决定的,在线性表的链接存储中,元素之间的逻辑关系是通过元素存储指针地址访问决定的。
6.深度为5的二叉树至多有结点数为31。
7.数据结构即数据的逻辑结构包括顺性存储结构、链式存储结构、非线性结构三种类型,树型结构和图型结构称为非线性结构。
四种基本存储方法:
(1)顺序存储方法
(2)链接存储方法(3)索引存储方法(4)散列存储方法
二.选择题
1.>
2.有一个10阶对称矩阵,采用压缩存储方式,以行序为主序存储,A[0][0]的地址为1,则A[7][4]的地址为(C)
A13B.18C.33D.40
3.线性表采用链表存储时其存储地址D。
A.必须是连续的B.部分地址必须是连续的
C.一定是不连续的D.连续不连续都可以
4.下列叙述中错误的是C。
A.串是一种特殊的线性表,其特殊性体现在数据元素是一个字符
B.栈和队列是两种特殊的线性表,栈的特点是后进先出,队列的特点是先进先出。
C.线性表的线性存储结构优于链式存储结构
D.;
E.二维数组是其数据元素为线性表的线性表
5.一棵二叉树的顺序存储结构如题图4-1所示,若中序遍历该二叉树,则遍历次序为A.
6.A.DBEGACFHB.ABDEGCFH
7.C.DGEBHFCAD.ABCDEFGH
8.1234567891011121314
A
B
C
D
E
F
%
G
H
题图4-1
9.?
10.设一棵二叉树的顺序存储结构如题图4-2所示,则该二叉树是C.
A.完全二叉树B.满二叉树C.深度为4的二叉树
D.深度为3的二叉树
1234567891011
1
2
3
4
5
:
6
7
题图4-2
11.—
12.设T是Huffman树,它具有6个树叶,且各树叶的权分别为1,2,3,4,5,6。
那么该树的非叶子结点的权之和为A。
A.51B.21C.30D.49
7.设有一无向图的邻接矩阵如下所示,则该图所有顶点的度之和为C。
abcde
a01110
b10101
c11000
d10000
e01000
A.8B.9C.10D.11
。
8.已知二叉树的后序遍历序列是fedbgca,中序遍历序列是dfebagc,则该二叉树的先序遍历序列是D。
A.defbagcB.abcdgefC.dbaefcgD.abdefcg
9.由三个结点构成的二叉树,共有C种不同的形态。
A.3B.4C.5D.6
10.在一个具有n个顶点的无向图中,要连通全部顶点至少需要D条边
A.nB.n+1C.n/2D.n-1
11.对线性表进行折半(二分)查找时,要求线性表必须B。
A.以顺序方式存储B.以顺序方式存储且数据元素有序
C.以链表方式存储D.以链表方式存储且数据元素有序
—
12.顺序查找一个具有n个元素的线性表,其时间复杂度为A,二分查找一个具有n个元素的线性表,其时间复杂度为B。
A.O(n)B.O(log2n)C.O(n2)D.O(nlog2n)
13.从未排序序列中依次取出元素与已排序序列中的元素进行比较,将其放入已排序序列中的正确位置上,此方法称为直接插入排序;从未排序序列中挑选元素,并将其放入已排序序列中的一端,此方法称为直接选择排序;依次将每两个相临的有序表合并成一个有序表的排序方法称为归并排序;当两个元素比较出现反序时就相互交换位置的排序方法称为交换排序;
A.归并排序B.选择排序
C.交换排序D.插入排序
三.
简述下面的几个概念:
单链表,栈、队列,排序二叉树。
四.简述空串和空格串的区别。
五.一棵度为2的树与二叉树有何区别
六.试分别画出具有3个结点的树和具有3个结点的二叉树的所有不同形态。
七.已知一二叉树如题图4-3所示,
1.用二叉链表和顺序存储方式分别存储此二叉树。
画出相应的存储结构图。
2.《
3.写出此二叉树的中序、先序、后序遍历序列。
八.
已知一无向图如题图4-4所示,请给出该图的
1.每个顶点的度。
2.\
3.邻接矩阵
4.邻接表
5.按上述的邻接表写出广度和深度遍历序列。
九.已知一组数据元素为(46,75,18,54,15,27,42,39,88,67)
1.利用直接插入排序方法,写出每次插入后的结果。
2.利用快速排序方法,写出每趟排序后的结果。
3.利用2-路归并排序方法,写出每趟归并后的结果。
4.利用冒泡排序方法,写出每趟排序后的结果。
一十.假定一个表为(32,75,63,48,94,25,36,18,70),散列空间为[0..10],
1.若采用除留余数法构造表,哈希函数为H(K)=KMOD11,用线性探测法解决冲突,试画出哈希表,并求在等概率情况下的平均查找长度。
2.\
3.若采用除留余数法构造表,哈希函数为H(K)=KMOD11,用链地址法解决冲突,试画出哈希表,并求在等概率情况下的平均查找长度。
一十一.有8个带权结点,权值为(3,7,8,2,6,10,14,9),试以它们为叶子结点构造一棵哈夫曼树(要求每个结点左子树的权值小于等于右子树的权值),并计算出带权路径长度。
一十二.一对称阵An*n,若只存储此对称阵的上半三角元,写出以行为主序存储和以列为主序存储时计算每一元素Aij存储地址的公式。
一十三.算法设计
1.写出在循环单链表L中查找查找第i个元素的算法:
SEARCH(L,i)。
2.设有如下题图4-3的单链表,链表头指针为H,写一个将链表进行逆转的算法,逆转以后的链表为题图4-4所示。
3.假定用一个带头结点的循环单链表表示循环队列(循环链队),该队列只设一个队尾指针,不设头指针,试编写下面的算法:
A.向循环链队中插入一个元素x的算法(入队)。
B.从循环链队中删除一个结点(出队)。
4.数组A[N]存放循环队列中的元素,同时设两个变量分别存储队尾元素的位置和队列中实际元素个数。
A.写出此队列的队满条件。
B.`
C.写出此队列的出、入队算法。
5.设LA和LB为两个顺序存储的线性表,且元素按非递减排列,写出算法将其合并为LC,且LC中的元素也按非递减排列。
6.已知一个由n个整数组成的线性表,请定义该线性表的一种存储结构,并用C语言编写算法,实现将n个元素中所有大于等于20的整数放在所有小于等于20的整数之后,要求算法的时间复杂度为O(n),空间复杂度为O
(1)。
7.编写算法,计算二叉树中叶子结点的数目。
8.编写一个按层次顺序(同一层自左向右)遍历二叉树的算法。
三种基于“分配”“收集”的线性排序算法---计数排序、桶排序与基数排序
】
文中代码见原文链接:
非基于比较的排序]
在计算机科学中,排序是一门基础的算法技术,许多算法都要以此作为基础,不同的排序算法有着不同的时间开销和空间开销。
排序算法有非常多种,如我们最常用的快速排序和堆排序等算法,这些算法需要对序列中的数据进行比较,因为被称为基于比较的排序。
基于比较的排序算法是不能突破O(NlogN)的。
简单证明如下:
N个数有N!
个可能的排列情况,也就是说基于比较的排序算法的判定树有N!
个叶子结点,比较次数至少为log(N!
)=O(NlogN)(斯特林公式)。
而非基于比较的排序,如计数排序,桶排序,和在此基础上的基数排序,则可以突破O(NlogN)时间下限。
但要注意的是,非基于比较的排序算法的使用都是有条件限制的,例如元素的大小限制,相反,基于比较的排序则没有这种限制(在一定范围内)。
但并非因为有条件限制就会使非基于比较的排序算法变得无用,对于特定场合有着特殊的性质数据,非基于比较的排序算法则能够非常巧妙地解决。
本文着重介绍三种线性的非基于比较的排序算法:
计数排序、桶排序与基数排序。
[计数排序]
首先从计数排序(CountingSort)开始介绍起,假设我们有一个待排序的整数序列A,其中元素的最小值不小于0,最大值不超过K。
建立一个长度为K的线性表C,用来记录不大于每个值的元素的个数。
算法思路如下:
[
扫描序列A,以A中的每个元素的值为索引,把出现的个数填入C中。
此时C[i]可以表示A中值为i的元素的个数。
对于C从头开始累加,使C[i]<-C[i]+C[i-1]。
这样,C[i]就表示A中值不大于i的元素的个数。
按照统计出的值,输出结果。
由线性表C我们可以很方便地求出排序后的数据,定义B为目标的序列,Order[i]为排名第i的元素在A中的位置,则可以用以下方法统计。
ViewCodeCPP
1
2
3
4
5
*
6
7
8
9
10
11
12
13
14
15
:
16
17
18
19
20
21
22
23
24
25
&
26
27
28
29
30
31
32
33
34
35
;
36
37
38
39
40
41
42
43
44
45
【
46
47
48
49
/*Memo:
计数排序*/
#include
#include
#include
#include
#include
(
usingnamespacestd;
voidCountingSort(int*A,int*B,int*Order,intN,intK)
{
int*C=newint[K+1];
inti;
memset(C,0,sizeof(int)*(K+1));
for(i=1;i<=N;i++)K的随机数
printf("BeforeCS:
/n");
for(i=1;i<=N;i++)
printf("%d",A[i]);
$
CountingSort(A,B,Order,N,K);
printf("/nAfterCS:
/n");
for(i=1;i<=N;i++)
printf("%d",B[i]);
printf("/nOrder:
/n");
for(i=1;i<=N;i++)
printf("%d",Order[i]);
return0;
}
程序运行效果如下:
$
BeforeCS:
2851105993566282
AfterCS:
1222355566889910
Order:
411315936101112214785
显然地,计数排序的时间复杂度为O(N+K),空间复杂度为O(N+K)。
当K不是很大时,这是一个很有效的线性排序算法。
更重要的是,它是一种稳定排序算法,即排序后的相同值的元素原有的相对位置不会发生改变(表现在Order上),这是计数排序很重要的一个性质,就是根据这个性质,我们才能把它应用到基数排序。
[桶排序]
可能你会发现,计数排序似乎饶了点弯子,比如当我们刚刚统计出C,C[i]可以表示A中值为i的元素的个数,此时我们直接顺序地扫描C,就可以求出排序后的结果。
的确是这样,不过这种方法不再是计数排序,而是桶排序(BucketSort),确切地说,是桶排序的一种特殊情况。
用这种方法,可以很容易写出程序,比计数排序还简单,只是不能求出稳定的Order。
ViewCodeCPP
1
2
3
4
%
5
6
7
8
9
10
11
12
13
14
\
15
16
17
18
19
20
21
22
23
24
>
25
26
27
28
29
30
31
32
33
34
/
35
36
/*Memo:
桶排序特殊实现*/
#include
#include
#include
#include
#include
usingnamespacestd;
voidBucketSort(int*A,int*B,intN,intK)
{
int*C=newint[K+1];
inti,j,k;
memset(C,0,sizeof(int)*(K+1));
for(i=1;i<=N;i++)K的随机数
BucketSort(A,B,N,K);
for(i=1;i<=N;i++)
printf("%d",B[i]);
return0;
}
·
这种特殊实现的方式时间复杂度为O(N+K),空间复杂度也为O(N+K),同样要求每个元素都要在K的范围内。
更一般的,如果我们的K很大,无法直接开出O(K)的空间该如何呢
首先定义桶,桶为一个数据容器,每个桶存储一个区间内的数。
依然有一个待排序的整数序列A,元素的最小值不小于0,最大值不超过K。
假设我们有M个桶,第i个桶Bucket[i]存储i*K/M至(i+1)*K/M之间的数,有如下桶排序的一般方法:
扫描序列A,根据每个元素的值所属的区间,放入指定的桶中(顺序放置)。
对每个桶中的元素进行排序,什么排序算法都可以,例如快速排序。
依次收集每个桶中的元素,顺序放置到输出序列中。
对该算法简单分析,如果数据是期望平均分布的,则每个桶中的元素平均个数为N/M。
如果对每个桶中的元素排序使用的算法是快速排序,每次排序的时间复杂度为O(N/M*log(N/M))。
则总的时间复杂度为O(N)+O(M)*O(N/M*log(N/M))=O(N+N*log(N/M))=O(N+N*logN–N*logM)。
当M接近于N是,桶排序的时间复杂度就可以近似认为是O(N)的。
就是桶越多,时间效率就越高,而桶越多,空间却就越大,由此可见时间和空间是一个矛盾的两个方面。
桶中元素的顺序放入和顺序取出是有必要的,因为这样可以确定桶排序是一种稳定排序算法,配合基数排序是很好用的。
ViewCodeCPP
1
2
!
3
4
5
6
7
8
9
10
11
12
:
13
14
15
16
17
18
19
20
21
22
·
23
24
25
26
27
28
29
30
31
32
*
33
34
35
36
37
38
39
40
41
42
!
43
44
45
46
47
48
49
50
51
52
:
53
54
55
56
57
58
59
/*Memo:
桶排序一般实现*/
#include
#include
、
#include
#include
#include
usingnamespacestd;
structlinklist
{
linklist*next;
intvalue;
linklist(intv,linklist*n):
value(v),next(n){}
~linklist(){if(next)deletenext;}
—
};
inlineintcmp(constvoid*a,constvoid*b)
{
return*(int*)a-*(int*)b;
}
/*
为了方便,我把A中元素加入桶中时是倒序放入的,而收集取出时也是倒序放入序列的,所以不违背稳定排序。
*/
voidBucketSort(int*A,int*B,intN,intK)
{
%
linklist*Bucket[101],*p;K的随机数
BucketSort(A,B,N,K);
for(i=1;i<=N;i++)
printf("%d",B[i]);
return0;
}
[基数排序]
下面说到我们的重头戏,基数排序(RadixSort)。
上述的基数排序和桶排序都只是在研究一个关键字的排序,现在我们来讨论有多个关键字的排序问题。
假设我们有一些二元组(a,b),要对它们进行以a为首要关键字,b的次要关键字的排序。
我们可以先把它们先按照首要关键字排序,分成首要关键字相同的若干堆。
然后,在按照次要关键值分别对每一堆进行单独排序。
最后再把这些堆串连到一起,使首要关键字较小的一堆排在上面。
按这种方式的基数排序称为MSD(MostSignificantDight)排序。
第二种方式是从最低有效关键字开始排序,称为LSD(LeastSignificantDight)排序。
首先对所有的数据按照次要关键字排序,然后对所有的数据按照首要关键字排序。
要注意的是,使用的排序算法必须是稳定的,否则就会取消前一次排序的结果。
由于不需要分堆对每堆单独排序,LSD方法往往比MSD简单而开销小。
下文介绍的方法全部是基于LSD的。
!
通常,基数排序要用到计数排序或者桶排序。
使用计数排序时,需要的是Order数组。
使用桶排序时,可以用链表的方法直接求出排序后的顺序。
下面是一段用桶排序对二元组基数排序的程序:
ViewCodeCPP
1
2
3
4
5
6
7
8
*
9
10
11
12
13
14
15
16
17
18
·
19
20
21
22
23
24
25
26
27
28
[
29
30
31
32
33
34
35
36
37
38
《
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/*Memo:
基数排序结构数组*/
#include
#include
#include
#include
#include
usingnamespacestd;
structdata
{
intkey[2];
};
structlinklist
{
linklist*next;
datavalue;
linklist(datav,linklist*n):
value(v),next(n){}
~linklist(){if(next)deletenext;}
};
voidBucketSort(data*A,intN,intK,inty)
{
linklist*Bucket[101],*p;ey[y]/M;ey[0]=rand()%K+1;
A[i].key[1]=rand()%K+1;
}
RadixSort(A,N,K);
for(i=1;i<=N;i++)
printf("(%d,%d)",A[i].key[0],A[i].key[1]);
printf("/n");
return0;
}
基数排序是一种用在老式穿卡机上的算法。
一张卡片有80列,每列可在12个位置中的任一处穿孔。
排序器可被机械地”程序化”以检查每一迭卡片中的某一列,再根据穿孔的位置将它们分放12个盒子里。
这样,操作员就可逐个地把它们收集起来。
其中第一个位置穿孔的放在最上面,第二个位置穿孔的其次,等等。
对于一个位数有限的十进制数,我们可以把它看作一个多元组,从高位到低位关键字重要程度依次递减。
可以使用基数排序对一些位数有限的十进制数排序。
[三种线性排序算法的比较]
从整体上来说,计数排序,桶排序都是非基于比较的排序算法,而其时间复杂度依赖于数据的范围,桶排序还依赖于空间的开销和数据的分布。
而基数排序是一种对多元组排序的有效方法,具体实现要用到计数排序或桶排序。
相对于快速排序、堆排序等基于比较的排序算法,计数排序、桶排序和基数排序限制较多,不如快速排序、堆排序等算法灵活性好。
但反过来讲,这三种线性排序算法之所以能够达到线性时间,是因为充分利用了待排序数据的特性,如果生硬得使用快速排序、堆排序等算法,就相当于浪费了这些特性,因而达不到更高的效率。
在实际应用中,基数排序可以用于后缀数组的倍增算法,使时间复杂度从O(N*logN*logN)降到O(N*logN)。
线性排序算法使用最重要的是,充分利用数