递归函数
1、确定递归函数的参数和返回值:因为要打印出前序遍历节点的数值,所以参数里需要传入vector来放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值,所以递归函数返回类型就是void,代码如下:
void traversal(TreeNode* cur, vector<int>& vec)
2、确定终止条件:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要结束了,所以如果当前遍历的这个节点是空,就直接return,代码如下:
if (cur == NULL) return;
3、确定单层递归的逻辑:前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中节点的数值,代码如下:
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
前序遍历顺序为中左右,因此在单层递归逻辑中首先处理当前结点,也就是进行vec.push_back(cur->val);
的操作,最先处理的是根结点,最后被处理的是树最右端的结点。而后续遍历顺序为左右中,因此最先处理的是最左边的结点。
常用的技巧:
1、利用一个pre指针记录当前遍历节点cur的前一个节点。
pre指针的使用技巧在二叉树:搜索树的最小绝对差 (opens new window)和二叉树:我的众数是多少? (opens new window)都提到了,这是常用的操作手段。
2、如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树 。
搜索一条边的写法:
if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;
例如二叉树:公共祖先问题就是标准的搜索一条边的写法,遇到递归函数的返回值,如果不为空,立刻返回。
搜索整个树写法:
left = 递归函数(root->left);
right = 递归函数(root->right);
以对称二叉树为例,
搜素整棵树的写法
class Solution {
public:
bool compare(TreeNode* left, TreeNode* right) {
// 首先排除空节点的情况
if (left == NULL && right != NULL) return false;
else if (left != NULL && right == NULL) return false;
else if (left == NULL && right == NULL) return true;
// 排除了空节点,再排除数值不相同的情况
else if (left->val != right->val) return false;
// 此时就是:左右节点都不为空,且数值相同的情况
// 此时才做递归,做下一层的判断
bool outside = compare(left->left, right->right); // 左子树:左、 右子树:右
bool inside = compare(left->right, right->left); // 左子树:右、 右子树:左
bool isSame = outside && inside; // 左子树:中、 右子树:中 (逻辑处理)
return isSame;
}
bool isSymmetric(TreeNode* root) {
if (root == NULL) return true;
return compare(root->left, root->right);
}
};
搜索一条边的写法如下,与搜索整棵树的区别在于:1、当搜索到不满足对称的结点,这种解法会立即返回结果。2、搜索一条边的解法使用的是前序遍历,思想在于若当前结点不满足对称条件,就结束遍历,而搜索整棵树的解法是后续遍历,也可以说是回溯的思想,当某个结点不满足对称条件时,会将false传递到他的父结点,而父节点通过left或right变量进行承接。
class Solution {
public:
bool travel(TreeNode* leftnode,TreeNode* rightnode){
if(leftnode==nullptr&&rightnode==nullptr) return true;
if(leftnode!=nullptr&&rightnode==nullptr) return false;
if(leftnode==nullptr&&rightnode!=nullptr) return false;
if(leftnode->val!=rightnode->val) return false;
if(!(travel(leftnode->left,rightnode->right))) return false;
if(!(travel(leftnode->right,rightnode->left))) return false;
return true;
}
bool isSymmetric(TreeNode* root) {
return travel(root->left,root->right);
}
};
3.终止条件的设立,通常都是指针遍历到空指针(nullptr)时设立终止条件:
1、若仅仅需要关心不为空的结点,那么终止条件设为if(cur==nullptr) return;
,例如在二叉树的前序遍历这一题中的代码如下,因为只需按照前序遍历顺序收集不为空结点的值,因此当指针指向空指针时仅代表此时应当结束递归:
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
2、而在需要指针指向空结点时,仍需进行一些判断时,此时的终止条件就需要适时进行变化,例如也是在对称二叉树这一题中,终止条件设立如下,这里当处理到空结点时做的就不仅仅是递归终止,而是根据不同的leftnode和rightnode的情况返回不同的函数返回值,这里的终止条件逻辑为当leftnode和rightnode全为空指针时代表两结点相等,但此时已经遍历至空结点,因此应该结束递归,故返回true,当leftnode和rightnode只有一个为空结点或leftnode和rightnode的值不相等时显然两结点不相等,此时应当终止递归,返回false,而终止条件下面的代码就是要去处理单层逻辑的代码了:
if(leftnode==nullptr&&rightnode==nullptr) return true;
if(leftnode!=nullptr&&rightnode==nullptr) return false;
if(leftnode==nullptr&&rightnode!=nullptr) return false;
if(leftnode->val!=rightnode->val) return false;
4、前序遍历包含遍历即处理的思想,且遍历完左节点回到中间结点时有一个回退的操作,而后序遍历则是先遍历左右结点,根据左右结点的结果得到中间结点的结果,因此要注意两种遍历方法的思想。
在求二叉树的最大深度这道题中,后续遍历和前序遍历的写法如下,要注意前序遍历中的当遍历完左节点回退回根结点时的回溯操作,这个操作在很多道题中都有体现:
//后序遍历,先遍历左右结点,然后根据左右结点得到中间结点的值,这里的求深度相当于先求根结点左孩子的深度,求法就是从底向上进行回溯。再求根结点右孩子的深度,最后求根结点深度。
class solution {
public:
int getdepth(TreeNode* node) {
if (node == NULL) return 0;
int leftdepth = getdepth(node->left); // 左
int rightdepth = getdepth(node->right); // 右
int depth = 1 + max(leftdepth, rightdepth); // 中
return depth;
}
int maxDepth(TreeNode* root) {
return getdepth(root);
}
};
//前序遍历
class Solution {
public:
int maxdepth=1;
void travel(TreeNode* root,int curdepth){
if(root==nullptr) return;
if(curdepth>maxdepth) maxdepth=curdepth;
if(root->left){
curdepth++;
travel(root->left,curdepth);
curdepth--;
}
if(root->right){
curdepth++;
travel(root->right,curdepth);
curdepth--;
}
}
int maxDepth(TreeNode* root) {
if(root==nullptr) return 0;
int curdepth=1;
travel(root,curdepth);
return maxdepth;
}
};
5.当处理的二叉树是二叉搜索树,要注意到中序遍历(左中右)得到的结点值序列应该是单调不减的这一性质,在二叉搜索树中的搜索,验证二叉搜索树,二叉搜索树中的众数这几道题中可以体现;另外要注意,因为二叉搜索树是有序的,所以可以利用这一点来控制树的遍历方向,在二叉搜索树中的插入操作可以体现出来。
6、充分反映出递归思想的题目,可以看看修剪二叉搜索树,这道题去除范围外的节点使用的就是前序遍历,使用递归很好的去除了不需要的节点。