《算法笔记》学习日记——9.1 树与二叉树&9.2 二叉树的遍历

9.1 树与二叉树

Codeup Contest ID:100000610
PS:本节是二叉树概念的讲述,没有相关习题。

9.2 二叉树的遍历

Codeup Contest ID:100000611

问题 A: 复原二叉树

题目描述
小明在做数据结构的作业,其中一题是给你一棵二叉树的前序遍历和中序遍历结果,要求你写出这棵二叉树的后序遍历结果。
输入
输入包含多组测试数据。每组输入包含两个字符串,分别表示二叉树的前序遍历和中序遍历结果。每个字符串由不重复的大写字母组成。
输出
对于每组输入,输出对应的二叉树的后续遍历结果。
样例输入

DBACEGF ABCDEFG
BCAD CBAD

样例输出

ACBFGED
CDAB

思路
这题很常规,书上也有具体的代码,就是已知前序序列+中序序列,先重建二叉树,再求后序序列即可。
代码

#include<cstdio>
#include<math.h>
#include<algorithm>
#include<string>
#include<iostream>
using namespace std;
struct node{
	char data;//数据域是一个字母
	node* lchild;
	node* rchild; 
};
string pre;
string in;
node* create(int preL, int preR, int inL, int inR){
	if(preL>preR){
		return NULL;
	}
	node* root = new node;
	root->data = pre[preL];//确定根节点
	int k;
	for(k=inL;k<=inR;k++){//遍历中序序列区间 
		if(in[k]==pre[preL]) break;//在中序序列中找到根节点了,break 
	}
	int numLeft = k-inL;//左子树的长度 
	root->lchild = create(preL+1, preL+numLeft, inL, k-1);
	root->rchild = create(preL+numLeft+1, preR, k+1, inR);
	return root;
}
void postorder(node* root){//后续遍历 
	if(root==NULL){
		return;
	}
	postorder(root->lchild);
	postorder(root->rchild);
	cout<<root->data;
}
int main(){
	while(cin>>pre>>in){
		node* root = create(0, pre.length()-1, 0, in.length()-1);//都是闭区间
		postorder(root);
		cout<<'\n';
		pre.clear();
		in.clear(); 
	}
	return 0;
}

问题 B: 二叉树

题目描述
在这里插入图片描述
如上所示,由正整数1,2,3……组成了一颗特殊二叉树。我们已知这个二叉树的最后一个结点是n。现在的问题是,结点m所在的子树中一共包括多少个结点。

比如,n = 12,m = 3那么上图中的结点13,14,15以及后面的结点都是不存在的,结点m所在子树中包括的结点有3,6,7,12,因此结点m的所在子树中共有4个结点。
输入
输入数据包括多行,每行给出一组测试数据,包括两个整数m,n (1 <= m <= n <= 1000000000)。最后一组测试数据中包括两个0,表示输入的结束,这组数据不用处理。
输出
对于每一组测试数据,输出一行,该行包含一个整数,给出结点m所在子树中包括的结点的数目。
样例输入

3 7
142 6574
2 754
0 0

样例输出

3
63
498

思路
这题我用递归来写,差点超时……为了保险起见大家还是把递归改成循环吧。

思路也很简单,按照题目的意思来说,就是给一棵完全二叉树,然后让你计算某个结点所在子树的总结点数(包括自己),因为题目中标号已经直接给出了(就是按一层一层这样子标号),那么就能运用书上的一个结论,假如说当前所在结点的标号是x,那它的左孩子就是2x,它的右孩子就是2x+1。

于是这个问题就可以用dfs去解决了:只要存在左孩子,就往那个方向递归;只要存在右孩子,就往那个方向递归。递归边界就是:如果算出来的2x或者2x+1已经大于给出的最后一个结点n,那就说明对应的左孩子或者右孩子不存在,return就好了。
代码

#include<cstdio>
#include<math.h>
#include<algorithm>
#include<iostream>
using namespace std;
void cnt(int m, int n, int &sum){//m是要求的结点,n是树的最后一个结点,sum是所求的结点m的子树包含的总结点 
	if(m>n) return ;
	if(2*m<=n){
		sum++;
		cnt(2*m, n, sum);
	}
	if(2*m+1<=n){
		sum++;
		cnt(2*m+1, n, sum);
	}
}
int main(){
	int m, n;
	while(scanf("%d%d", &m, &n) != EOF){
		if(m==0&&n==0) break;
		int sum = 1;//先算上结点本身 
		cnt(m, n, sum);
		printf("%d\n", sum);
	}
	return 0;
}

问题 C: 二叉树遍历

题目描述
二叉树的前序、中序、后序遍历的定义:
前序遍历:对任一子树,先访问跟,然后遍历其左子树,最后遍历其右子树;
中序遍历:对任一子树,先遍历其左子树,然后访问根,最后遍历其右子树;
后序遍历:对任一子树,先遍历其左子树,然后遍历其右子树,最后访问根。
给定一棵二叉树的前序遍历和中序遍历,求其后序遍历(提示:给定前序遍历与中序遍历能够唯一确定后序遍历)。
输入
两个字符串,其长度n均小于等于26。
第一行为前序遍历,第二行为中序遍历。
二叉树中的结点名称以大写字母表示:A,B,C…最多26个结点。
输出
输入样例可能有多组,对于每组测试样例,
输出一行,为后序遍历的字符串。
样例输入

