2021-05-22

JLU数据结构第三次实验课解题报告

目录

JLU数据结构第三次实验课解题报告

         7-1 二叉树最长路径 (100 分)

         7-2 森林的层次遍历 (100 分)

7-3 纸带切割 (100 分)

 7-4 序列乘积 (100 分)

收获:


7-1 二叉树最长路径 (100 分)

给定一棵二叉树T,求T中的最长路径的长度,并输出此路径上各结点的值。若有多条最长路径,输出最右侧的那条。

输入格式:

第1行,1个整数n,表示二叉树有n个结点, 1≤n≤100000.

第2行,2n+1个整数,用空格分隔,表示T的扩展先根序列, -1表示空指针,结点用编号1到n表示。

输出格式:

第1行,1个整数length,length表示T中的最长路径的长度。

第2行,length+1个整数,用空格分隔,表示最右侧的最长路径。

输入样例:

5
1 2 -1 -1 3 4 -1 -1 5 -1 -1

输出样例:

2
1 3 5 

题解思路:本题是要求给定二叉树T的最长最右路径,并且给定二叉树是由先根存储,所以第一个考点是先根顺序创建一棵二叉树,然后求解最长最右路径 。

  • 创建一棵二叉树,我是用递归方法求解的。
void CreatBinTree(T &t)
{
	int ch;
	scanf("%d",&ch);
	if(ch==-1)
	{ 
	    t=NULL;
	    return;
	}
	else
	{
		t=(struct Tree*)malloc(sizeof(struct Tree));
		t->data=ch;
		CreatBinTree(t->lchild);
		CreatBinTree(t->rchild);
	}
}
  • 对于求解最大路径长度,我没有用递归方法去求解,因为我刚开始以为用两次递归会超时或者内存超限。思路从根节点开始,先向右深度遍历,把路径压入到栈,当到了叶子节点时候,比较栈中元素个数与已经找到的最大路径长度,如果大于,更新最大值。然后回退,从栈顶元素向左,循环。有一点“图”中回溯法的影子。
  • 代码如下:
#include<bits/stdc++.h>            //先根顺序创建一棵二叉树 
using namespace std;
typedef struct Tree{
	int data;
	struct Tree *lchild;
	struct Tree *rchild;
}*T;
void CreatBinTree(T &t){
	int ch;
	scanf("%d",&ch);
	if(ch==-1){ 
	    t=NULL;
	    return;
	}
	else{
		t=(struct Tree*)malloc(sizeof(struct Tree));
		t->data=ch;
		CreatBinTree(t->lchild);
		CreatBinTree(t->rchild);
	}
}
/*void PreOrder(T t){
	if(t==NULL)return;
	printf("%d",t->data);
	PreOrder(t->lchild);
	PreOrder(t->rchild);
}*/
void Findlongest(T t){
	int k;
	T p=t,r=NULL;
	T l[1000000];
	int max=0;
	stack<T> s;
	while(p||!s.empty()){ 
        if(p!=NULL){ 
            s.push(p); 
            p=p->rchild; 
        } 
        else{ 
            p=s.top(); 
            if(p->lchild!=NULL && p->lchild!=r) 
                p=p->lchild; 
            else{ 
               if(s.size()>max){
				    max=s.size();//最大层次即为高度 
				    for(k=1;k<=max;k++){
				    	l[k]=s.top();
				    	s.pop();
					}
					for(k=max;k>=1;k--){
						s.push(l[k]);
					}
				}
                r=p; 
                s.pop(); 
                p=NULL; 
            } 
        } 
    } 
    printf("%d\n",max-1);
    for(k=max;k>=1;k--){
    	printf("%d",l[k]->data);
        if(k!=1)printf(" ");
	}
    printf("\n");
} 
int main(){
	T t=NULL;
	int n;
	scanf("%d",&n);
	CreatBinTree(t);
	Findlongest(t);
	return 0;
} 

7-2 森林的层次遍历 (100 分)

给定一个森林F,求F的层次遍历序列。森林由其先根序列及序列中每个结点的度给出。

输入格式:

第1行,1个整数n,表示森林的结点个数, 1≤n≤100000.

