数据结构(C语言)第二版 第三章课后答案

数据结构(C语言)第二版 第三章课后答案

1~5 C C D A A
6~10 D A B C D
11~15 D D B C B

1.选择题

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

栈是后进先出的线性表,所以可以还原其入栈顺序
栈底–>—>----->------>---->—>栈顶
A. 1->2->3->4->5 满足依次入栈顺序
B. 1->2 然后 2 出栈 1 出栈 ->3->4->5 满足依次入栈顺序
C. 2->1->3->4 依次再出栈 ->5 不满足题意
D. 1->2 2 出栈 ->3 3 出栈->4->5 满足依次入栈顺序

(2)若已知一个栈的入栈序列是1,2 ,3,…, n,其输出序列为p1, p2,p3,…, pn,若p1=n ,则pi 为(C) 。
A. i B. n-i C. n-i+1 D. 不确定

栈是后进先出的线性表
一个栈的入栈序列是1, 2, 3,… , n ,且输出序列的第一个元素为n ,说明1 ,2,3 ,, , n 一次性全部进栈, 再进行出栈
所以p1=n ,p2=n-1 ,…,pi=n-i+1 。

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

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

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

先储存栈顶的data,再将栈顶指向下一个结点
x=top->data;top=top->link;

(5)设有一个递归算法如下:
int fact ( int n )
{ //n 大于等于0
if(n<=0) return 1;
else return n*fact(n-1); }
则计算fact(n) 需要调用该函数的次数为(A)。

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

观察fact函数可以发现这个函数是用来计算n的阶乘,即为n*(n-1)…*1
所以将执行n+1次
特殊值法。n = 0 ,仅调用一次fact(n) 函数,所以执行n+1次

(6)栈在(D)中有所应用。
A.递归调用 B.函数调用
C.表达式求值 D.前三个选项都有

这是看这些应用中是否使用了栈的后进先出性质
递归调用、函数调用、表达式求值均可以使用栈的后进先出性质。

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

解决缓冲区问题应用的是一种先进先出的缓冲机制,
这些选项中只有队列是能够体现先进先出的思想。

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

求栈S 的容量至少应该为多少,就是找出栈S中同时最多存在了几个元素
一个元素出栈后即进入队列Q,栈是后进先出,队列是先进先出,所以出队顺序就是入队顺序,也是出栈顺序。
元素出队的序列是e2、e4、e3、e6、e5 和e1 ,可知元素入队的序列是e2、e4、e3、e6、e5 和e1
所以元素出栈的序列也是e2、e4、e3、e6、e5 和e1,而元素e1、e2、e3、e4、e5 和e6 依次进入栈
具体过程:
e1、e2 入栈( e2 出栈) e3、e4 入栈 (e4出栈、e3出栈)e5、e6 入栈(e6 出栈、e5出栈、e1出栈)
栈S 中最多同时存在3 个元素,所以栈S 的容量至少为3。

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

top 初始值为 n+1 ,且元素存储在向量空间V[1…n] 中,所以元素储存位置的下标是由 n–>1
进栈时 top 指针先下移,之后再将元素x 存储在V[top] 。

(10)设计一个判别表达式中左,右括号是否配对出现的算法,采用(D)数据结构最佳。
A.线性表的顺序存储结构 B.队列
C.线性表的链式存储结构 D.栈

利用栈的后进先出原则。
先将所有的左括号储存起来,遇见右括号时匹配栈顶元素,如果匹配,做出栈操作,不匹配结束或栈顶元素出栈继续匹配。

(11)用链接方式存储的队列,在进行删除运算时(D)。
A. 仅修改头指针 B.仅修改尾指针
C. 头、尾指针都要修改 D.头、尾指针可能都要修改

进行删除时,也会使用队列的先进先出性质。
所以一般情况下只修改头指针,但是,当删除的是队列中最后一个元素时,队尾指针也丢失了,因此需对队尾指针重新赋值,让其指向头结点。

(12)循环队列存储在数组A[0…m] 中,则入队时的操作为(D)。
A.rear=rear+1 B.rear=(rear+1)%(m-1)
C.rear=(rear+1)%m D.rear=(rear+1)%(m+1)

