中国矿业大学2023级高级语言程序设计C++实验5题解

1.【实验5-1】缺失的数字

一个整数集合中含有n个数字,每个数字都在0~n之间。假设0~n的n+1个数字中有且仅有一个数字不在该集合中,请找出这个数字。

【输入格式】

第一行输入一个数字s,表示集合中数字的数量

第二行输入s个数字,以空格隔开。

1 <= s <= 1000

【输出格式】

输出缺失的数字

【输入样例】

4

0 1 2 4

【输出样例】

3

思路概要:创建一个数组,将数字存入数组中,升序排序后,依次查找找出缺少数字即可

易错点:若缺失数字在数组的最后一位,则需要特判,将其输出,否则无法查找出来!

时间复杂度:O(n)

代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
int main(){
	int n,num[1005];
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>num[i];
	sort(num,num+n);
	int cnt=0,flag=1;
	for(int i=0;i<n;i++){
		if(num[i]!=cnt){
			cout<<cnt;
			flag=0;
			break;
		}
		cnt++;
	}
	if(flag) cout<<n;
	return 0;
}

备注:本题作者较懒(bushi),所以使用了algorithm库的sort函数,读者最好自己写排序算法,以锻炼编码能力哦。

2.【实验5-2】查找最长单词

在进行文章重复度检查时,经常需要统计一段英文中的单词数量,并找出长度最长的单词。

设有如下定义:char str[500];

编写程序,通过利用cin.getline(str,500);实现从键盘输入一小段英文(其中可以包含空格,但在同一行),利用函数统计该段英文中包含几个单词,输出统计出的单词数量、最长单词的长度以及长度最长的单词,空格隔开。

注意:函数声明使用void split(char *str);如果有最长的单词不只一个,输出最先找到的那个。

【输入形式】一小段英文,不要超过500个字符

【输出形式】单词数量、最长单词的长度以及长度最长的单词,空格隔开。

【输入样例】welcome to china university of mining and technology

【输出样例】8 10 university

思路概要:用空格代替单词进行计数,并且定义cnt变量,记录单词的长度,用打擂台的方法找出最长单词的长度和初位置,即可完成本题。

易错点:这里也不能叫易错点吧,只是一个小技巧,我们可以在原句后加一个空格,则进行函数调用时候,不需要特判就可以计量最后一个单词的长度!

时间复杂度:O(n)

代码如下:

#include<iostream>
#include<string.h>
using namespace std;
void solveProblem(char str[]){//可以分为两个函数,但是需要遍历2次,所以这里遍历一次以缩短时间
	strcat(str," ");//在字符串后加一个空格,方便下列程序的操作
	int len=strlen(str);
	int num=0,cnt=0,pos,max_cnt=0;
	for(int i=0;i<len;i++){
		if(str[i]==' ') {
			num++;
			if(cnt>max_cnt){
				max_cnt=cnt;
				pos=i-max_cnt;//找出最大单词的位置
			}
			cnt=0;
		}
		else if((str[i]>='a'&&str[i]<='z')||(str[i]>='A'&&str[i]<='Z')){
			cnt++;
		}
	}
	cout<<num<<" "<<max_cnt<<" ";
	for(int i=pos;i<pos+max_cnt;i++){
		cout<<str[i];
	}//最后输出最长字符串 
}
int main(){
	char str[500];
	cin.getline(str,500);
	solveProblem(str);
	return 0;
}

备注:读者也可以将本题函数分为两个函数,分别进行处理,这样可以使得程序更加模块化,但是会增加一次遍历次数(不过本题的数据也不大咧)。

3.【实验5-3】到底买不买

小红想买些珠子做一串自己喜欢的珠串。卖珠子的摊主有很多串五颜六色的珠串,但是不肯把任何一串拆散了卖。小红要你帮忙判断一下,某串珠子里是否包含了全部自己想要的珠子?如果是,那么告诉她有多少多余的珠子。

为方便起见,用[0-9]、[a-z]、[A-Z]范围内的字符来表示颜色。如下图中,第3串是小红想做的珠串;那么第1串可以买,因为包含了她全部想要的珠子,还多了8颗不需要的珠子;第2串不能买,因为没有黑色珠子,并且少了一颗红色的珠子。

【输入形式】每次输入包含两个字符串,分别在2行中先后给出摊主的珠串和小红想做的珠串,两串都不超过500个珠子。

【输出形式】如果可以买,则在一行中输出“Yes”以及有多少颗多余的珠子,其间以1个空格分隔;如果不可以买,则在一行中输出“No”以及缺少多少颗珠子,其间以1个空格分隔;。

【输入样例1】

ppRYYGrrYBR2258

