数据结构第三次上机

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

 解题思路:

这道题老师之前已经布置过作业了,所以开始的时候我把之前的代码改了一下提交发现只能过4个测试点,还有一个始终无法过去。后经过同学的提醒,我想起来在作业中,老师的做法是先将高度存储下来,再搜寻最长路径,这样的时间复杂度是O(n)的,但如果不存储的话,时间复杂度会达到O(n^2),在考虑递归的代价,时间复杂度难以想象。因此我在原先的结构体中增加了存储高度的空间,再把最后搜寻路径的操作改为循环实现,这样测试后所有的测试点都过了。(需要多试一下,因为加入了一个高度域,总的使用内存刚刚好达到限制5M,可能会出错,如果想减少空间,我觉得可以用字符串之类的存储)

代码:

#include<iostream>
#include<algorithm>
using namespace std;
class tree {
	struct a {
		int data;
		struct a* left;
		struct a* right;
		int height;
	};
public:
	struct a* root;
	void b(struct a*& p);
	void an();
	int maxh(struct a* p);
};
void tree::b(struct a*& p) {
	int k;
	scanf("%d", &k);
	if (k == -1) {
		p = NULL;
		return;
	}
	else {
		p = new struct a;
		p->data = k;
		b(p->left);
		b(p->right);
	}
}
int tree::maxh(struct a* p) {
	if (p) {
		p->height = max(maxh(p->left), maxh(p->right)) + 1;
		return p->height;
	}
	else return -1;
}
void tree::an() {
	struct a* l, * r;
	struct a* q = root;
	int i = 0;
	while (q) {
		if (i)printf(" ");
		printf("%d", q->data);
		if (q->left == NULL || (q->left && q->right && q->right->height >= q->left->height)) {
			q = q->right;
		}
		else q = q->left;
		i++;
	}
}
int main(void) {
	tree a;
	int n;
	scanf("%d", &n);
	a.b(a.root);
	a.maxh(a.root);
	printf("%d\n", a.root->height);
	a.an();
}

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

解题思路: 

这道题我最开始想的是通过先根序列的特性,可以不建树,而是在读取过程中计算每一个结点的高度,然后再有序地打印出来,这样就是森林的层次遍历了。又因为sort函数是快排,稳定性差,因此最开始我用sort只过了一个测试点,后面我改为插入排序,寻找位置用二分法(不会归并排序),但这样最后一个会超时,这时我发现,因为我是用vector存储结点,所以在插入时时间复杂度会达到O(n),虽然每次查找代价较低,但总体上代价仍然很高。因此最后我改成了直接先建树,再通过队列实现森林的层次遍历,这样总代价应该是O(n)的(不考虑递归代价)。

代码: 

#include<iostream>
#include<queue>
using namespace std;
struct tree {
	char data;
	struct tree* left;
	struct tree* right;
};
char* jd;
int loc = 0;
tree* root;
queue<tree*> Q;
void build(int n,tree* &p) {
	tree* t0 = NULL;
	int f;
	for (int i = 0; i < n; i++,loc++) {
		scanf("%d", &f);
		tree* t;
		t = new tree;
		t->data = jd[loc];
		t->left = NULL;
		t->right = NULL;
		if (!i)p->left = t;
		else t0->right = t;
		if (f) {
			loc++;
			build(f, t);
			loc--;
		}
		t0 = t;
	}
}
int main(void) {
	int n;
	int k;
	int i;
	int height = 1;
	tree* p;
	tree* l0 = NULL;
	scanf("%d", &n);
	jd = new char[n];
	char ch = getchar();
	for (i = 0; i < n; i++) {
		scanf("%c ", &jd[i]);
	}
S:	tree* l;
	l = new tree;
	l->data = jd[loc++];
	l->left = l->right = NULL;
	if (loc==1)root = l;
	else l0->right = l;
	l0 = l;
	scanf("%d", &k);
	if (k)build(k, l);
	if (loc < n)goto S;
	i = 0;
	Q.push(root);
	while (!Q.empty()) {
		p = Q.front();
		Q.pop();
		if(p)Q.push(p->left);
		while (p) {
			if (i)printf(" ");
			printf("%c", p->data);
			i++;
			p = p->right;
			if(p)Q.push(p->left);
		}
	}
}

 

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

 解题思路:

这道题可以逆题目思考,也就是最开始都是一截一截的纸片,需要一次次拼接到完整纸带,使得总代价最小。这样的话,每次拼接时应该要取两个最小的纸片使得每次的代价最小,这样就与哈夫曼的思想十分类似的,又因为这道题的时间限制是100ms,所以我们应该通过堆来优化取最小的操作,这里需注意的是,我们不必真正建一颗哈夫曼树,只需要模拟其中取最小的操作即可,这样可以节省很多的内存。并且,注意到总代价的值可能会超过int的范围,因此一定要使用long long int!

 代码:

