已经学习过了二叉树的基础知识,那么间奏部分就来感受一下二叉树部分的OJ题吧。
单值二叉树
单值二叉树很多人第一个想到的做法就是递归遍历整棵树看各结点的值是否相等,但是麻烦了许多。
对于不方便遍历的题,我们最好使用分治的思想去做
以这道题为例,可以
检查每个结点和他的孩子,分成三部分:
1.自己
2.左子树
3.右子树
因为相等具有传递性,所以我们可以这样做。
bool isUnivalTree(struct TreeNode* root)
{
if(root==NULL)
{
return true;
}
if(root->left&&root->left->val!=root->val)
{
return false;
}
if(root->right&&root->right->val!=root->val)
{
return false;
}
return isUnivalTree(root->left)&&isUnivalTree(root->right);
}
相同的树
还是利用递归分治的思想
分成
1.自己
2.左子树
3.右子树
对应的部分与对应的部分比
bool isSameTree(struct TreeNode* p, struct TreeNode* q)
{
//都为空
if(p==NULL&&q==NULL)
{
return true;
}
//其中一个为空
if(p==NULL||q==NULL)
{
return false;
}
//都不为空且不相等
if(p->val!=q->val)
{
return false;
}
return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
}
二叉树的前序遍历
前序遍历好哇,我可会。
但是这个题哥,你没事吧?
你要我返回数组是怎么个意思呢?这个接口都是干啥的呢?这returnSize又是在难为谁?
这题要求我们完成前序遍历,将值存放在数组中,这个数组需要我们自己用malloc开辟。
那么开辟空间要开辟多少呢?
直接开辟一个超级大的空间可不可以呢?
int* a=(int*)malloc(10000000);
可以是可以,但是代码太挫了,属于硬编码,在实践中很忌讳(在没有其他办法的时候才会采用硬编码)
我们可以写一个计算树结点个数的函数,来帮助我们完成动态内存开辟(虽然时间换空间,但比直接写死的代码好得多)
int TreeSize(struct TreeNode*root)
{
if(root==NULL)
{
return 0;
}
return TreeSize(root->left)+TreeSize(root->right)+1;
}
那returnSize是干嘛的呢?
在力扣上有个特点,返回一个数组后不知道这个数组有多大,所以力扣凡是返回数组的题都会配备一个参数returnSize,这个参数即为数组大小
int* preorderTraversal(struct TreeNode* root, int* returnSize)
为什么returnSize的类型是整形指针呢?很简单,形参是实参的一份临时拷贝,对形参的修改不影响实参。这种参数叫输出型参数,其实很早之前我们就已经接触过输出型参数了,那就是:scanf,比如scanf从IO流里面拿一个整形是需要传地址过去的,那为什么要传整形的地址呢?
在控制台终端的流里面拿到变量,若传值过去,则scanf里面的修改对外面没有影响,所以要进行传址调用。
由于我们不好递归当前函数,所以我们可以再写一个前序遍历的函数(OJ题是会把我们新增的函数和后台的代码合并再去运行的)
于是自信敲起代码:
void preorder(struct TreeNode*root,int*a,int i)
{
if(root==NULL)
{
return;
}
a[i++]=root->val;
preorder(root->left,a,i);
preorder(root->right,a,i);
}
自信提交,会发现:诶?没过。
酚烯一波:
由于每个栈帧都有一个i,下一层的i++不会影响上一层,下一层是上一层的拷贝,而我们期望的是i一直往后走。
那么尝试一下全局变量呢?
全局变量并不在栈区存储,而是存储在数据段。
但是会发现还是过不了,发生了越界
int i=0;
void preorder(struct TreeNode*root,int*a)
{
if(root==NULL)
{
return;
}
a[i++]=root->val;
preorder(root->left,a);
preorder(root->right,a);
}
问题出在哪里呢?其实还是OJ题的测试会运行不止一遍,而由于i是全局变量,会进行累加从而出现越界,在每次使用前置0就好了:
int TreeSize(struct TreeNode*root)
{
if(root==NULL)
{
return 0;
}
return TreeSize(root->left)+TreeSize(root->right)+1;
}
int i=0;
void preorder(struct TreeNode*root,int*a)
{
if(root==NULL)
{
return;
}
a[i++]=root->val;
preorder(root->left,a);
preorder(root->right,a);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize)
{
int n=TreeSize(root);
int* a=(int*)malloc(sizeof(int)*n);
*returnSize=n;
i=0;
preorder(root,a);
return a;
}
但是能不用全局变量尽量就不用,全局变量涉及到Linux中线程安全的问题:多线程中函数交给两个线程去统计两棵树,会在同一个变量上累加,最好是用局部变量,没有线程安全的问题:
int TreeSize(struct TreeNode*root)
{
if(root==NULL)
{
return 0;
}
return TreeSize(root->left)+TreeSize(root->right)+1;
}
void preorder(struct TreeNode*root,int *a,int *pi)
{
if(root==NULL)
{
return;
}
a[(*pi)++]=root->val;
preorder(root->left,a,pi);
preorder(root->right,a,pi);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize)
{
int n=TreeSize(root);
int* a=(int*)malloc(sizeof(int)*n);
*returnSize=n;
int i=0;
preorder(root,a,&i);
return a;
}
二叉树的创建与遍历
通过前序遍历数组构建二叉树,首先先来手动复原一下这棵二叉树:
#include <stdio.h>
#include<stdlib.h>
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
} TreeNode;
TreeNode* TreeCreate(char* a, int* pi)
{
if (a[*pi] == '#')
{
(*pi)++;
return NULL;
}
TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
if (root == NULL)
{
perror("malloc fail");
exit(-1);
}
root->data = a[(*pi)++];
root->left = TreeCreate(a, pi);
root->right = TreeCreate(a, pi);
return root;
}
void InOrder(TreeNode* root)
{
if(root!=NULL)
{
InOrder(root->left);
printf("%c ",root->data);
InOrder(root->right);
}
}
int main()
{
TreeNode* root;
char s[101]={0};
scanf("%s",s);
int i=0;
root=TreeCreate(s,&i);
InOrder(root);
return 0;
}
思路就是:
1.为空则返回空,继续遍历
2.不为空则开辟空间,按照前序遍历创建一棵二叉树
3.利用中序遍历输出这棵二叉树