YrR8RrY

【输出样例1】

Yes 8

【输入样例2】

ppRYYGrrYB225

YrR8RrY

【输出样例2】

No 2

思路概要:创建一个数组,遍历两条珠串,第一条珠串对应的珠子的ASCII码的数组元素加1,第二条珠串对应的珠子的ASCII码的数组元素减1,例如第一条珠串”ABC”,则s[‘A’]+=1,s[‘B’]+=1…以此类推,最后判断:如果有负数的存在,则”不可以买“,负数之和的绝对值即为缺少珠子数目。若没有负数,则”可以买“,正数之和即为多余珠子数目。

易错点:最后的查找过程可以遍历整个ASCII码表,当然读者可以采用其他更加快速的方式,但这种方式是比较简单易懂的。

时间复杂度:O(n)

代码如下:

#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
int main(){
	int str[200]={0};
	int extra=0,lack=0;
	bool flag=1;
	char S1[505],S2[505];
	cin.getline(S1,505);
	int len1=strlen(S1);
	for(int i=0;i<len1;i++){
		str[int(S1[i])]++;//利用强转,将char转为int类型
	}
	cin.getline(S2,505);
	int len2=strlen(S2);
	for(int i=0;i<len2;i++){
		str[int(S2[i])]--;
	}
	for(int i=0;i<200;i++){
		if(str[i]<0){
			flag=0;
			lack-=str[i];
		}
		if(str[i]>0){
			extra+=str[i];
		}
	}
	if(flag) cout<<"Yes"<<" "<<extra;
	else cout<<"No"<<" "<<lack;
	return 0;
}

4.【实验5-4】逆波兰记法

逆波兰表示法(Reverse Polish Notation,RPN,或逆波兰记法),是一种是由波兰逻辑学家 J. Lukasiewicz于1929年提出的数学表达式的方法。在逆波兰记法中,所有操作符置于操作数的后面,因此也被称为后缀表示法。逆波兰记法不需要括号来标识操作符的优先级。

例如:1 + 2 * (3 – 4) – 5 * 6的后缀表达式为1 2 3 4 – * + 5 6 * –

假设表达式中只包含变量(变量名只由小写字母构成)和加、减、乘、除四种运算,并且乘、除运算符的优先级高于加、减,请设计函数 

void transfer(char* source, char* target),实现将表达式转换成逆波兰记法。

【输入格式】

一个表达式,仅包含变量、加减乘除四种运算符,并且变量名只由小写字母构成

【输出格式】

该表达式的逆波兰记法

【输入样例】

a+b*c-d+f/e

【输出样例】

abc*+d-fe/+

思路概要:这道题大概算这次实验的较难题了,这里需要用到栈的技巧,这里分几点进行讲解。

1.栈是什么?

栈是一种常见的数据结构,有着先进后出的性质,犹如一个羽毛球桶。

2.本题的思路?

本题可以使用双栈法,定义第一个栈存放运算过程,第二栈作为临时栈,用于临时存放运算符。

stack<char> s, s_tmp;

3.如何进栈?or 进栈顺序如何?

1.如果碰到数字(变量),则直接压入栈s中

