数据结构第一次上机实验解题报告

本文详细介绍了三种算法问题的解决方案:使用链表解决报数游戏问题,利用单调队列解决最喜爱序列问题,以及采用中缀表达式转换和堆栈计算算术表达式。每个问题的解法都包含思路分析和代码实现,旨在帮助读者理解和掌握算法应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

7-1重复计数(100分)

 在一个有限的正整数序列中,有些数会多次重复出现。请你统计每个数的出现次数,然后按数字在序列中第一次出现的位置顺序输出数及其次数。

输入格式:

第1行,1个整数N,表示整数的个数,(1≤N≤50000)。

第2行,N个正整数,每个整数x 都满足 1 ≤ x ≤2000000000。

输出格式:

若干行,每行两个用一个空格隔开的数,第一个是数列中出现的数,第二个是该数在序列中出现的次数。

输入样例:

在这里给出一组输入。例如:

12
8 2 8 2 2 11 1 1 8 1 13 13

输出样例:

在这里给出相应的输出。例如:

8 3
2 3
11 1
1 3
13 2

这个题,一个很显然的想法是用一个20亿个元素的int数组,下标表示输入的数字,值表示出现的次数。但是题目没有提供这么大的空间(3000万个int大约是128mb)

解法一:

又有一个很显然的想法是,对于每一个输入的数,先在已经输入并保存起来的数中查找,如果已经输入过,对应的次数加一,如果还没有出现过,就保存起来,并且出现次数设置为1.可以采用一个数组进行存储,每次输入都从头开始遍历,如果需要添加元素的话就在最后的位置后面添加。这样一来,输出结果时从头开始顺序输出,就会按照原数字出现的顺序。这个思路最坏情况下时间复杂度是O(n^2).在pta上可以通过(但不一定总是能通过),但是在另一个平台上两次提交都是70分。

代码如下:

#include<bits/stdc++.h>
using namespace std;

struct number{
	int num;
	int times;
}h[50100];
int rr;

int main()
{
	int n,i,k,j,flag;
	scanf("%d",&n);
	for(i=0;i<n;i++)
	{
		flag=0;
		scanf("%d",&k);
		for(j=0;j<rr;j++)
		{
			if(h[j].num==k)
			{
				h[j].times++;
				flag=1;
				break;
			}
		}
		if(flag==0)
		{
			h[rr].num=k;
			h[rr++].times=1;
		}
	}
	for(i=0;i<rr;i++)
	{
		if(i>0)printf("\n");
		printf("%d %d",h[i].num,h[i].times);
	}
	return 0;
}

解法二:

老师说他本来是希望我们用排序来做的。(dalao们都谈了这个方法,我就复述一遍吧)这个方法是把输入的数据排序,相同的数会放在一起,找出一个数在排序后的序列中最后一次出现的位置和第一次出现的位置,就能知道这个数出现的次数。但是排序后这些数第一次出现的次序就会丢失,于是需要事先存储。可以用一个queue来存储,一个数如果还没出现,就入队。不过这里也并不是非用queue不可,我们只是需要一个能够顺序地放入数据,并且能够从头开始顺序读取的结构而已,随便写一个数组也行。

那么,我们还需要知道一个数它是否已经出现。如果用从头遍历queue的办法,那就走了解法一的老路,效率较低。可以采用STL中的set,来存储已经出现的数,未出现的数就加入到其中。至于为什么可以用set:

STL中的set底层采用红黑树的实现,查询和插入时间都是O(logn)。也就是说每次查找的时候不需要遍历一遍所有元素。至于红黑树可以把它近似理解成一颗深度接近于logn的二叉树,所以最多只要往下跑logn次就能访问元素。

 代码如下:

#include<bits/stdc++.h>
using namespace std;

queue<int> q;
set<int> s;
int num[50010];

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",num+i);
        if (!s.count(num[i]))
        {
            q.push(num[i]);
            s.insert(num[i]);
        }
    }
    sort(num+1,num+n+1);
    while (!q.empty())
    {
        int pre=q.front();
        printf("%d %d\n",pre,upper_bound(num+1,num+n+1,pre)-lower_bound(num+1,num+n+1,pre));
        q.pop();
    }
    return 0;
}

7-2 报数游戏(100分)

  n个人围成一圈,从1开始依次编号,做报数游戏。 现指定从第1个人开始报数,报数到第m个人时,该人出圈,然后从其下一个人重新开始报数,仍是报数到第m个人出圈,如此重复下去,直到所有人都出圈。总人数不足m时将循环报数。请输出所有人出圈的顺序。

