二叉树常见操作及练习题

写在前面(很重要)

hehe在二叉树这里,我们经常要使用递归来解决问题,所以深刻理解递归解决问题的过程是极为重要的,在这里我不详细展开递归的介绍,我想先让你们深刻理解递归是如何将问题缩小,最终拆解成小问题并解决的。
hehe我们可以举这样例子来理解:一个学校要统计报名参加六级考试的人数,校长将任务分给院长,院长就要求自己院的人数,院长就把任务分给辅导员,辅导员就要求自己管理班级的人数,辅导员又把任务分配给班长,班长就很轻松的得到自己班级的人数,班长汇总给辅导员,辅导员汇总给院长,院长汇总给校长,校长得到最终的结果。
hehe在上级向下级分配任务的过程就是“递”的过程,在不断的拆解问题,而下级将任务结果返回给上级就是“归”的过程,从而得到最终的结果。
这是函数递归具体介绍和相关练习帮助理解的文章想看具体有关函数递归内容的老铁请点该链接
hehe还要再声明的一点就是,在代码的注释中,由书写代码要注意的问题,和在实现过程中仍然需要注意的小细节,所以要仔细阅读注释,才能真正理解所有题目是如何实现的

一、二叉树常见操作

首先先将树的结构体介绍一下

typedef struct BTNode	//树的结点的结构体
{
	BTDataType x;		//存该结点的值    //BTDdataType是指存储的数据类型
	struct BTNode* leftchild;	//储存左孩子的地址
	struct BTNode* rightchild;	//储存右孩子的地址

}BTNode;

1.深度优先遍历(前、中、后序)

(1)前序遍历:对于每一个结点,先访问该结点本身,再去访问它的左孩子,最后再去访问它的右孩子。
在这里插入图片描述
1:先访问本身1,再去访问1的左子树
2:先访问2本身,再去访问2的左子树4
3:先去访问4本身,再去访问4的左子树
4:4的左子树为空,所以返回至4
5:再去访问4的右子树,右子树为空,所以返回至4,此时4已经访问结束,返回至2,再去访问2的右子树
6:先访问5本身,再去访问5的左子树
7:左子树为空,返回至5
8:再去访问5的右子树,右子树为空,所以返回至5,此时5已经访问结束,所以返回至2,2也访问结束,所以返回至1
9:再去访问1的右子树3,先去访问3本身
10:再去访问3的左子树,左子树为空,返回至3,再去访问3的右子树
11:3的右子树为空,返回至3,3已经访问结束,返回至1,1访问结束,完成遍历
就是始终保证一个原则,就是遇到每一个结点先访问自己,再去访问它的左子树,然后再先访问左子树的根,再去访问左子树的根的左子树。。。无限套娃。当遇到空结点时时就返回,再去访问右子树,对于右子树也是先访问它的根,再去访问根的左子树。。。当一个结点右子树也遍历结束,那么这个结点就遍历结束。返回至它的双亲结点。当整棵树的根的右子树也遍历结束,说明整个树遍历结束。

```c void PrevOrder(BTNode* root) { if (root == NULL) //如果访问到空树,直接返回 { printf("NULL "); return; } printf("%c ", root->x); //先访问根自身 PrevOrder(root->leftchild);//再访问根的左子树(会不断递归,直到叶子节点) PrevOrder(root->rightchild);//再访问根的右子树(会不断递归,直到叶子节点) } ```

(2)中序遍历:对于每一个结点,先访问该结点的左子树,再访问该节点本身,最后访问该节点的右子树
在这里插入图片描述

void InOrder(BTNode* root)
{
	if (root == NULL)	//访问到空树,直接返回
	{
		printf("NULL ");
		return;
	}
	InOrder(root->leftchild);//先去访问根的左子树(会不断递归,直到叶子结点)
	printf("%c ", root->x); //再访问根本身
	InOrder(root->rightchild);//最后访问根的右子树(会不断递归,直到叶子结点)
}

(3)后序遍历:对于每一个结点,先访问该结点的左子树,再访问该节点的右子树,最后访问该节点本身。

void PostOrder(BTNode* root)
{
	if (root == NULL)	//遇到空树直接返回
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->leftchild); //先访问根的左子树(直到叶子节点)
	PostOrder(root->rightchild); //再访问根的右子树(直到叶子节点)
	printf("%c ", root->x);		//最后访问根
}