入队时,rear先+1再对数组长度取余。
数组A[0…m] 中共含有 m+1 个元素,故在求模运算时应为 %(m+1)。

(13)最大容量为n 的循环队列, 队尾指针是rear ,队头是front ,则队空的条件是(B)。
A. (rear+1)%nfront B. rearfront
C. rear+1front D. (rear-l)%nfront

最大容量为 n 的循环队列, 队满条件是(rear+1)%nfront , 队空条件是rearfront 。

(14)栈和队列的共同点是(C)。
A. 都是先进先出 B. 都是先进后出
C. 只允许在端点处插入和删除元素 D. 没有共同点

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

(15)一个递归算法必须包括(B)。
A. 递归部分B. 终止条件和递归部分
C. 迭代部分 D. 终止条件和迭代部分

递归算法是函数内部继续调用这个函数,所以它必须要有终止条件,而另一部分是它的递归部分

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);break;
	}
} 

(3) 出栈操作
ElemType pop(stk S,int i){			//i 代表栈号, i=0 时为左栈, i=1 时为右栈。出栈成功时返回退栈元素,否则返回-1
	if(i<0 || i>1){cout<< "栈号输入错误"<<endl;exit(0);}
	switch(i){
		case 0:
			if(S.top[0]==-1) {cout<< "栈空"<<endl;return (-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]++]);
	} 
} 

(4) 判断栈空
int Empty(){
	return (S.top[0]==-1 && S.top[1]==m);
}

(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);			//构造一个空栈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 100		//栈空间容量
void InOutS(int s[maxsize]){			//s是元素为整数的栈
	int top=0; //top 为栈顶指针,定义top=0 时为栈空。
	for(i=1; i<=n; i++) //n 个整数序列作处理。
	{
		cin>>x; 		// 从键盘读入整数序列。
		// 读入的整数不等于-1 时入栈。
		if(x!=-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(x){
			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;
						}
					}
				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: // 其它符号不作处理。
		}
		cin>>x;					// 读入表达式中下一个字符。
	}
	cout<<"后缀表达式的值为" <<pop(OPND);
}

(5)假设以I 和O 分别表示入栈和出栈操作。栈的初态和终态均为空,入栈和出栈的操作序列可表示为仅由I 和O 组成的序列, 称可以操作的序列为合法序列, 否则称为非法序列。
①下面所示的序列中哪些是合法的?
A. IOIIOIOO B. IOOIOIIO C. IIIOIOIO D. IIIOOIOO
②通过对①的分析, 写出一个算法, 判定所给的操作序列是否合法。若合法, 返回true ,否则返回false (假定被判定的操作序列已存入一维数组中)。

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

[ 算法描述]
① 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]!= ' O') {				// 当未到字符数组尾就作。
		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);}
}

(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;
	}
}
(2) 判队空
int EmptyQueue( LinkQueue *Q){ 			// 判队空。当头结点的next 指针指向自己时为空队
	return Q->rear->next->next==Q->rear->next;
}
(3) 入队
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;					// 将尾指针移至新结点
}
(4) 出队
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 )算法。
[ 算法描述]

(1) 初始化
SeQueue QueueInit(SeQueue Q){			// 初始化队列
	Q.front=Q.rear=0; Q.tag=0;
	return Q;
}
(2) 入队
SeQueue QueueIn(SeQueue Q,int e){		// 入队列
	if((Q.tag==1) && (Q.rear==Q.front)) cout<<" 队列已满"<<endl;
	else{
		Q.rear=(Q.rear+1) % m;
		Q.data[Q.rear]=e;
		if(Q.tag==0) Q.tag=1; 		// 队列已不空
	}
	return Q;
}
(3) 出队
ElemType QueueOut(SeQueue Q){		// 出队列
	if(Q.tag==0) { cout<<" 队列为空"<<endl; exit(0);}
	else{
		Q.front=(Q.front+1) % m;
		e=Q.data[Q.front];
		if(Q.front==Q.rear) Q.tag=0; // 空队列
	}
	return(e);
}

