虽然二叉树用处不大,但为帮助你打开思维。
1.二叉树的构建及遍历
- 分析:
- 牛客题要自己写main,用数组创建树,用递归方便,从上到下建树,中序遍历很好写。主要是怎么写建树的递归。
- 主函数:接收一个字符串就行了,所以用%s,数组大小开100,因为说长度不超100。那就用char arr[100]。
- 递归过程中,需要访问数组需要不断后移,在递归调用如果传参,用地址。
因为create(left, i), create(right, i)
的写法,因为i在调用深入后,i在left子树中会变化,但left做完返回后,同一级的right中的i没有变化。所以不能用临时变量,用传地址的方法。
- 思路:
- 递归出口: 如果数组当前访问到的值是“#”,说明这棵树到底了,使数组指针往后且返回NULL即可。
- 递归主体:先malloc当前节点,再递归到左右子树,因为数组是层次遍历的,所以一个节点后挨着的是它左右孩子。对left、right递归,然后返回当前节点。
- 做法步骤:
- 写树的结构体:
注意树节点类型是char。- 递归树:
出口:访问到"#"即可。
主体:当前、左右、返回。- 中序遍历:
先判断是不是出口,即当前是不是NULL。NULL就return。
- 代码:
#include<stdio.h>
#include<malloc.h>
typedef char BTDataType;
typedef struct BTNode
{
BTDataType data;
struct BTNode* left;
struct BTNode* right;
}BTNode;
BTNode* BTreeCreate(char a[], int* pi)
{
if (a[*pi] == '#')
{
(*pi)++;
return NULL;
}
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("MALLOC ERR \n");
exit(-1);
}
node->data = a[*pi];
(*pi)++;
node->left = BTreeCreate(a, pi);
node->right = BTreeCreate(a, pi);
return node;
}
void Inorder(BTNode* root)
{
if (root == NULL)
return;
Inorder(root->left);
printf("%c ", root->data);
Inorder(root->right);
}
int main()
{
char str[100];
int i = 0;
scanf("%s", str);
BTNode* root = BTreeCreate(str, &i);
Inorder(root);
return 0;
}
- 遇到的错误:
-
栈溢出:
我查看了pi的值,发现pi不变,看a[*pi],也不变。
恍然大悟:*pi没有++。 -
访问越界
treenodeType应该是char,而不是int。
解释:如果我BTNodeDataType写成int类型,而传参中的数组中存char都是1字节。而因为树Nodetypeint类型一次读取4字节,结果在造树的时候,a数组值不够读,必会越界。
- 递归的图示:递归过程是如何返回的,走到哪里返回的。
leetcode110. 平衡二叉树
- 分析:
- 左右子树高度差小于等于1才符合。
- 所以要求高度差。
- 树的题考虑递归。
- 思路:
- 对于求树高:
【递归法:】
a.出口:当前点NULL,返回0
b. 主体:返回左右孩子高度大的+1。- 验证BalanceTree:
出口:root == NULL :返回true;
核心:递归中重要的一环是:写出每个子问题怎么做?
求左右子树高之差的绝对值:
abs(HL-HR); >1 则 false,小于则正确- 但是,上述核心中,小于则返回正确只是判断了当前情况,并没有递归下去。
所以,如何递归?,
一般地,当前节点不符合情况,返回错误,如果当前节点符合就往儿子上判断,儿子上如果正确,会通过递归出口返回正确,儿子中有不符合的,会通过当前节点中所写的错误情况而返回错误,所以递归的出口,是真正的出口,且是递归唯一的出口。
- 代码:
typedef char BTDataType;
typedef struct BTNode
{
BTDataType data;
struct BTNode* left;
struct BTNode* right;
}BTNode;
int TreeHeight(BTNode* root)
{
if (root == NULL)
return 0;
int l = TreeHeight(root->left)+1;
int r = TreeHeight(root->right) + 1;
return l > r ? l : r;
}
// 左右子树高度之差:是从高到低还是从低到高,
bool BalanceTree(BTNode* root)
{
if (root == NULL)
return true;
int LH = TreeHeight(root->left);
int RH = TreeHeight(root->right);
if (abs(LH - RH) > 1)
return false;
else
return BalanceTree(root->left)&& BalanceTree(root->right);
}
- 收获【对递归题的感悟】:
- 递归中的出口是唯一出口。
- 子问题中一般只关注错误 ,错误时返回错误,正确时递归去孩子。如上面代码中,>1,return false;否则:return Balance(root->left)&&Balance(root->right);
100. 相同的树:判断两棵树是否相同
- 分析:
- 树的题用递归
- 判断两树是否相等,依次比当前节点值即可。
- 做法步骤:
- 递归出口:
两个树节点都不存在,说明比完了。返回true。
两个树一个存在一个不存在,返回false
两个树值不相等,返回false。- 递归主体:
去做每个节点的问题:
值不相等时,返回了错。
值相等时,递归去左右孩子做比较。
- 代码:
bool isSameTree(struct TreeNode* r1, struct TreeNode* r2)
{
if (r1 == NULL && r2 == NULL)
return true;
if (r1 == NULL && r2)
return false;
if (r2 == NULL && r1)
return false;
if (r1->val != r2->val)
return false;
return isSameTree(r1->left, r2->left) && isSameTree(r1->right, r2->right);
}
- 收获:
从上一题我我已经明白了递归的关键,就是错误直接返回,正确递归去子问题再判断。
leetcode572. 另一棵树的子树
- 分析:
- 查某棵树是否包含另外一颗,是要判断它的子树每个节点是否存在与它相等的部分。
- 这是上一题的提升:依次遍历这个树,然后判断这个树是否存在以某个节点为根的和这个子树完全相等的部分
- 这个题特别在对时才返回,不对时,做递归。
- 思路:
- 上一题的判断相同的代码copy过来
- 出口:
递归主体:以当前节点为root和整个被检查的树做比较。
- 遇到的疑惑:
能走到判断点不存在的地方,我怕左边一样,右边不一样怎么办?这个要画调用图。如果只是暂时不存在呢?【这个其实是上个题留下的疑惑,一会再看。】
这个题,每个点都对一个完整的树做对比,如果他俩同时空,那可以返回true。
错误1。
- 错的思路和代码和错误的代码
想错的地方在isSname:
- 【出口判断上有错】当前节点处的val和根节点的val不等就返回false,不可以。因为当前节点不一样不能返回false,因为当前节点不存在不能说明它的孩子中不存在包含这个的部分。【一个点错,不是所有点都错了】。
- 【思路上的大问题】递归部分(对子问题没有正确认识,应该对每个节点做比较):这里递归部分写错了,思路是检查当前点是否符合情况,符合就返回对,不符合才做递归,这里根本没有递归。核心递归经过所有点,去做isSame判断。
- 这个题是反着玩:对的点就返回对,错的点就递归。
bool isSameTree(struct TreeNode* r1, struct TreeNode* r2)
{
if (r1 == NULL && r2 == NULL)
return true;
if (r1 == NULL && r2)
return false;
if (r2 == NULL && r1)
return false;
if (r1->val != r2->val)
return false;
return isSameTree(r1->left, r2->left) && isSameTree(r1->right, r2->right);
}
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
if(subRoot == NULL)
return true;
if (root == NULL && subRoot)
return false;
if (root->val != subRoot->val)
return false;
return isSameTree(root->left, subRoot) || isSameTree(root->right, subRoot);
}
错误2.
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) {
if (isSameTree(root, subRoot))
return true;
return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}
- 错误思想:
- 错在了当前相同就返回,但是没有返回错误的地方。
- 所以应该返回当前点的结果和对当前左右孩子的结果的递归。
- 左右孩子存在,就对左右孩子做递归。
错误3:
编译没通过。
bool isSameTree(struct TreeNode* r1, struct TreeNode* r2)
{
if (r1 == NULL && r2 == NULL)
return true;
if (r1 == NULL && r2)
return false;
if (r2 == NULL && r1)
return false;
if (r1->val != r2->val)
return false;
return isSameTree(r1->left, r2->left) && isSameTree(r1->right, r2->right);
}
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) {
if (isSameTree(root, subRoot))
return true;
if(root->left)
bool L = isSubtree(root->left, subRoot);
if(root->right)
bool R = isSubtree(root->right, subRoot);
return L || R;
}
- 原因:
因为变量在if中,如果if不成立,L和R不存在。
- 改正后的代码:【把L、R定义提出来。】
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) {
if (isSameTree(root, subRoot))
return true;
bool L = false;
bool R = false;
if(root->left)
L = isSubtree(root->left, subRoot);
if(root->right)
R = isSubtree(root->right, subRoot);
return L || R;
}
+ 正确代码1:
bool isSameTree(struct TreeNode* r1, struct TreeNode* r2)
{
if (r1 == NULL && r2 == NULL)
return true;
if (r1 == NULL && r2)
return false;
if (r2 == NULL && r1)
return false;
if (r1->val != r2->val)
return false;
return isSameTree(r1->left, r2->left) && isSameTree(r1->right, r2->right);
}
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) {
if (root == NULL && subRoot == NULL)
return true;
if (root == NULL && subRoot)
return false;
return isSameTree(root, subRoot) || isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}
-
如果树root和subroot都空呢?
不存在,因为题上有限定, 它们都至少有一个节点,所以如果遍历到root==NULL时,尽管返回false即可。 -
因为root和subroot都至少有一个节点,所以如果root为NULL,直接返回false。
+ 正确代码2.(1的改进)
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) {
if (root == NULL)
return false;
return isSameTree(root, subRoot) || isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}
- 特别点:这个递归题是或的关系,所以遇对就返回,遇错就递归,出口很简单,就是root不存在,因为原始的root和subtree都至少1个点。
- 这个题特别在遇到对就返回,错误就递归。【因为它是有一个点符合就行,是或关系】。或关系,或关系。
- 习惯检查当前结果和递归左右子节点的或结果返回。
- 收获:
- 做递归第一步判断出口和子问题。
- 第二步判断是否是与问题还是或问题。
leetcode101. 对称二叉树:
- 分析:
- 粗略分析,对称比较的对象的是左右孩子,所以应该传入两个参数,而leetcode给了一个参,所以还需要子函数去做递归,所以这个题的关键是:写子函数去比较两个树是否对称相等。
- 子函数是一个与问题,那么有错误就返回,对就继续往下走。
- 思想:
- 递归出口:【找错不找对】
两树p、q是否对称相等:
>> p、q都不存在,对【都空肯定对称】
>> p、q值不相等,错【当前点显然直接不等】
>> 两个树根一个存在,一个不存在,错 【一个有,一个无】- 递归主体:
>>比较两个树的左右、右左是否相等。- 让主函数,调用这个子函数去做判断。且注意裁枝的递归:根不存在,直接false。
- 代码:
bool isSymmetricTree(struct TreeNode* r1, struct TreeNode* r2) {
if (r1 == NULL && r2 == NULL)
return true;
if (r1 == NULL || r2 == NULL)
return false;
if (r1->val != r2->val)
return false;
return isSymmetricTree(r1->left, r2->right) && isSymmetricTree(r1->right, r2->left);
}
bool isSymmetric(struct TreeNode* root) {
if (root == NULL)
return true;
return isSymmetricTree(root->left, root->right);
}
- 收获:
- 这个题是之前单个函数写递归的提升,需要考虑递归的是谁?某些递归题,需要写出一个子函数,比如这里去递归树的左右孩子。
leetcode226. 翻转二叉树
- 分析:
- 翻转一个二叉树, 需要翻转左子树和右子树,然后左子树再翻转自己,右子树再翻转自己。
- 因为要返回根,所以借助一个子函数做翻转,最后返回一个总函数节点即可。
- 子函数出口:
NULL时停止。- 子函数当前操作主体:左右子树互换。
递归部分:对左子树调用该函数。对右子树调用该函数。- 主函数:当前root为NULL,不用翻转。最后返回根。
- 代码:
void InvertNode(struct TreeNode* root)
{
if (root == NULL)
return;
struct TreeNode* r1 = root->left;
root->left = root->right;
root->right = r1;
if (root->left)
InvertNode(root->left);
if(root->right)
InvertNode(root->right);
}
struct TreeNode* invertTree(struct TreeNode* root){
InvertNode(root);
return root;
}
- 改进:
struct TreeNode* invertTree(struct TreeNode* root) {
if (root == NULL)
return NULL;
struct TreeNode* tmp = root->left;
root->left = root->right;
root->right = tmp;
// 对已左
root->left = invertTree(root->left);
root->right = invertTree(root->right);
return root;
}
- 上面思路犯的错:
对当前的节点已经做了翻转后,对以左儿子为根的树做翻转,再对以右儿子为根的树做翻转。所以root->left = inverse(root->left),root->right = inverse(root->right);
leetcode104. 二叉树的最大深度
- 分析:
出口:根处返回0,
递归主体:如果不是根,就返回max:左右孩子高度+1。
注意max(左右孩子高度)+1
int maxDepth(struct TreeNode* root) {
if (root == NULL)
return 0;
int L = maxDepth(root->left);
int R = maxDepth(root->right);
return L > R ? L+1 : R+1;
}
leetcode 965. 单值二叉树
- 分析
- 这是一个递归与问题
- 出口:当能深入到NULL节点,说明可以返回true。不一样则返回false。
- 主体:递归去左右孩子,且返回左右孩子的与结果。
- 代码(一)
思路:带着根节点,去访问每一个节点。遇到不一样就返回错,一样就继续递归去检查孩子。
bool isSameval(struct TreeNode* root, int val)
{
if (root == NULL)
return true;
if (root->val != val)
return false;
return isSameval(root->left, val) && isSameval(root->right, val);
}
bool isUnivalTree(struct TreeNode* root) {
if (root == NULL)
return true;
return isSameval(root, root->val);
}
- 代码(二)
思路:总体和第一个一样,但是只用一个函数,把左右孩子的结果,合并起来。
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;
bool L = isUnivalTree(root->left);
bool R = isUnivalTree(root->right);
return L && R;
}