数据结构课后习题答案清华大学出版社殷人昆

更新时间:2023-08-27 01:44:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

.

1-1什么是数据? 它与信息是什么关系?

【解答】

什么是信息?广义地讲,信息就是消息。宇宙三要素(物质、能量、信息)之一。它是现实世界各种事物在人们头脑中的反映。此外,人们通过科学仪器能够认识到的也是信息。信息的特征为:可识别、可存储、可变换、可处理、可传递、可再生、可压缩、可利用、可共享。

什么是数据?因为信息的表现形式十分广泛,许多信息在计算机中不方便存储和处理,例如,一个大楼中4部电梯在软件控制下调度和运行的状态、一个商店中商品的在库明细表等,必须将它们转换成数据才能很方便地在计算机中存储、处理、变换。因此,数据(data)是信息的载体,是描述客观事物的数、字符、以及所有能输入到计算机中并被计算机程序识别和处理的符号的集合。在计算机中,信息必须以数据的形式出现。

1-2什么是数据结构? 有关数据结构的讨论涉及哪三个方面?

【解答】

数据结构是指数据以及相互之间的关系。记为:数据结构= { D, R }。其中,D是某一数据对象,R是该对象中所有数据成员之间的关系的有限集合。

有关数据结构的讨论一般涉及以下三方面的内容:

①数据成员以及它们相互之间的逻辑关系,也称为数据的逻辑结构,简称为数据结构;

②数据成员极其关系在计算机存储器内的存储表示,也称为数据的物理结构,简称为存储结构;

③施加于该数据结构上的操作。

数据的逻辑结构是从逻辑关系上描述数据,它与数据的存储不是一码事,是与计算机存储无关的。因此,数据的逻辑结构可以看作是从具体问题中抽象出来的数据模型,是数据的应用视图。数据的存储结构是逻辑数据结构在计算机存储器中的实现(亦称为映像),它是依赖于计算机的,是数据的物理视图。数据的操作是定义于数据逻辑结构上的一组运算,每种数据结构都有一个运算的集合。例如搜索、插入、删除、更新、排序等。

1-3数据的逻辑结构分为线性结构和非线性结构两大类。线性结构包括数组、链表、栈、队列、优先级队列等; 非线性结构包括树、图等、这两类结构各自的特点是什么?

【解答】

线性结构的特点是:在结构中所有数据成员都处于一个序列中,有且仅有一个开始成员和一个终端成员,并且所有数据成员都最多有一个直接前驱和一个直接后继。例如,一维数组、线性表等就是典型的线性结构

非线性结构的特点是:一个数据成员可能有零个、一个或多个直接前驱和直接后继。例如,树、图或网络等都是典型的非线性结构。

1-4.什么是抽象数据类型?试用C++的类声明定义“复数”的抽象数据类型。要求

(1) 在复数内部用浮点数定义它的实部和虚部。

(2) 实现3个构造函数:缺省的构造函数没有参数;第二个构造函数将双精度浮点数赋给复数的实部,虚部置为0;第三个构造函数将两个双精度浮点数分别赋给复数的实部和虚部。

(3) 定义获取和修改复数的实部和虚部,以及+、-、*、/等运算的成员函数。

(4) 定义重载的流函数来输出一个复数。

【解答】

.

抽象数据类型通常是指由用户定义,用以表示应用问题的数据模型。抽象数据类型由基本的数据类型构成,并包括一组相关的服务。

//在头文件complex.h中定义的复数类

#ifndef _complex_h_

#define _complex_h_

#include <iostream.h>

class comlex {

public:

complex ( ){ Re = Im = 0; }//不带参数的构造函数

complex ( double r ) { Re = r;Im = 0; }//只置实部的构造函数

complex ( double r, double i ) { Re = r;Im = i; }//分别置实部、虚部的构造函数

double getReal ( ) { return Re; }//取复数实部

double getImag ( ) { return Im; }//取复数虚部

void setReal ( double r ) { Re = r; }//修改复数实部

void setImag ( double i ) { Im = i; } //修改复数虚部

complex& operator = ( complex& ob) { Re = ob.Re;Im = ob.Im; }//复数赋值

complex& operator + ( complex& ob );//重载函数:复数四则运算

complex& operator– ( complex& ob );

complex& operator * ( complex& ob );

complex& operator / ( complex& ob );

friend ostream& operator << ( ostream& os, complex& c );//友元函数:重载<<

private:

double Re, Im; //复数的实部与虚部

};

#endif

//复数类complex的相关服务的实现放在C++源文件complex.cpp中

#include <iostream.h>

#include <math.h>

#include“complex.h”

complex& complex :: operator + ( complex & ob ) {

//重载函数:复数加法运算。

complex * result = new complex ( Re + ob.Re, Im + ob.Im );

return *result;

}