输入格式:

一行,两个整数n和m。n表示游戏的人数,m表示报数出圈的数字,1≤n≤50000,1≤m≤100.

输出格式:

一行,n个用空格分隔的整数,表示所有人出圈的顺序

输入样例:

在这里给出一组输入。例如:

5 2

输出样例:

在这里给出相应的输出。例如:

2 4 1 5 3

这个题没什么好说的,直接用链表就行了,用STL的list 

代码如下:

#include <bits/stdc++.h>
using namespace std;

list<int> circle;

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    int i;
	for(i=1;i<=n;i++)
	{
		circle.push_back(i);
	}
	list<int>::iterator it=circle.begin();
	int t=0;
    while (!circle.empty())
	{
        for(int i=1;i<=m-1;i++)
        {
        	it++;
        	it=it==circle.end()?circle.begin():it;
		}
        list<int>::iterator del=it;
        it++;
		it=it==circle.end()?circle.begin():it;
        if(t>0)
		    printf(" ");
        printf("%d",*del);
	    circle.erase(del);
	    t++;
    }
    return 0;
}

7-3 算数表达式计算(100分)

任务: 计算算术表达式的值。

算术表达式按中缀给出,以=号结束,包括+,-,,/四种运算和(、)分隔符。运算数的范围是非负整数,没有正负符号,小于等于109 。

计算过程中,如果出现除数为0的情况,表达式的结果为”NaN” ; 如果中间结果超出32位有符号整型范围,仍按整型计算,不必特殊处理。 输入保证表达式正确。

输入格式:

一行,包括1个算术表达式。算术表达式的长度小于等于1000。

输出格式:

一行,算术表达式的值 。

输入样例:

在这里给出一组输入。例如:

(1+30)/3=

输出样例:

在这里给出相应的输出。例如:

10

老师的ppt里讲的就很清晰,直接照搬:

S1.READ(X).
S2.IF x是操作数 THEN Q<=x.
S3.IF x是运算符 THEN //运算符包括括号
         IF S为空 THEN S<=x.
         ELSE //S 非空   '('优先级最低
             IF  x是'('   OR   优先级x>top(S)  THEN   S<=x
             ELSE  IF x是')' THEN(
                      WHILE  top(S)!='('   DO
                      ( d2<=Q.    d1<=Q.  t<=S.   Q<=d1  t  d2.).
                       t<=S.)
             ELSE (
                      WHILE   S非空  AND  优先级  top(S)>=x   DO
                      (d2<=Q.  d1<=Q.  t<=S.  Q<=d1 t d2.)
                      S<=x.
                      )
S4.IF x是'#'THEN(
       WHILE  S非空 DO(d2<=Q.   d1<=Q.  t<=S.   Q<=d1 t d2.)
       RETURN).
S5.GOTO S1

 代码如下:

#include<cstdio>
#include<cstdlib>
#include<stack>
using namespace std;

stack<int> sn;
stack<char> sop;

int prior(char ch)
{
	if(ch=='+'||ch=='-')return 1;
	if(ch=='*'||ch=='/')return 2;
	if(ch=='(')return 0;
}

int calcu(int a,int b,char op)
{
	if(b==0&&op=='/'){
		printf("NaN");
		exit(0);
	}
	if(op=='+')return a+b;
	if(op=='-')return a-b;
	if(op=='*')return a*b;
	if(op=='/')return a/b;
	
}

int main()
{
	int num=0,flag=0;
	char ch;
	while(scanf("%c",&ch))
	{
		if(ch>='0'&&ch<='9')
		{
			num=num*10+ch-'0';
			//printf("num=%d\n",num);
			flag=1;
		}
		else if(ch!='=')
		{
			if(flag)
			{
				sn.push(num);
			    num=0;
			    flag=0;
			}
			if(sop.empty())
			{
				sop.push(ch);
			}
			else if(ch=='('||prior(ch)>sop.top())
			{
				sop.push(ch);
				//printf("herehere\n");
			}
			else if(ch==')')
			{
				int d2,d1;
				char op;
				//printf("here\n");
				while(sop.top()!='(')
				{
					d2=sn.top();
					//printf("d2=%d\n",d2);
					sn.pop();
					d1=sn.top();
					//printf("d1=%d\n",d1);
					sn.pop();
					op=sop.top();
					//printf("op=%c\n",op);
					sop.pop();
					sn.push(calcu(d1,d2,op));
				}
				sop.pop();
			}
			else
			{
				int d1,d2;
				char op;
				while(!sop.empty()&&prior(sop.top())>=prior(ch))
				{
					d2=sn.top();
					sn.pop();
					d1=sn.top();
					sn.pop();
					op=sop.top();
					sop.pop();
					sn.push(calcu(d1,d2,op));
				}
				sop.push(ch);
			}
	    }
		else
		{
			if(flag)
			{
				sn.push(num);
			    num=0;
			    flag=0;
			}
			int a,b;
			char op;
			while(!sop.empty())
			{
				b=sn.top();
				sn.pop();
				a=sn.top();
				sn.pop();
				op=sop.top();
				sop.pop();
				sn.push(calcu(a,b,op));
			}
			break;
		}
	}
	printf("%d",sn.top());
	return 0;
}