(8)如果允许在循环队列的两端都可以进行插入和删除操作。要求:
① 写出循环队列的类型定义;
② 写出“从队尾删除”和“从队头插入”的算法。

[ 题目分析]
用一维数组 v[0…M-1] 实现循环队列,其中M 是队列长度。设队头指针front 和队尾指针rear ,约定front 指向队头元素的前一位置, rear 指向队尾元素。定义front=rear 时为队空, (rear+1)%m=front 为队满。约定队头端入队向下标小的方向发展,队尾端入队向下标大的方向发展。

[ 算法描述]

#define M 100		//队列可能达到的最大长度
typedef struct{
	ElemType data[M];
	int front,rear;
}cycqueue;
ElemType delqueue(cycqueue Q){
	if (Q.front==Q.rear) {cout<<" 队列空"<<endl; exit(0);}
	Q.rear=(Q.rear-1+M)%M; 			// 修改队尾指针。
	return(Q.data[(Q.rear+1+M)%M]); 	// 返回出队元素。
}
void enqueue(cycqueue Q, ElemType x){
	if(Q.rear==(Q.front-1+M)%M) {cout<<" 队满"<<endl; exit(0);)
	Q.data[Q.front]=x; 			//x 入队列
	Q.front=(Q.front-1+M)%M; 		// 修改队头指针。
}

(9)已知Ackermann 函数定义如下:
在这里插入图片描述
① 写出计算Ack(m,n) 的递归算法,并根据此算法给出出Ack(2,1) 的计算过程。
② 写出计算Ack(m,n) 的非递归算法。

[ 算法描述]

int Ack(int m,n){
	if (m==0) return(n+1);
	else if(m!=0&&n==0) return(Ack(m-1,1));
	else return(Ack(m-1,Ack(m,m-1));
}Ack(2,1) 的计算过程
Ack(2,1)=Ack(1,Ack(2,0)) 		// 因m<>0,n<>0 而得
=Ack(1,Ack(1,1))				 // 因m<>0,n=0 而得
=Ack(1,Ack(0,Ack(1,0)))		 // 因m<>0,n<>0 而得
=Ack(1,Ack(0,Ack(0,1))) 		// 因m<>0,n=0 而得
=Ack(1,Ack(0,2)) 				// 因m=0 而得
=Ack(1,3) 					// 因m=0 而得
=Ack(0,Ack(1,2)) 			// 因m<>0,n<>0 而得
= Ack(0,Ack(0,Ack(1,1))) 		// 因m<>0,n<>0 而得
= Ack(0,Ack(0,Ack(0,Ack(1,0))))		 // 因m<>0,n<>0 而得
= Ack(0,Ack(0,Ack(0,Ack(0,1)))) 		// 因m<>0,n=0 而得
= Ack(0,Ack(0,Ack(0,2)))		 // 因m=0 而得
= Ack(0,Ack(0,3))				 // 因m=0 而得
= Ack(0,4) 					// 因n=0 而得
=5 							// 因n=0 而得int Ackerman(int m, int n){
	int akm[M][N];int i,j;
	for(j=0;j<N;j++) akm[0][j]=j+1;
	for(i=1;i<m;i++){
		akm[i][0]=akm[i-1][1];
		for(j=1;j<N;j++)akm[i][j]=akm[i-1][akm[i][j-1]];
	}
	return(akm[m][n]);
}

(10)已知f 为单链表的表头指针, 链表中存储的都是整型数据,试写出实现下列运算的递归算法:
①求链表中的最大整数;
②求链表的结点个数;
③求所有整数的平均值。

[算法描述]

//求链表中的最大整数
int GetMax(LinkList p){
	if(!p->next) return p->data;
	else{
		int max=GetMax(p->next);
		return p->data>=max ? p->data:max;
	}
}
//求链表的结点个数
int GetLength(LinkList p){
	if(!p->next)  return 1;
	else { return GetLength(p->next)+1;}
}
//求所有整数的平均值
double GetAverage(LinkList p , int n){
	if(!p->next)  return p->data;
	else {
	double ave=GetAverage(p->next,n-1);
	return (ave*(n-1)+p->data)/n;
	}
}
  • 50
    点赞
  • 228
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值