ABC
CBA
ABCDEFG
DCBAEFG

样例输出

CBA
DCBGFEA

思路
这题和问题A一样,建议尽量自己独立写出重建二叉树和后序遍历的函数,毕竟考试的时候可不能看书呀~
代码

#include<cstdio>
#include<math.h>
#include<algorithm>
#include<string>
#include<iostream>
using namespace std;
struct node{
	char data;
	node* lchild;
	node* rchild;
};
string pre;
string in;
node* create(int preL, int preR, int inL, int inR){
	if(preL>preR) return NULL;
	node* root = new node;
	root->data = pre[preL];
	int k;
	for(k=inL;k<=inR;k++){
		if(in[k]==pre[preL]) break;
	}
	int numLeft = k-inL;
	root->lchild = create(preL+1, preL+numLeft, inL, k-1);
	root->rchild = create(preL+numLeft+1, preR, k+1, inR);
	return root;
}
void postorder(node* root){
	if(root==NULL) return;
	postorder(root->lchild);
	postorder(root->rchild);
	cout<<root->data;
}
int main(){
	while(cin>>pre>>in){
		node* root = create(0, pre.length()-1, 0, in.length()-1);
		postorder(root);
		cout<<'\n';
		pre.clear();
		in.clear();
	}
	return 0;
}

问题 D: 二叉树遍历

题目描述
编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。
例如如下的先序遍历字符串:
ABC##DE#G##F###
其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。
输入
输入包括1行字符串,长度不超过100。
输出
可能有多组测试数据,对于每组数据,
输出将输入字符串建立二叉树后中序遍历的序列,每个字符后面都有一个空格。
每个输出结果占一行。
样例输入

a#b#cdef#####
a##

样例输出

a b f e d c 
a 

思路
这题因为自己的蠢卡了一段时间o(╥﹏╥)o,其实思路非常简单,我一开始就想到了,但是中序的输出一直不对,然后就改了书上的那种左右子树区间的写法来建树,结果一直卡在答案错误50。后来看了大佬【小魚兒.】的写法,结果发现居然跟我一开始的想法差不多,于是我就用单步调试去比较了一下才找到问题所在……

那么来说一下这题的思路,如果把样例在纸上画一画,就会发现,这通过先序序列建树的方法根本就是DFS嘛,先往左子树的方向建树,“死胡同”就是碰到字符"#",然后返回上一层,再往右子树的方向建。这里特别容易出错的一点是,千万不要在函数参数里写当前序列位置index!!因为我们在手写建树的时候,总是读字符串下一位来判断的,如果下一位是“#”,那么返回上一层,往另一个方向去填“#”的下一位,以样例来说,因为第一位不是“#”,说明不是空树,那么读入a是根,然后往它的左子树方向走,读下一位,下一位是“#”,是空树,说明a的左子树是空的,返回上一层,然后再往a的右子树方向走,于是读入“b”(如果函数参数里面写index的话,那么返回a的那一层,准备往a的右子树方向走的时候,它的index依然是a的下一位,即“#”)。
代码

#include<cstdio>
#include<math.h>
#include<algorithm>
#include<string>
#include<iostream>
using namespace std;
struct node{
	char data;
	node* lchild;
	node* rchild;
};
string pre;
int index;//要用全局的 
node* create(){//index不要写在函数里 
	if(pre[index]=='#'){
		index++;
		return NULL;
	}
	node* root = new node;
	root->data = pre[index];
	index++;
	root->lchild = create();
	root->rchild = create();//否则左子树建立完回退到右子树这里的话,index也会回退
	//参照dfs,如果index是函数参数,那么会回退到上一个岔路口的状态,所以index会回退
	//但是我们需要的是index一直从字符串左端往右走 
	return root;
}
void inorder(node* root){
	if(root==NULL) return;
	inorder(root->lchild);
	cout<<root->data<<' ';//每个字符后面都有一个空格 
	inorder(root->rchild);
}
int main(){
	while(cin>>pre){
		index = 0;
		node* root = create();
		inorder(root);
		cout<<'\n';
		pre.clear();
	}
	return 0;
}

小结

二叉树的遍历其他几题都很简单(明明是太常规了书上都有现成的代码),最后一题稍微卡了一下,参考了大佬们的题解并通过自己的单步调试发现了问题所在,可以说是对DFS有了更深刻的理解(递归返回上一层之后,所有状态都会回溯,就像回到过去一样hhh),所以要是想像最后一题那样对字符串一路从头读到尾而不回退的话,还是用全局变量叭~

发布了54 篇原创文章 · 获赞 27 · 访问量 4979

猜你喜欢

转载自blog.csdn.net/weixin_42257812/article/details/105508391