2.求整棵树中结点个数

我们始终要牢记,在二叉树这里,我们能用递归就要使用递归,将大问题分解成一个一个小问题,简化问题,才能更加思路清晰的写代码实现相应功能。
而对于求结点个数的问题,我们可以拆解问题。
求:左子树的结点个数+右子树的结点个数+自身根

而左子树又可以分为:左子树的结点个数 = 左子树的左子树结点个数+左子树的右子树结点个数+左子树自身

右子树又可分为:右子树的结点个数 = 右子树的左子树结点个数+右子树的右子树结点个数+右子树自身

不断套娃套娃,就将问题分解成最后的叶子上。
在这里插入图片描述

int BTNodeSize(BTNode* root)
{
	//如果访问到空树,说明没有结点,那么值返回0
	if(root == NULL)	
		return 0//不是空树,就计算左子树个数+右子树个数+自身的1个
	return BTNodeSize(root->left) + BTNodeSize(root->right) + 1;
}

3.求叶子结点的个数

叶子结点就是左右孩子都是空的结点。也是不断拆解问题
树的叶子结点个数 = 左子树的叶子结点个数 + 右子树的叶子结点个数

左子树的叶子结点个数 = 左子树的左子树叶子结点个数 + 左子树的右子树叶子结点个数

右子树的叶子结点个数 = 右子树的左子树叶子结点个数 + 右子树的左子树叶子结点个数

不断套娃套娃

int BTLeafSize(BTNode* root)
{
	//访问到空结点,返回值0
	if (root == NULL)
		return 0;
	//如果是叶子结点  root->left == NULL root->right = NULL
	if (root->leftchild == NULL && root->rightchild == NULL)
		return 1;
	//计算对应左子树+右子树叶子结点并返回
	return BTLeafSize(root->leftchild) + BTLeafSize(root->rightchild);
}

4.求第K层结点的个数

hehe还是一样,直接去求第K层的结点个数对于二叉树来说是很难做到的,我还是需要用分治的思想,使用递归的方式来解决问题。
hehe求相对于第一层求第K层,就是先对于第二层求K-1层……直到K=1时,转变成求该层的结点个数,这样就把问题彻底拆解,变成小问题了。

在这里插入图片描述

int BTKLevelSize(BTNode* root, int k)	//k值代表层数
{
	if (root == NULL)	//当访问到空时,说明无,返回0
		return 0;
	if (k == 1)		//k == 1时说明,到达所求层,并且该层有一个所求结点,返回1
		return 1;
	return BTKLevelSize(root->leftchild, k - 1) + BTKLevelSize(root->rightchild, k - 1);	//k != 1说明还没到所求的层数,则返回该结点左右子树的第k-1层结点数
}

二叉树的OJ练习

1.单值二叉树

题目描述

在这里插入图片描述

问题分析:

hewh该题目是判断这棵树所有结点的值是否相同,按照大问题转化为小问题的思想,那就是判断,一个结点的值与它左右孩子的值是否相同,如果每一个结点的值都与自己左右孩子结点的值相同,那么整棵树每个结点的值都相同。
hewh所以,小问题就是判断“根节点”的值与左右孩子值是否相等,再判断,左右孩子值是否与左右孩子的左右孩子值相等,这样不断递归,就完成了整棵树的判断。

代码实现:

bool isUnivalTree(struct TreeNode* root)
{
//访问到空,直接返回真
    if(root == NULL)	
        return true;
    //首先要判断left是否为空,因为如果是空的话,无法解引用
    //再判断根与左孩子值是否相等
    //注:如果判断是相等,那么无法拿出结果,还要继续往下判断直到结束
    //所以我们需要判断是否不等,有一个不等就说明不是单值,直接返回false
    if(root->left && root->left->val != root->val)	
        return false;
    //右孩子与左孩子同理
    if(root->right && root->right->val != root->val)
        return false;
    //走到这里说明该结点与它的左右孩子值相等,这时就要继判断左右孩子的
    //左右孩子值与各自双亲结点值是否相等,就要递归。
    //因为有一个不相等就返回false,所以用&&连接
    return isUnivalTree(root->left) && isUnivalTree(root->right);
}