第2行,n个字符,用空格分隔,表示森林F的先根序列。字符为大小写字母及数字。

第3行,n个整数,用空格分隔,表示森林F的先根序列中每个结点对应的度。

输出格式:

1行,n个字符,用空格分隔,表示森林F的层次遍历序列。

输入样例:

14
A B C D E F G H I J K L M N
4 0 3 0 0 0 0 2 2 0 0 0 1 0

输出样例:

A M B C G H N D E F I L J K

 

解题思路:

这道题最难的地方就是建一个森林。

  • 这里用递归法建森林, 辅助空间用了vector容器数组。vector保存每个节点的所有的儿子的下标。
void buildforest(int x){
	 for(int i=0;i<child[x];i++){  //回溯   先序储存 
    	A[x].push_back(++Count); //把第一个孩子下标先记录到vector,之后递归返回时把孩子下标加入 
	   	buildforest(Count);  
   }
}



//Count初始为1

while(Count<=n){
		A[0].push_back(Count); //每一棵树的结点都会被保存到这里 
                               //第一层的,当第一棵树被建好,会返回到这儿,把第二个根存入
		buildforest(Count);
		Count++; 
	}
  •  层次遍历是用了辅助空间用了普通队列,这个点是容易实现的。遍历一层的同时,把他们的儿子都入队。
  • 完整代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Max 100005
char Node[Max];
int child[Max];
vector <int> A[Max]; //类似邻接表 ,把每个节点的孩子都存到一个里面
queue <int> Q;
int Count=1;
void buildforest(int x){
	 for(int i=0;i<child[x];i++){  //回溯   先序储存 
    	A[x].push_back(++Count); //把第一个孩子存入递归返回时把剩下孩子下标加入 
	   	buildforest(Count);  
   }
}
int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf(" %c",&Node[i]);
	}
	for(int i=1;i<=n;i++){
		scanf("%d",&child[i]);
	}
	while(Count<=n){
		A[0].push_back(Count); //每一棵树的结点都会被保存到这里  //第一层的,当第一棵树被建好,会返回到这儿,把第二个根存入
		buildforest(Count);
		Count++; 
	}
	for(vector<int>::iterator i=A[0].begin();i!=A[0].end();i++){//遍历器 
		   Q.push(*i);
		}
	while(!Q.empty()){
		int ant=Q.front();
		Q.pop();
		for(vector<int>::iterator i=A[ant].begin();i!=A[ant].end();i++){
				Q.push(*i);
		}
		printf("%c",Node[ant]);
		printf("%c",!Q.empty()?' ':'\n');
	}	
	return 0;
} 

7-3 纸带切割 (100 分)

有一条细长的纸带,长度为 L 个单位,宽度为一个单位。现在要将纸带切割成 n 段。每次切割把当前纸带分成两段,切割位置都在整数单位上,切割代价是当前切割纸带的总长度。每次切割都选择未达最终要求的最长纸带切割,若这样的纸带有多条,则任选一条切割。如何切割,才能完成任务,并且总代价最小。

输入格式:

第1行,1个整数n,表示切割成的段数, 1≤n≤100000.

第2行,n个整数Li,用空格分隔,表示要切割成的各段的长度,1≤Li≤200000000,1≤i≤n.

输出格式:

第1行,1个整数,表示最小的总代价。

第2行,若干个整数,用空格分隔,表示总代价最小时每次切割的代价

  输入样例:

5
5 6 7 2 4 

输出样例:

54
24 13 11 6

解题思路:

考察哈夫曼树的思想。要求每次切割最小代价,以及总的代价。每次都是最小的代价,当然最后的总代价就是最小的。这道题表面是让我们体现切割的过程,但是我们输入的是已经切好的长度,所以我们应该倒过来,将他一段段的合起来。要求最小代价以及最小总代价。每段长度相当于哈夫曼树中每个节点的权值,所以整体过程就是把最小的两个合成一个在插入重新排序,循环这个过程,直到只剩下一个结点。辅助空间用了优先队列,排序用堆排序进行优化。思想如此,但是实现过程没有建哈夫曼树。

代码如下:

#include<bits/stdc++.h>
using namespace std;
priority_queue <long long,vector<long long>,greater<long long> > Q;//默认从大到小 ,所以用大根堆 从小到大 
int main(){
	long long n;
	long long i,k,count;
	long long leastprice[100001];//记录下每次 
	long long Most=0;
	scanf("%lld",&n);
	if(n==1)
	{
    	printf("0");
        return 0;
}
	for(i=1;i<=n;i++){
		scanf("%lld",&k);
		Q.push(k);
	}
	for(i=1;i<=n-1;i++){  //一条纸带切割成n段,要切割n-1次 
		long long x=Q.top();
		Q.pop();
		long long y=Q.top();
		Q.pop();
		leastprice[i]=x+y;
		Most+=x+y;
		Q.push(x+y);
	}
	printf("%lld\n",Most);
	for(i=n-1;i>=1;i--){
		printf("%lld",leastprice[i]);
		if(i!=1)printf(" ");
	}
	printf("\n");
	return 0;
}

 7-4 序列乘积 (100 分)

两个递增序列A和B,长度都是n。令 Ai 和 Bj 做乘积,1≤i,j≤n.请输出n*n个乘积中从小到大的前n个。

输入格式:

第1行,1个整数n,表示序列的长度, 1≤n≤100000.

第2行,n个整数Ai,用空格分隔,表示序列A,1≤Ai≤40000,1≤i≤n.

第3行,n个整数Bi,用空格分隔,表示序列B,1≤Bi≤40000,1≤i≤n.

输出格式:

1行,n个整数,用空格分隔,表示序列乘积中的从小到大前n个。

输入样例:

5
1 3 5 7 9 
2 4 6 8 10

输出样例:

2 4 6 6 8

思路与程序难点:

这道题,如果简单去想,那思路真是太简单了!!暴力求解,sort()排序,但是,运行超时。如果基本运算是排序的话 ,n^2最大可以达到10^10,让这么多的数排序的话,就得考虑堆排序O(log n)级别的。而且,空间开n^2也是很大的。所以,就要考虑,不是要把n^2个数直接算出来并且都保存起来。而是需要哪个,我们就计算哪个。受别人启发,A中每个元素都和B中每个元素相乘得积,这样,就可以构成一个方阵。A[i]对应每一列,B[j]对应每一行。由于这里的A B数列是特殊的,都是单调递增的,所以A[1](由于是矩阵,这里的第一个坐标为1)与B中所有元素相乘结果单调递增的,并且相乘结果一定是每一行中的最小数,把这n个数放入优先队列,取出最小数(最小中的最小,一定是最小),然后再让出队的那个元素所在行的它后面元素入队。如此循环,知道取出n个数为止,这样,既用堆优化了排序,还节省了空间,只需要维护O(n)的优先队列空间即可。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int A[100001];
int B[100001];
struct tmp2{  //需要排序的是结构体,要对<进行重载
	int data,row,col;
	bool operator < (const tmp2 &a)const{
		return data>a.data;
	} 
};
priority_queue <tmp2> Q;
int main(){
	int n;
	int i,ent,ant;
	scanf("%d",&n);
	for(i=1;i<=n;i++){
		scanf("%d",&A[i]);
	}
	for(i=1;i<=n;i++)
	{
		scanf("%d",&B[i]);
        struct tmp2 k;
		k.data=A[1]*B[i];  
        k.row=i;
        k.col=1;
        Q.push(k);
	}
    for(i=1;i<=n;i++)
    {
        struct tmp2 M=Q.top();
        Q.pop();
        printf("%d",M.data);
        if(i!=n)printf(" ");
        ent=M.col;
        ant=M.row;
        M.col=ent+1;
        M.row=ant;
        M.data=B[ant]*A[ent+1];
        Q.push(M);
    }
    printf("\n");
	return 0;
}

收获:

这次实验课我收获了很多很多,我发现了自己很大很大的不足,说实话,对于关于树的知识,我感觉自己学的稀里糊涂,在思维上我就想不到自己要去如何的实现它以及在代码上更是实现不了,书上的算法也是没有记牢。感觉自己好菜。我会努力的。我知道这是一门很重要很重要的课程。这次的实验课,还用了我没有使用过的优先队列,堆排序,收获很大。

 

 

 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值