由于是逐字符读取,我们需要一个标志来表示上一个字符是否是一个操作数。如果上一个字符属于一个操作数,当前字符是运算符,就要把操作数压栈。

7-4最喜爱的序列

 小唐这段时间在研究序列。拿来N个整数的序列,他给序列中的每个整数都赋予一个喜爱值。喜爱值也是整数,有正有负,越大表明越喜欢。他想知道,如何从序列中连续取最多m个数,他获得喜爱值最大。1≤N≤500000,1≤m≤N。

输入格式:

第一行是两个整数N,m。分别代表序列中数的个数以及能取的最多个数。

第二行用空格隔开的N个整数,第i个整数Li代表他对第i个数的喜爱值。│Li│≤1000

输出格式:

一行,三个数,表示获得最大喜爱值,及第一个取最大喜爱值的区间。

输入样例:

在这里给出一组输入。例如:

5 2
1 4 5 2 3

输出样例:

 在这里给出相应的输出。例如:

9 2 3

 求一段序列的和,可以采用前缀和的方法。开一个int数组sum,边输入便计算前面所有数的和。

sum[0]=0;//注意把sum[0]先赋上0
for(i=1;i<=n;i++)
{
	scanf("%lld",sum+i);
	sum[i]+=sum[i-1];
}

于是,问题就转化为,对于每一个  i  ,求sum[i]-sum[i-1]  ,  sum[i]-sum[i-2]  ,  ...sum[i]-sum[i-m]  中的最大值,即求  sum[i-m]  ,  sum[i-m+1]  ,  ...  ,  sum[i-1]  中的最小值,于是就是对于每一个  sum[i]  ,都要设法知道它前面最多m个元素中的最小值。这就能看出单调队列的应用了,保持队首始终是前最多m个元素中的最小值(用队尾也一样)。

维护这样一个单调队列的代码:

while(!q.empty()&&sum[q.back()]>sum[i-1])
{
	q.pop_back();
}
q.push_back(i-1);
while(!q.empty()&&i-q.front()>m)
{
	q.pop_front();
}

 用max,posl,posr分别保存最大喜爱值和区间,每次都检查是否需要更新。

long long max=-1*INT_MAX;
int posl=0,posr=0;
for(i=1;i<=n;i++)
{
	while(!q.empty()&&sum[q.back()]>sum[i-1])
	{
		q.pop_back();
	}
	q.push_back(i-1);
	while(!q.empty()&&i-q.front()>m)
	{
		q.pop_front();
	}
	if(max<sum[i]-sum[q.front()])
	{
		max=sum[i]-sum[q.front()];
		posl=q.front()+1;
		posr=i;
	}
}

由于我们要求的是第一个取得最大喜爱值的区间,sum[q.back()]>sum[i-1]  中  >  不能写成   >=  .

全部代码如下:

#include<bits/stdc++.h>
using namespace std;

long long sum[500010];
deque<int> q;

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	int i;
	sum[0]=0;
	for(i=1;i<=n;i++)
	{
		scanf("%lld",sum+i);
		sum[i]+=sum[i-1];
	}
	long long max=-1*INT_MAX;
	int posl=0,posr=0;
	for(i=1;i<=n;i++)
	{
		while(!q.empty()&&sum[q.back()]>sum[i-1])
		{
			q.pop_back();
		}
		q.push_back(i-1);
		while(!q.empty()&&i-q.front()>m)
		{
			q.pop_front();
		}
		if(max<sum[i]-sum[q.front()])
		{
			max=sum[i]-sum[q.front()];
			posl=q.front()+1;
			posr=i;
		}
	}
	printf("%lld %d %d\n",max,posl,posr);
	return 0;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值