2.二叉树的最大深度

题目描述

在这里插入图片描述

问题分析:

hewh求整棵树的最大深度,就是求树的高度,还是按照将问题分解,问题小化的原则。求树的最大高度,那么就要去求左右子树的高度中最大的+1,就得到整棵树的高度。同样左右子树的高度就是它们各自的左右子树的高度的最大值+1……如此循环往复。
在这里插入图片描述

代码实现:

//求最大值的函数,用于求左右子树中高度最大的
int Max(int a,int b)
{
    return a>b?a:b;
}
int maxDepth(struct TreeNode* root){
	//如果是空,说明高度为0,返回0
    if(root == NULL)
        return 0;
    //返回左右子树中最大的+1(自己这一层)
    return Max(maxDepth(root->left),maxDepth(root->right))+1;
}

3.翻转二叉树

题目描述

在这里插入图片描述

问题分析:


hewh观察上图,我们可以很清楚发现,反转就是每个结点都要将自己的左右子树交换,这直接就给了我们递归的实现思路,就是交换一个结点的左右子树之后,再递归实现分别交换左右孩子的左右子树。而交换一个结点的左右子树的操作是很简单的,就是将这个结点的左孩子指针和右孩子指针指向的内容交换就ok。

代码实现:

struct TreeNode* invertTree(struct TreeNode* root){
	//如果是空,直接返回即可,不需要操作
    if(root == NULL)
        return root;
    //将根的左右子树交换
    struct TreeNode* left = root->left;
    struct TreeNode* right = root->right;
    root->left = right;
    root->right = left;
    //递归交右孩子的左右子树
    invertTree(right); 
    //递归交换左孩子的左右子树
    invertTree(left);
    return root;
}

4.相同的树

题目描述:

在这里插入图片描述

问题分析:

hewh这个题其实比较简单,就是有两棵树,把问题分解成:如果这两棵树的根相同,并且两棵树的左右子树分别相同,那么返回true,三部分(根、左子树、右子树)有一处不相等,那么就返回false。
hewh唯一需要注意的问题就是:空的处理。
1.一个是空,一个非空返回false
(1)第一颗树是空,第二棵树不是空
(2)第二棵树是空,第一棵树不是空
2.两个都是空,返回true

代码实现:

bool isSameTree(struct TreeNode* p, struct TreeNode* q){
	//两棵树都等于NULL为真
    if(p == NULL && q == NULL)   
        return true;
    //到这里只存在都不是空或者有一个为空的情况,所以如果有一个是空则为假
    if(q == NULL || p == NULL)  //只有一个为NULL才进入if语句里
        return false;
	//如果根不相等就直接返回false,根相等再去比较左右子树
    if(p->val != q->val)
        return false;
        //递归比较左右子树
        //因为左右子树都要相等才为真,有一个为假就是假,所以用&&连接
    return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
    
}

5.对称二叉树

问题描述:

在这里插入图片描述

问题分析:

hewh通过观察,就是将树反转得到一个对称树,如果原树与对称树相同,则原树对称
在这里插入图片描述
在这里插入图片描述
直接复用上面几个题的代码

代码实现:

//反转树
struct TreeNode* invertTree(struct TreeNode* root){
    if(root == NULL)
        return root;
    struct TreeNode* left = root->left;
    struct TreeNode* right = root->right;
    root->left = right;
    root->right = left;
    invertTree(right); 
    invertTree(left);
    return root;
}
//判等树
bool isSameTree(struct TreeNode* p, struct TreeNode* q){
    if(p == NULL && q == NULL)   //都等于NULL为真
        return true;
    //存在不为空
    if(q == NULL || p == NULL)  //只有一个为NULL才为真
        return false;

    if(p->val != q->val)
        return false;
    return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
    
}
//判别树是否对称
bool isSymmetric(struct TreeNode* root)
{
    struct TreeNode* tree1 = root;
    struct TreeNode* tree2 = invertTree(root);
    return isSameTree(tree1,tree2);
}

6.另一个树的子树

问题描述:

在这里插入图片描述

问题分析:

hewh这个题简单之处就是子树必须是一直到结束,不能仅仅是中间一段,所以这就是一个将问题十分简化的重要条件,给我们解决问题提供了极大的可能。
hewh所以问题就可以变成,每遇到一个结点,我就拿子树与该节点为树的这两棵树比较,是否相等,如果相等,说明是原树的子树,如果原树所有节点都被比较结束,还没有相等的,那么就不是原树的子树。(还是会复用到“相同的树”题目的代码)

代码实现


bool isSameTree(struct TreeNode* p, struct TreeNode* q){
    if(p == NULL && q == NULL)   //都等于NULL为真
        return true;
    //存在不为空
    if(q == NULL || p == NULL)  //只有一个为NULL才为真
        return false;

    if(p->val != q->val)
        return false;
    return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
    
}


//判断是否是子树
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
	//如果都到空了,说明不是子树,直接返回false
    if(root == NULL)
        return false;
    //如果不是空,那么就与子树比较是否相等,相等返回true
    if(isSameTree(root,subRoot))
        return true;
    //走到这里,说明,该节点不是空,且不相等,那么就判断该节点的左右子树
    //用||连接,因为左右子树中有一个是子树,那就说明是子树。
    return isSubtree(root->right,subRoot) || isSubtree(root->left,subRoot);
    
}

7.二叉树的前序遍历

问题描述:

在这里插入图片描述

问题分析:

hewh这个题的要求是,需要自己开辟一个数组,将树的内容通过前序遍历保存到数组中,最后将开辟的这个数组返回。
hewh1.我们首先需要知道树有几个结点,才能malloc开辟数组。
hewh2.前序遍历,先访问根再访问左右子树。注意,这里的访问不是打印,而是将内容放进数组中。
hewh3.既然要将内容放进数组,我们就需要知道到数组的哪个位置了。具体小细节在代码中具体说明。

代码实现:

//求结点个数
int size(struct TreeNode* root)
 {
     return root == NULL? 0 : size(root->left) + size(root->right) + 1;
 }
//分装的子函数
void _preorderTraversal(struct TreeNode* root,int* a,int* i)
{
	//前序遍历,遇到空,返回
    if(root == NULL)
        return;
    //不是空,就将数据放入数组
    a[*i] = root->val;
    (*i)++;
    //递归遍历左右子树
    _preorderTraversal(root->left,a,i);
    _preorderTraversal(root->right,a,i);
}



//问题主函数
//1.因为我们要在这个函数里开空间等准备工作,所以不能反复迭代这个函数,所以
//将递归这个小问题单独分装成一个子函数_preorderTraversal
int* preorderTraversal(struct TreeNode* root, int* returnSize){
	//由于遍历放到了子函数,我们就要时刻知道到数组的那个位置了
	//所以就需要一个变量i一直指向数组当前位置,来保存数据
    int i = 0;
    //我们要开空间,所以就要知道树中结点的个数,分装一个函数求结点个数
    *returnSize = size(root);
    int* a = (int*)malloc(sizeof(int)*(*returnSize));
    //子问题(前序遍历),需要1.树2.开辟的数组3.指向数组位置的变量(必须传地址,才能保证改变的始终是一个变量)
    _preorderTraversal(root,a,&i);

	//完成任务后,返回数组(题目要求)
    return a;
}

二叉树的后序遍历

二叉树的中序遍历

hewh这两道题跟上一道题的思路完全一致,代码逻辑也是完全一样,唯一的区别就是处理根的实际不一样。
hewh中序:左子树、根、右子树
hewh后序:左子树、右子树、根

//中序
void _preorderTraversal(struct TreeNode* root,int* a,int* i)
{
    _preorderTraversal(root->left,a,i);
     if(root == NULL)
        return;
    a[*i] = root->val;
    (*i)++;
    _preorderTraversal(root->right,a,i);
}

//后序
void _preorderTraversal(struct TreeNode* root,int* a,int* i)
{
    _preorderTraversal(root->left,a,i);
    _preorderTraversal(root->right,a,i);
    if(root == NULL)
        return;
    a[*i] = root->val;
    (*i)++;
}

结语