complex& complex :: operator– ( complex& ob ) {

//重载函数:复数减法运算

complex * result = new complex ( Re – ob.Re, Im – ob.Im );

return * result;

}

complex& complex :: operator * ( complex& ob ) {

//重载函数:复数乘法运算

.

1n ,21)n(n i n

1i ≥+=∑=complex * result =

new complex ( Re * ob.Re – Im * ob.Im, Im * ob.Re + Re * ob.Im );

return *result ;

}

complex & complex :: operator / ( complex & ) {

//重载函数:复数除法运算

double d = ob.Re * ob.Re + ob.Im * ob.Im ;

complex * result = new complex ( ( Re * ob.Re + Im * ob.Im ) / d,

( Im * ob. Re – Re * ob.Im ) / d ); return * result ; }

friend ostream& operator << ( ostream& os, complex & ob ) {

//友元函数:重载<<,将复数ob 输出到输出流对象os 中。

return os << ob.Re << ( ob.Im >= 0.0 ) ? “+” : “-” << fabs ( ob.Im ) << “i”;

} 1-5 用归纳法证明:

(1)

(2) 1n ,61)1)(2n n(n i n 1i 2≥++=∑= (3) 0n 1, x ,1x 1x x 1n n 0i i

≥≠--=+=∑ 【证明】略

1-6 什么是算法? 算法的5个特性是什么? 试根据这些特性解释算法与程序的区别。

【解答】

通常,定义算法为“为解决某一特定任务而规定的一个指令序列。”一个算法应当具有以下特性:

① 有输入。一个算法必须有0个或多个输入。它们是算法开始运算前给予算法的量。这些输入取自于特定的对象的集合。它们可以使用输入语句由外部提供,也可以使用赋值语句在算法内给定。

② 有输出。一个算法应有一个或多个输出,输出的量是算法计算的结果。

③ 确定性。算法的每一步都应确切地、无歧义地定义。对于每一种情况,需要执行的动作都应严格地、清晰地规定。

④ 有穷性。一个算法无论在什么情况下都应在执行有穷步后结束。

⑤ 有效性。算法中每一条运算都必须是足够基本的。就是说,它们原则上都能精确地执行,甚至人们仅用笔和纸做有限次运算就能完成。

算法和程序不同,程序可以不满足上述的特性(4)。例如,一个操作系统在用户未使用前一直处于“等待”的循环中,直到出现新的用户事件为止。这样的系统可以无休止地运行,直到系统停工。

此外,算法是面向功能的,通常用面向过程的方式描述;程序可以用面向对象方式搭建它的框架。

.

∑∑∑====n 1i n 1j 3

n 1

k n 162)1)(n n(n 21)n(n 2161)1)(2n n(n 21 i 21i 2121)i(i j 1n 1i n

1

i n 1i 2n 1i i 1j n 1i i 1j j 1k ++=++++==+=??? ??+==∑∑

∑∑∑∑∑∑========??? ??++=++??? ??++=+??? ??++==++=+==∑∑==21)n(n 2121)n(n 21)n(n 1j 21)n(n 1i 时, 2i 21)n(n 1j 1i 时 1i n 1j n 1j ,??? ??++=+???? ?

???? ??++==∑=21)n(n 31j 21)n(n 21i 时, 3i n 1j 1-7 设n 为正整数, 分析下列各程序段中加下划线的语句的程序步数。

(1) for (int i = 1; i <= n ; i++) (2) x = 0; y = 0;

for (int j = 1; j <= n ; j++) { for (int i = 1; i <= n ; i++) c[i][j] = 0.0; for (int j = 1; j <= i ; j++)

for (int k = 1; k <= n ; k++) for (int k = 1; k <= j ; k++)

c[i][j] = c[i][j] + a[i][k] * b[k][j]; x = x + y ;

}

(3) int i = 1, j = 1; (4) int i =1;

while (i<=n && j<=n) { do {

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

} i = i + j ;

} while ( i < 100 + n );

【解答】

(1) (2)

(3) i = 1时,i = 2,j = j + i = 1 + 2 = 2 + 1,

i = 2时,i = 3,j = j + i = ( 2 + 1 ) + 3 = 3 + 1 + 2,

i = 3时,i = 4,j = j + i = ( 3 + 1 + 2 ) + 4 = 4 + 1 + 2 + 3,

i = 4时,i = 5,j = j + i = ( 4 + 1 + 2 + 3 ) + 5 = 5 + 1 + 2 + 3 + 4,

……

i = k 时,i = k + 1,j = j + i = ( k + 1 ) + ( 1 + 2 + 3 + 4 + … + k ), 解出满足上述不等式的k 值,即为语句i = i + 1的程序步数。

(4)

一般地, 求出满足此不等式的k 值,即为语句i = i + j 的程序步数。 ()()()n 2

33k k 21k k 1k n

i 1k j 2k

1i ≤++=+++∴≤++=∑= n 10021)n(n k 1i ,时k i +<??

? ??++==

.

1-8 试编写一个函数计算n!*2n的值,结果存放于数组A[arraySize]的第n个数组元素中,0 ≤n ≤ arraySize。若设计算机中允许的整数的最大值为maxInt,则当n > arraySize或者对于某一个k (0 ≤ k ≤ n),使得k!*2k > maxInt时,应按出错处理。可有如下三种不同的出错处理方式:

(1) 用cerr<<及exit (1)语句来终止执行并报告错误;

(2) 用返回整数函数值0, 1来实现算法,以区别是正常返回还是错误返回;

(3) 在函数的参数表设置一个引用型的整型变量来区别是正常返回还是某种错误返回。

试讨论这三种方法各自的优缺点,并以你认为是最好的方式实现它。

【解答】

#include "iostream.h"

#define arraySize 100

#define MaxInt 0x7fffffff

int calc ( int T[ ], int n ) {

int i, value = 1;

if ( n != 0 ) {

int edge = MaxInt / n / 2;

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

value *= i*2;

if ( value > edge ) return 0;

}

value *= n * 2;

}

T[n] = value;

cout << "A[" << n << "]=" << T[n] << endl;

return 1;

}

void main ( ) {

int A[arraySize];

int i;

for ( i = 0; i < arraySize; i++ )

if ( !calc ( A, i ) ) {

cout << "failed at " << i << " ." << endl;

break;

}

}

1-9 (1) 在下面所给函数的适当地方插入计算count的语句:

void d (ArrayElement x[ ], int n ) {

int i = 1;

do {

x[i] += 2; i +=2;

.

} while (i <= n );

i = 1;

while ( i <= (n/2) ) {

x[i] += x[i+1];i++;

}

}

(2) 将由(1)所得到的程序化简。使得化简后的程序与化简前的程序具有相同的count值。

(3) 程序执行结束时的count值是多少?

(4) 使用执行频度的方法计算这个程序的程序步数,画出程序步数统计表。

【解答】

(1) 在适当的地方插入计算count语句

void d ( ArrayElement x [ ], int n ) {

int i = 1;

count ++;

do {

x[i] += 2; count ++;

i += 2;count ++;

count ++;//针对while语句

} while ( i <= n );

i = 1;

count ++;

while ( i <= ( n / 2 ) ) {

count ++;//针对while语句

x[i] += x[i+1];

count ++;

i ++;

count ++;

}

count ++; //针对最后一次while语句

}

(2) 将由(1)所得到的程序化简。化简后的程序与原来的程序有相同的count值:

void d ( ArrayElement x [ ], int n ) {

int i = 1;

do {

count += 3;i += 2;

} while ( i <= n );

i = 1;

while ( i <= ( n / 2 ) ) {

count += 3;i ++;

}

count += 3;

}

(3) 程序执行结束后的count值为3n + 3。

当n为偶数时,count = 3 * ( n / 2 ) + 3 * ( n / 2 ) + 3 = 3 * n + 3

.

当n为奇数时,count = 3 * ( ( n + 1 ) / 2 ) + 3 * ( ( n – 1 ) / 2 ) + 3 = 3 * n + 3

(4) 使用执行频度的方法计算程序的执行步数,画出程序步数统计表:

1-10 设有3个值大小不同的整数a、b和c,试求

(1)其中值最大的整数;

(2)其中值最小的整数;

(3)其中位于中间值的整数。

【解答】

(1) 求3个整数中的最大整数的函数

【方案1】

int max ( int a, int b, int c ) {

int m = a;

if ( b > m ) m = b;

if ( c > m ) m = c;

return m;

}

【方案2】(此程序可修改循环终止变量扩大到n个整数)

int max ( int a, int b, int c ) {

int data[3] = { a, b, c };

int m = 0;//开始时假定data[0]最大

for ( int i = 1; i < 3; i++ ) //与其他整数逐个比较

if ( data[i] > data[m] ) m = i;//m记录新的最大者

return data[m];

}

(2)求3个整数中的最小整数的函数

可将上面求最大整数的函数稍做修改,“>”改为“<”,可得求最小整数函数。

【方案1】

int min ( int a, int b, int c ) {

int m = a;

if ( b < m ) m = b;

.

if ( c < m ) m = c;

return m;

}

【方案2】(此程序可修改循环终止变量扩大到n个整数)

int max ( int a, int b, int c ) {

int data[3] = { a, b, c };

int m = 0;//开始时假定data[0]最小

for ( int i = 1; i < 3; i++ ) //与其他整数逐个比较

if ( data[i] < data[m] ) m = i;//m记录新的最小者

return data[m];

}

(3)求3个整数中具有中间值的整数

可将上面求最大整数的函数稍做修改,“>”改为“<”,可得求最小整数函数。

【方案1】

int mid ( int a, int b, int c ) {

int m1 = a, m2;

if ( b < m1 ) { m2 = m1;m1 = b; }

else m2 = b;

if ( c < m1 ) { m2 = m1;m1 = c; }

else if ( c < m2 ) { m2 = c; }

return m2;

}

【方案2】(此程序可修改循环终止变量扩大到n个整数寻求次小元素)

int mid ( int a, int b, int c ) {

int data[3] = { a, b, c };

int m1 = 0, m2 = -1; //m1指示最小整数, m2指示次小整数

for ( int i = 1; i < 3; i++ ) //与其他整数逐个比较

if ( data[i] < data[m1] ) { m2 = m1; m1 = i; }//原来最小变为次小, m1指示新的最小

else if ( m2 == -1 || data[i] < data[m2] ) m2 = i;//m2记录新的次小者

return data[m2];

}

第10章索引与散列

10-1 什么是静态索引结构?什么是动态索引结构?它们各有哪些优缺点?

【解答】

静态索引结构指这种索引结构在初始创建,数据装入时就已经定型,而且在整个系统运行期间,树的结构不发生变化,只是数据在更新。动态索引结构是指在整个系统运行期间,树的结构随数据的增删及时调整,以保持最佳的搜索效率。静态索引结构的优点是结构定型,建立方法简单,存取方便;缺点是不利于更新,插入或删除时效率低。动态索引结构的优点是在插入或删除时能够自动调整索引树结构,以保持最佳的搜索效率;缺点是实现算法复杂。

10-2 设有10000个记录对象, 通过分块划分为若干子表并建立索引, 那么为了提高搜索效率, 每一个子表的大小应设计为多大?

.

【解答】

每个子表的大小 s = ?n ? = ?10000? = 100 个记录对象。

10-3如果一个磁盘页块大小为1024 (=1K) 字节,存储的每个记录对象需要占用16字节,其中关键码占4字节,其它数据占12字节。所有记录均已按关键码有序地存储在磁盘文件中,每个页块的第1个记录用于存放线性索引。另外在内存中开辟了256K 字节的空间可用于存放线性索引。试问:

(1) 若将线性索引常驻内存,文件中最多可以存放多少个记录?(每个索引项8字节,其中关键码4字节,地址4字节)

(2) 如果使用二级索引,第二级索引占用1024字节(有128个索引项),这时文件中最多可以存放多少个记录?

【解答】

(1) 因为一个磁盘页块大小为1024字节,每个记录对象需要占用16字节,则每个页块可存放1024 / 16 = 64个记录,除第一个记录存储线性索引外,每个页块可存储63个记录对象。又因为在磁盘文件中所有记录对象按关键码有序存储,所以线性索引可以是稀疏索引,每一个索引项存放一个页块的最大关键码及该页块的地址。若线性索引常驻内存,那么它最多可存放256 * (1024 / 8 ) = 256 * 128 = 32768个索引项,文件中可存放 32768 * 63 = 2064384个记录对象。

(2) 由于第二级索引占用1024个字节,内存中还剩255K 字节用于第一级索引。第一级索引有255 * 128 = 32640个索引项,作为稀疏索引,每个索引项索引一个页块,则索引文件中可存放32640 * 63 = 2056320。

10-4 假设在数据库文件中的每一个记录是由占2个字节的整型数关键码和一个变长的数据字段组成。数据字段都是字符串。为了存放右面的那些记录,应如何组织线性索引? 【解答】 将所有字符串依加入的先后次序存放于一个连续的存储空间store 中,这个空间也叫做“堆”,它是存放所有

字符串的顺序文件。它有一个指针free ,指示在堆store 中当前可存放数据的开始地址。初始时free 置为0,表示可从文件的0号位置开始存放。线性索引中每个索引项给出记录关键码,字符串在store 中的起始地址和字符串的长度:

0 10-5 记录地址

.

10032

10068

10104

10140

10176

10212

10248

10284

10320

其中,

索结果关键码。(1) 男性职工;(2) 月工资超过800元的职工;(3) 月工资超过平均工资的职工;(4) 职业为实验员和行政秘书的男性职工;(5) 男性教师或者年龄超过25岁且职业为实验员和教师的女性职工。

【解答】

(1) 男性职工(搜索性别倒排索引):{034, 073, 081, 092, 123}

(2) 月工资超过800元的职工(搜索月工资倒排索引):{064, 081}

(3) 月工资超过平均工资的职工(搜索月工资倒排索引) {月平均工资776元}:

{064, 081, 140}

(4) 职业为实验员和行政秘书的男性职工(搜索职务和性别倒排索引):

{073, 123, 175}&&{034, 073, 081, 092, 123} = {073, 123}

本文来源:https://www.bwwdw.com/article/iqmi.html

Top