#include<iostream>
#include<stack>
using namespace std;
int *num;
int maxs;
stack<int> u;
void down(int i){
	int x,y;
	int k;
	x=i<<1;y=x+1;
	while(x<=maxs&&num[i]>num[x]||y<=maxs&&num[i]>num[y]){
		if(y<=maxs&&num[x]>num[y])x++;
		k=num[i];
		num[i]=num[x];
		num[x]=k;
		i=x;
		x=i<<1;y=x+1;
	}
}
int main(){
	int n;
	int i;
	int sw;
	long long int m1,m2;
	long long int price=0;
	scanf("%d",&n);
	num=new int[n+1];
	for(i=1;i<=n;i++){
		scanf("%d",&num[i]);
	}
	maxs=n;
	for(i=maxs/2;i>=1;i--)down(i);
	while(maxs>1){
		m1=num[1];
		sw=num[1];
		num[1]=num[maxs];
		num[maxs]=sw;
		maxs--;
		down(1);
		m2=num[1]+m1;
		u.push(m2);
		price=price+m2;
		num[1]=m2;
		down(1);
	}
	printf("%lld\n",price);
	while(!u.empty()){
		if(u.size()<n-1)printf(" ");
		printf("%d",u.top());
		u.pop();
	}
} 

 

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

 解题思路及代码:

这道题最开始我的思路是用插入法做,始终保持向量里的元素为n,在遇到大于向量中最大元素的数,就直接不用比较后面的数(因为数据的递增性质),因为每次向量插入的代价大并且这样最多甚至能比较n^2次,所以最后一个样例没过。后面根据炫神的思路,我想了一种方法,就是建一个大小为n的堆,我是每次取最大,如果新进来的元素比它小,则覆盖该值,否则根据递增性质后面的数(Bj)可以不用再与当前的Ai值相乘计算(我是先往堆中放了A0*Bi的值,一共n个),最后再进行一次快排,最后插入代价减小但比较次数仍然很大,这里我取巧了,只比较n/2个数(侥幸心理,感觉后面的可能很大不用比较),就过了样例,方法如下(时间复杂度肯定超了!):

#include<iostream>
#include<algorithm>
using namespace std;
int maxs;
int* H;
int* a;
int* b;
void down(int i) {
	int x, y;
	int k;
	x = i << 1; y = x + 1;
	while (x <= maxs && H[x] > H[i] || y <= maxs && H[y] > H[i]) {
		if (y <= maxs && H[y] > H[x])x++;
		k = H[i];
		H[i] = H[x];
		H[x] = k;
		i = x;
		x = i << 1;
		y = x + 1;
	}
}
int main() {
	int n;
	int i;
	int j;
	int m;
	scanf("%d", &n);
	maxs = n;
	a = new int[n];
	b = new int[n];
	H = new int[n+1];
	for (i = 0; i < n; i++) {
		scanf("%d", &a[i]);
	}
	for (i = 0; i < n; i++) {
		scanf("%d", &b[i]);
		H[i + 1] = a[0] * b[i];
	}
	for (i = n / 2; i >= 1; i--)down(i);
	for (i = 1; i < n/2+1; i++) {
		for (j = 0; j < n/2+1; j++) {
			m = a[i] * b[j];
			if (m >= H[1])break;
			else {
				H[1] = m;
				down(1);
			}
		}
	}
	sort(H + 1, H + n + 1);
	for (i = 1; i <= n; i++) {
		if (i != 1)printf(" ");
		printf("%d", H[i]);
	}
}

之后我又借鉴炫神及其他大佬的代码,发现可以用矩阵的思想来看这道题,初始化的时候堆还是之前那样,但后面直接是每次取最小然后输出,在最小值输出后,由于Ai,Bi递增的性质,可以知道每行或者每列最小肯定是第一个元素,因此每次取最小就在所有行的第一个元素中取(一共n个),取完最小元素后,放入该结点的元素就是与之相邻的下一个元素(根据你选择的列不变或者行不变来看,这同时需要我们开结构体存储元素,不仅存储乘积还要存储所在的行列数),也就相当于删除该元素,把整行或列往前提一位 ,再在这新的n个元素中取最小,直到取了n个元素。这样下来循环n次,每次down代价为logn,总体是nlogn,就达到要求了。

#include<iostream>
#include<algorithm>
#define M 0x7fffffff
using namespace std;
int maxs;
struct mt {
	int data;
	int line;
	int row;
};
struct mt* H;
int* a;
int* b;
void down(int i) {
	int x, y;
	mt k;
	x = i << 1; y = x + 1;
	while (x <= maxs && H[x].data < H[i].data || y <= maxs && H[y].data < H[i].data) {
		if (y <= maxs && H[y].data < H[x].data)x++;
		k = H[i];
		H[i] = H[x];
		H[x] = k;
		i = x;
		x = i << 1;
		y = x + 1;
	}
}
int main() {
	int n;
	int i;
	int m;
	scanf("%d", &n);
	maxs = n;
	a = new int[n];
	b = new int[n];
	H = new mt[n + 1];
	for (i = 0; i < n; i++) {
		scanf("%d", &a[i]);
	}
	for (i = 0; i < n; i++) {
		scanf("%d", &b[i]);
		H[i + 1].data = a[0] * b[i];
		H[i + 1].line = 0;
		H[i + 1].row = i;
	}
	for (i = n / 2; i >= 1; i--)down(i);
	for (i = 1; i <= n; i++) {
		m = H[1].data;
		if (i != 1)printf(" ");
		printf("%d", m);
		if (H[1].line < n - 1) {
			H[1].line += 1;
			H[1].data = a[H[1].line] * b[H[1].row];
		}
		else H[1].data = M;
		down(1);
	}
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值