hewh这篇博文是我将自己在学习二叉树这块内容时,遇到的难题与难于实现的二叉树操作方面的总结。我知道肯定还有不少题在文章中没有涉及。但是这篇博文的最大意义就是,能够通过各式各样的例子,让我们更加深刻的理解,递归对于二叉树操作的重大意义。对于二叉树的逻辑结构和存储结构天生是极度适合递归的逻辑的。因此恰当的使用递归便可以使二叉树的大问题划分为小问题,在正常的顺序逻辑下,便可以轻松解决问题。但是最重要的及时抓住递归过程中的递归条件和边界。因为在不断“递”的过程中,要不断逼近终止条件,才可以开始“归”。
hewh老铁们,对于二叉树或者数据结构这里还有什么问题可以私信我哦,我一定会尽力解答。让我们共同努力哦~

  • 20
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
### 回答1: 南大遥感复试的C语言上机操作题主要涉及计算机编程和数据处理方面的内容。根据个人经验,可能涉及以下几个方面的题目。 1. 基本的程序设计题目:可能会考察对C语言基本语法和流程控制语句的理解和运用。例如,计算两个数的和、差、积、商的程序编写;求一个数的平方和立方的程序编写等。 2. 数组和字符串的处理题目:可能会考察对C语言数组和字符串的操纵能力。例如,给定一个数组,求出其中的最大值和最小值;给定一个字符串,统计其中某个字符出现的次数等。 3. 文件操作题目:可能会需要读取文件中的数据,并进行相关的处理。例如,读取一个文本文件中的数字,并统计其中的奇数个数、偶数个数等。 4. 数据结构相关的题目:可能会考察对C语言中数据结构的使用和实现。例如,模拟实现一个队列或栈,并进行相应的操作等。 对于这些上机题目,需要熟悉C语言的语法和常见的编程技巧,合理使用控制语句和数据结构,能够通过编码将问题转化成程序,并得到正确的结果。解答时需要注意代码的规范性和效率性,在有限的时间内完成题目,展示自己的编程能力。 ### 回答2: 南大遥感复试中的C语言上机操作题包括以下内容: 1. 字符串操作:考察对字符串的处理能力。可能涉及到字符串的输入输出、字符串连接、字符串截取等操作。 2. 数组及指针操作:可能会涉及到数组的初始化、数组元素的读取和修改、指针的使用等操作。 3. 控制流程:包括条件语句、循环语句等。可能会涉及到if语句、for循环、while循环的使用。 4. 文件操作:可能会要求读取文件中的数据、写入数据到文件中等操作,涉及到文件的打开、读写及关闭等操作。 5. 函数的使用:可能会要求完成特定的功能,需要定义函数并正确调用。可能会考察函数的返回值、参数传递等概念。 6. 数据结构的使用:可能会要求使用相关的数据结构,如链表、二叉树等。涉及到数据结构的创建、插入、删除等操作。 在复试中,考生往往需要在限定时间内完成上述操作题。在答题过程中,要注意编程规范性、程序逻辑的清晰性和正确性。同时还需要注意代码的可读性和注释的规范性。 对于准备参加南大遥感复试的考生来说,建议提前做好充分的准备。熟悉C语言的基本语法和常用操作,多进行编程练习和实践。另外,了解遥感相关的知识和技术,在复试中能更好地应对与遥感相关的问题。 ### 回答3: 南大遥感复试中的C语言上机操作题通常要求考生根据给定的要求编写程序。这些题目旨在考察考生对C语言基本语法和常见操作的掌握程度,以及解决问题的能力。 在上机操作题中,我们可能会遇到一些数据处理或算法实现的题目。例如,题目可能要求我们读取一个输入文件,对其中的数据进行处理,然后把结果输出到一个输出文件中。在这个过程中,我们可能需要使用循环、条件判断、数组等基本的C语言语法。 除了基础的数据处理题目,我们还可能会遇到一些涉及字符串操作、文件操作、指针等高级概念的题目。这些题目要求我们能够熟练地使用相关的C语言库函数和语法,灵活地使用指针来处理数据。 解决上机操作题的关键是理解题目要求、设计合理的算法思路以及实现正确的代码。在编写代码的过程中,我们需要考虑边界情况、错误处理和代码的可读性等方面。正确的代码应该能够满足题目的要求,并且能够处理各种输入情况。 总的来说,南大遥感复试中的C语言上机操作题旨在考察考生对C语言的掌握程度和解决问题的能力。通过这些题目,我们可以展示我们在编程方面的技能和经验,同时也可以提升我们的编程能力和思维能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值