if (*(source + i) >= 'a' && *(source + i) <= 'z') {
			s.push(*(source + i));

2.如果碰到运算符,我们有这样的入栈法则:

①如果是空栈,则入栈。

②如果不是空栈,且该运算符的优先级大于栈顶,则入栈。

当然,这里提到的栈是s_tmp。

那么问题又来了,我们如何进行优先级的判断呢?

这里给出一个函数: 

int isPrio(char c) { //优先级判断
	switch (c) {
		case'+':
		case'-':
			return 1;
		case'*':
		case'/':
			return 2;
	}
	return 0;
}

我们将+ -的优先级定义为1,将* /的优先级定义为2。(当然读者可以设置为其他值,这里没有任何限制,只需要优先级正确即可)

4.如何出栈?or 何时出栈?

s_tmp的出栈顺序是本题的关键,这里给出出栈条件,读者可以自行揣摩:

①如果s_tmp栈不为空,并且循环到达尾声,即i=len-1,栈中剩余元素依次出栈,并且压入s中。

②如果预入栈元素优先级小于等于栈顶的元素(这个比较过程直到栈顶优先级大于预入栈元素,或者栈为空才会停止,否则一直进行),则栈顶元素出栈,并且压入s中。最后将预入栈元素入栈。

备注:这里讲讲为什么是优先级小于等于,而不是小于。由于算式计算按照由左到右的顺序,所以左侧的运算符的优先级,且运算符本身的优先级相等的运算符大于右侧的运算符的优先级。我知道这句话非常绕,所以在下面举个例子:

例如:2+3+5 这里的2后面的+号的优先级是大于3后的+号的,所以事实上,第一个+号优先级是大于第二个+号的优先级的。

然而,我们的入栈顺序也是从左向右的,所以便是小于等于了。

这样,我们本题的思路便差不多了,下面给出代码。

代码如下:

#include <iostream>
#include <stack>
#include <string.h>
using namespace std; 
void push_back(char c, char *target) {
	static int len = 0;
	target[len++] = c;
}
int isPrio(char c) { //优先级判断
	switch (c) {
		case'+':
		case'-':
			return 1;
		case'*':
		case'/':
			return 2;
	}
	return 0;
}
void print_ex(char *target) {//反向输出,由于出栈会生成一个逆向的后缀式,所以需要反向输出
	int len = strlen(target);
	for (int i = len - 1; i >= 0; i--) {
		cout << target[i];
	}
}
void transfer(char *source, char *target) { //双栈转换法
	int len = strlen(source);
	stack<char> s, s_tmp;
	for (int i = 0; i < len; i++) {
		if (*(source + i) >= 'a' && *(source + i) <= 'z') {
			s.push(*(source + i));
		}
		if (*(source + i) == '+' || *(source + i) == '-' || *(source + i) == '*' || *(source + i) == '/') {
			if (s_tmp.empty() || isPrio(*(source + i)) > isPrio(s_tmp.top())) {
				s_tmp.push(*(source + i));
			} else if ((isPrio(*(source + i)) <= isPrio(s_tmp.top())) && !s_tmp.empty()) {
				while (isPrio(*(source + i)) <= isPrio(s_tmp.top())) {
					s.push(s_tmp.top());
					s_tmp.pop();
					if (s_tmp.empty()) break;
				}
				s_tmp.push(*(source + i));
			}
 
		}
	}
	if (!s_tmp.empty()) {
		while (!s_tmp.empty()) {
			s.push(s_tmp.top());
			s_tmp.pop();
		}
	}
	while (!s.empty()) {
		push_back(s.top(), target);
		s.pop();
	}
	print_ex(target);
}
 
int main() {
	char source[200000]={0}, target[200000]={0};//这里必须初始化,否则后面输出结果可能会输出乱码
	cin.getline(source, 200000);
	transfer(source, target);
	return 0;
}

本题代码比较繁琐,读者需要细细研读,最好可以自己写出来,这里提供另一个博客,提供一个思路中缀表达式转逆波兰表达式(后缀表达式)

其实是本蒟蒻太菜了,写的代码太繁琐了呜呜呜呜/(ㄒoㄒ)/~~

备注:这里我使用了C++ STL库中的stack类型,感兴趣的读者可以自己试试用数组模拟一个栈,使用起来会更加方便噢。

5. 【实验5-5】集合的交集

设有两个整数集合A和B,请输出A∩B的元素,其中集合A、B用单向链表表示。

【输入格式】

第一行输入一个数字n,表示集合A中数字的数量

第二行输入n个数字,以空格隔开。

第三行输入一个数字m,表示集合B中数字的数量

第四行输入m个数字,以空格隔开。

【输出格式】

若干个整数,即A∩B的元素,每个整数后面有一个空格

【输入样例】

7

3 9 11 4 2 7 1

5

1 3 5 7 9

【输出样例】

1 7 9 3

程序请按照如下要求设计:

struct Node

{    int data;

     Node *next;

};

struct Set

{    int num;

    Node *head;

};

void init(Set *x)

{

    x->head=NULL;

    x->num=0;

}

void createSet(Set *x)

{

生成链表时一定采用往前插入的方法,即每个新产生的结点插入到链表的第一个结点的前面。

void intersect(Set *x,Set *y)

{

外层循环对集合x进行遍历,内层循环对集合y进行多次遍历,遇到交集的元素,输出的是集合x中的值。

}

int main()

{

Set a,b;

 init(&a);

 init(&b);

createSet(&a);

createSet(&b);

intersect(&a,&b);

return 0;

}

思路概要:本题已经给出了函数的具体框架,读者只需要填入缺失的地方就可以解题了。

本题还是很简单的,如果有同学不了解链表的具体定义和使用方法,可以看看下面的链接C++链表

时间复杂度O(n)

代码如下:

#include<iostream>
using namespace std;
struct Node
{
	int data;
	Node *next;
};
struct Set
{
	int num;
	Node *head;
};

void init(Set *x)
{
	x->head = NULL;
	x->num = 0;
}

void createSet(Set *x)
{
	int n;
	int num1;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>num1;
		Node *p=new Node;
		p->data=num1;
		p->next=NULL;
		if(x->head==NULL){
			x->head=p;
		}
		else{
			p->next=x->head;
			x->head=p;
		}
		x->num++;
	}
}

void intersect(Set *x, Set *y)

{
	Node *p=x->head;
	Node *q=y->head;
	while(p){
		while(q){
			if(p->data==q->data){
				cout<<p->data<<" ";
			}
			q=q->next;
		}
		q=y->head;
		p=p->next;
	}
}

int main()
{
	Set a, b;
	init(&a);
	init(&b);
	createSet(&a);
	createSet(&b);
	intersect(&a, &b);
	return 0;
}

备注:大家要熟悉链表的实现,在以后的程序设计中,链表的运用是很多的,需要加强练习哦。

在最后,链表最好自写一个clear函数进行清除,否则占用堆区空间则会导致系统运行缓慢,要养成好的习惯。

6. 【实验5-6】丢手绢

设有n个小朋友围坐一圈玩丢手绢(用编号1,2,……n代表玩游戏的人),从第k(k大于等于1并且小于等于n)个小朋友开始报数,数到m的那个人出列。他的下一位继续从1开始报数,数到m的人出列,依此类推,直到所有人都出列为止。

要求:n个小朋友围坐一圈用单向循环链表表示。

【输入格式】

三个整数n k m,其中n表示玩游戏的人数,k表示从第k个人开始玩,m表示报数的值

【输出格式】

n个整数,分别对应依次退出游戏的小朋友的编号

【输入样例】

13 2 3

【输出样例】

4 7 10 13 3 8 12 5 11 6 2 9 1

程序可以参考如下框架:

struct Node {

int data;

Node* next;

};

struct List {

Node* head;

int length;

};

void createList(List &lst,int n){

生成链表时采用往后插入的方法,即每个新结点插入到链表尾部,最后的结点指向链表第一个结点,形成单向循环链表。

}

void play(List& lst, int k, int m){

}

int main()

{

int n, k, m;

cin>>n>>k>>m;

List lst;

createList(lst,n);

play(lst,k,m); 

        return 0;

}

思路概要:本题是一个经典的约瑟夫问题,利用循环链表的结构定义与功能,多次遍历即可实现。

什么是约瑟夫问题

易错点:本题要注意,当最后删除只剩下一个节点时,便不能再采取之前删除节点的方法,而是应该特殊判断。这里给出一个两个节点便开始特殊判断的程序,读者也可以自己尝试只剩下1个节点进行特殊判断。

代码如下:

#include<iostream>
using namespace std;
struct Node {
	int data;
	Node* next;
};
struct List {
	Node* head;
	int length;
};
void createList(List &lst,int n){
	lst.length=0;
	Node *q;
	for(int i=1;i<=n;i++){
		Node *p=new Node;
		p->next=NULL;
		if(i==1){
			lst.head=p;
			p->data=i;
			q=lst.head;
			lst.length++;
		}
		else {
			while(q->next){
				q=q->next;
			}
			q->next=p;
			p->data=i;
			lst.length++;
		}
	}
	q->next->next=lst.head;
	return;
}

void play(List& lst, int k, int m){	
	Node *q=lst.head;
	Node *p=q;
	int Node_count=lst.length;
	if(Node_count==1) {
		cout<<q->data;
		return;
	}
	for(int i=0;i<k-1;i++){
		q=q->next;//向后遍历k-1次
	}
	while(Node_count>2){
		int cnt=1;
		for(;cnt<m;cnt++){
			q=q->next;
			while(p->next!=q){
				p=p->next;
			}
		}	
		cout<<q->data<<" ";
		p->next=q->next;
		q=p->next;
		Node_count--;
	}
	for(int cnt=1;cnt<m;cnt++){
		q=q->next;
	}
	cout<<q->data<<" "<<q->next->data;
}
int main()
{
	int n, k, m;
	cin>>n>>k>>m;
	List lst;
	createList(lst,n);
	play(lst,k,m); 
	return 0;
}

备注:作者的代码可能不是最优解,还望原谅┭┮﹏┭┮。

终章:结束

好了,这次中国矿业大学实验五的题解到此结束啦,希望同学们顺利完成实验,作者第一次写博客,写的不好还望大家多多体谅哇QAQ。

本博客可能有些问题没有说清楚,代码也存在纰漏,还望大家指出,本鼠鼠会接受大家的批评教育,努力学习,争取给大家一个比较易懂的题解┭┮﹏┭┮。

就这样啦!期待下一次见面!

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
xiaoxingxing的头像xiaoxingxing管理团队
上一篇 2023年12月22日
下一篇 2023年12月22日

相关推荐