二叉树(一)目录
基础概念
二叉树的种类
1.满二叉树
2.完全二叉树
3.二叉搜索树
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树
4.平衡二叉搜索树
它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
stl底层源码非常值得读;算法复杂度非常值得分析,后面的学习尽量都去考虑这两点。
二叉树的存储方式:
1.链式存储
2.顺序存储
二叉树的遍历方式
1.深度优先遍历:先往深走,遇到叶子节点再往回走。
前序遍历(递归法,迭代法)(中左右)
中序遍历(递归法,迭代法)(左中右)
后序遍历(递归法,迭代法)(左右中)
2.广度优先遍历:一层一层的去遍历。
层次遍历:迭代法
这两种遍历是图论中最基本的两种遍历方式。
1.二叉树递归遍历
这道题我完全按照代码随想录的思路和想法来做的,确实比较简洁。
代码随想录中给出了写递归遍历的思维方法:
1.确定递归函数的参数和返回值
2.确定终止条件
3.确定单层递归的逻辑
(第1步是整体思路的总结,确定递归函数参数和返回值,第2,3步则是递归函数内部编写的逻辑)
方法论还需要多实践
注意:
按照代码随想录中的递归思路:
class TreeNode {
public:
int val;
TreeNode* left;
TreeNode* right;
TreeNode() :val{ 0 }, left{ nullptr }, right{ nullptr }{};
TreeNode(int x) :val{ x }, left{ nullptr }, right{ nullptr }{};
TreeNode(int x, TreeNode* left, TreeNode* right) :val{ x }, left{ left }, right{ right }{};
};
class Solution {
public:
void Traversal(TreeNode* root, vector<int>& vec) {
//考虑停止条件
if (root == nullptr) {
return;
}
//顺序执行
vec.push_back(root->val);//先中
Traversal(root->left, vec);//再左
Traversal(root->right, vec);//再右
}
vector<int> preorderTraversal(TreeNode* root) {
//我的任务是遍历整个二叉树
//首先是前序遍历,递归方法
//什么时候是用递归呢?需要循环操作的地方。在遍历二叉树的时候,每到一个结点的时候都得访问左右两个结点,这就构成了循环递归的条件
//1.考虑递归函数的输入输出,最后得到的数据需要存储在一个数组中去,在preorderTraversal内部定义vector向量的话,使用preorderTraversal作为递归函数的话,
//就变成vector向量拼接了
//先尝试代码随想录的方法
vector<int> result;
Traversal(root, result);//输入结点和存储向量
return result;
}
};
2.二叉树迭代遍历
这个也是跟着代码随想录的方法来的。其中前序遍历相对比较容易理解,中序遍历和后序遍历就不太好理解了,理解起来并不够直观。等后期熟悉之后再来细致理解一下二叉树的三种迭代遍历。
2024.8.24日复习观察:
前序遍历(中左右)中序遍历(左中右)后续遍历(左右中),代码的逻辑需要画图找规律,前序遍历和中序遍历通过找规律可以有很好的效果,然后后序遍历使用的是(按照中右左遍历之后再将vector数组翻转,具有一定的规律性,后面复习时特别背记一下就好了)
前序遍历
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
//遍历二叉树,这次用迭代法去求解
//用栈的思路
stack<TreeNode*> tempStack;
vector<int> result;
if (root == nullptr) {
return result;
}
tempStack.push(root);//先把根结点压进去
while (!tempStack.empty()) {
TreeNode* node = tempStack.top();
result.push_back(node->val);
tempStack.pop();
if (node->right) tempStack.push(node->right);
if (node->left) tempStack.push(node->left);
}
}
};
中序遍历:
class Solution {
public:
vector<int> midorderTraversal(TreeNode* root) {
//遍历二叉树,这次用迭代法去求解
//用栈的思路
stack<TreeNode*> st;
vector<int> result;
TreeNode* node = root;
while (node != nullptr || !st.empty()) {
if (node != nullptr) {
st.push(node);
node = node->left;
}
else {
node = st.top();
st.pop();
result.push_back(node->val);
node = node->right;
}
}
return result;
}
};
//后序遍历
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
//遍历二叉树,这次用迭代法去求解
//用栈的思路
stack<TreeNode*> tempStack;
vector<int> result;
if (root == nullptr) {
return result;
}
tempStack.push(root);//先把根结点压进去
while (!tempStack.empty()) {
TreeNode* node = tempStack.top();
result.push_back(node->val);
tempStack.pop();
if (node->left) tempStack.push(node->left);
if (node->right) tempStack.push(node->right);
}
reverse(result.begin(), result.end());
return result;
}
};
3.二叉树的统一迭代法
明天上午刷到二叉树周末总结。
晚上回来后看黑马程序员的stl要做笔记,它里面有容器,算法,迭代器的结构,记笔记,看源码。
后期把封装,继承,多态这几个东西重新再理解一下,太生疏了。
策略:算法题每天一题足够了,其他时间用来把C++的封装,多态,继承的细节再搞清楚一下,还有就是计算机网络的知识非常薄弱,中途补一下。
总之:每天刷一题,其他闲暇时间学计算机网络,以及C++的底层性能等等。
2024.8.1重新调整
确定:每天只刷一题,其他时间去啃计算机网络以及STL基本容器及源码(黑马程序员),C++封装,继承,多态再温习一遍后去做实际项目:Webserver就作为练手吧。
继续二叉树的统一迭代法
1.对于中序遍历,需要画图,需要按步骤看代码理解这一个过程,我完全按照代码随想录的思路来写,其实不好理解,以后对这方面还是要加深一下认识。
2.统一迭代法的写法和递归遍历的优点是前中后序遍历在代码上调整非常简单。
3.这道题要求对于前中后序遍历的结点扫描顺序非常了解,不然的话很难理解。
4.核心思想:扫描的结点与处理的结点保持一致,这个一致是用nullptr去做标记的。
5.画图理解时,可以用一个深度为3的满二叉树去画图理解,比较容易一些。
统一迭代法,复习时背记加画图理解是比较合适的。理解了一次,之后复习会快速很多
统一迭代法的中序遍历:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;//二叉树遍历后的元素保存在result中
stack<TreeNode*> st;
if (root != nullptr) {
st.push(root);
}
while (!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {//如果是nullptr就要进行处理了
//由于中序遍历是左中右,那我压入顺序则是右中左
st.pop();
if (node->right) st.push(node->right);
st.push(node);
st.push(nullptr);
if (node->left) st.push(node->left);
}
else {
st.pop();//把nullptr踢出
node = st.top();
result.push_back(node->val);
st.pop();
}
}
return result;
}
};
统一迭代法的前序遍历:
//统一迭代法的前序遍历(跟着代码随香炉的思路写)
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;//二叉树遍历后的元素保存在result中
stack<TreeNode*> st;
if (root != nullptr) {
st.push(root);
}
while (!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {//如果是nullptr就要进行处理了
//由于前序遍历是中左右,那我压入顺序则是右左中
st.pop();
if (node->right) st.push(node->right);
if (node->left) st.push(node->left);
st.push(node);
st.push(nullptr);//此时访问的结点是中结点,要处理的结点也是中结点,用nullptr做标记
}
else {
st.pop();//把nullptr踢出
node = st.top();
result.push_back(node->val);
st.pop();
}
}
return result;
}
};
统一迭代法的后序遍历
//统一迭代法的后序遍历(跟着代码随香炉的思路写)
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;//二叉树遍历后的元素保存在result中
stack<TreeNode*> st;
if (root != nullptr) {
st.push(root);
}
while (!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {//如果是nullptr就要进行处理了
//由于后序遍历是左右中,那我压入顺序则是中右左
st.pop();
st.push(node);
st.push(nullptr);//此时访问的结点是中结点,要处理的结点也是中结点,用nullptr做标记
if (node->right) st.push(node->right);
if (node->left) st.push(node->left);
}
else {
st.pop();//把nullptr踢出
node = st.top();
result.push_back(node->val);
st.pop();
}
}
return result;
}
};
4.二叉树的层序遍历
8.14日任务:刷一道题
需要克制:每天刷一题,其他闲暇时间学计算机网络,以及C++的底层性能等等。
循环法相对比较好想象,比较简单。
递归法,还是不太好理解,我目前理解的也不够,只是能够看着代码理解整个递归的流程,但是我自己还没有掌握独立写出这一递归的能力。后期复习可以再尝试看会不会有深的理解。
循环法:
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
//二叉树层序遍历
//两种方法:先写循环,再写递归
//方法1:循环法
queue<TreeNode*> tempqueue;
vector<vector<int>> result;
if (root == nullptr) {
return result;
}
tempqueue.push(root);
TreeNode* node;
while (!tempqueue.empty()) {
int size = tempqueue.size();
vector<int> temp;
for (int i = 0; i < size; i++) {
node = tempqueue.front();
tempqueue.pop();
temp.push_back(node->val);
if (node->left) tempqueue.push(node->left);
if (node->right) tempqueue.push(node->right);
}
result.push_back(temp);
}
return result;
}
};
递归法:(自行尝试编写),自行没写出来,最后还是按照代码随想录的方法来的。
class Solution {
public:
void order(TreeNode* node, vector<vector<int>>& result, int depth) {
//首先是终止条件
if (node == nullptr) {
return;
}
//接着顺序执行
if (result.size() == depth) { //depth = 0为第一层, result.size() == depth为1时,说明缺少存放该层元素的数组
result.push_back(vector<int>());//建立对应的数组
}
result[depth].push_back(node->val);
order(node->left, result, depth + 1);
order(node->right, result, depth + 1);
}
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
int depth = 0;
order(root,result,depth);
return result;
}
};
临时总结:对于二叉树目前只是看着代码可以理清遍历流程,但是目前还没有掌握独立写出递归的能力。顺序还是比较好理解,递归对能力要求有点高。
二叉树层序遍历II
只要将二叉树层次遍历I得到的result数组翻转即可。
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
//二叉树的层次遍历
queue<TreeNode*> tempqueue;
vector<vector<int>> result;
TreeNode* node = nullptr;
if (root == nullptr) {
return result;
}
tempqueue.push(root);
while (!tempqueue.empty()) { //如果队列不为空
int size = tempqueue.size();//确定前一层的个数
vector<int> temp;
for (int i = 0; i < size; i++) {
node = tempqueue.front();
temp.push_back(node->val);
tempqueue.pop();
if (node->left) {
tempqueue.push(node->left);
}
if(node->right) {
tempqueue.push(node->right);
}
}
result.push_back(temp);
}
reverse(result.begin(),result.end());
return result;
}
};
二叉树的右视图
层序遍历的时候,判断是否遍历到单层的最后面的元素,如果是,就放进result数组中,随后返回result就可以了。(比较简单)
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
//二叉树层序遍历
//两种方法:先写循环,再写递归
//方法1:循环法
queue<TreeNode*> tempqueue;
vector<int> result;
if (root == nullptr) {
return result;
}
tempqueue.push(root);
TreeNode* node;
while (!tempqueue.empty()) {
int size = tempqueue.size();
vector<int> temp;
for (int i = 0; i < size; i++) {
node = tempqueue.front();
tempqueue.pop();
if (i == size - 1) {
result.push_back(node->val);
}
if (node->left) tempqueue.push(node->left);
if (node->right) tempqueue.push(node->right);
}
}
return result;
}
};
二叉树的层平均值
也比较简单,层序遍历就可以解。
class Solution {
public:
vector<double> averageOfLevels(TreeNode* root) {
queue<TreeNode*> tempqueue;
vector<double> result;
TreeNode* node = nullptr;
if (root == nullptr) {
return result;
}//已经说明非空二叉树,其实可以不用这一句的。
tempqueue.push(root);
while (!tempqueue.empty()) { //如果队列不为空
int size = tempqueue.size();//确定前一层的个数
double sum = 0;
for (int i = 0; i < size; i++) {
node = tempqueue.front();
sum += node->val;
tempqueue.pop();
if (node->left) {
tempqueue.push(node->left);
}
if(node->right) {
tempqueue.push(node->right);
}
}
result.push_back(sum/size);
}
return result;
}
};
N叉树的层序遍历
对于N叉树结点的数据结构看懂的话,就很简单。
N叉树结点的数据结构:
class Node {
public:
int val;
vector<Node*> children;
Node() {}
Node(int _val) {
val = _val;
}
Node(int _val, vector<Node*> _children) {
val = _val;
children = _children;
}
};
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
//N叉树的层序遍历
queue<Node*> tempqueue;
vector<vector<int>> result;
if (root == nullptr) {
return result;
}
tempqueue.push(root);
Node* node;
while (!tempqueue.empty()) {
int size = tempqueue.size();
vector<int> temp;
for (int i = 0; i < size; i++) {
node = tempqueue.front();
tempqueue.pop();
temp.push_back(node->val);
//加下俩要插入他的子结点
for(int j = 0; j < (node->children).size(); j++) {
if((node->children)[j]) tempqueue.push((node->children)[j]);
}
}
result.push_back(temp);
}
return result;
}
};
插入子结点时使用迭代器进行遍历:
for(vector<Node*>::iterator it = (node->children).begin(); it != (node->children).end(); it++) {
if(*it) tempqueue.push(*it);
}
在每个树行中找最大值
代码随想录中求最大值的简洁写法,以后要常用。
INT_MIN和INT_MAX分别是表示int类型的最小值和最大值常量。
class Solution {
public:
vector<int> largestValues(TreeNode* root) {
queue<TreeNode*> tempqueue;
vector<int> result;
if (root == nullptr) {
return result;
}
tempqueue.push(root);
TreeNode* node;
while (!tempqueue.empty()) {
int size = tempqueue.size();
int maxValue = INT_MIN;
for (int i = 0; i < size; i++) {
node = tempqueue.front();
tempqueue.pop();
maxValue = maxValue < node->val ? node->val : maxValue;
if (node->left) tempqueue.push(node->left);
if (node->right) tempqueue.push(node->right);
}
result.push_back(maxValue);
}
return result;
}
};
填充每个节点的下一个右侧节点指针I/II
比较简单:没什么难度。
class Solution {
public:
Node* connect(Node* root) {
queue<Node*> tempqueue;
if (root == nullptr) {
return root;
}
tempqueue.push(root);
while (!tempqueue.empty()) {
int size = tempqueue.size();
for (int i = 0; i < size; i++) {
Node* node = tempqueue.front();
tempqueue.pop();
if (i == size - 1) {
node->next = nullptr;
}
else {
node->next = tempqueue.front();
}
if (node->left) tempqueue.push(node->left);
if (node->right) tempqueue.push(node->right);
}
}
return root;
}
};
二叉树的最大深度
class Solution {
public:
int maxDepth(TreeNode* root) {
queue<TreeNode*> tempqueue;
if (root == nullptr) {
return 0;
}
tempqueue.push(root);
int maxdepth = 0;
while (!tempqueue.empty()) {
int size = tempqueue.size();
for (int i = 0; i < size; i++) {
TreeNode* node = tempqueue.front();
tempqueue.pop();
if (node->left) tempqueue.push(node->left);
if (node->right) tempqueue.push(node->right);
}
maxdepth++;
}
return maxdepth;
}
};
二叉树的最小深度
比较简单,需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点。
class Solution {
public:
int minDepth(TreeNode* root) {
queue<TreeNode*> tempqueue;
if (root == nullptr) {
return 0;
}
tempqueue.push(root);
int mindepth = 0;
while (!tempqueue.empty()) {
int size = tempqueue.size();
for (int i = 0; i < size; i++) {
TreeNode* node = tempqueue.front();
tempqueue.pop();
if (node->left) tempqueue.push(node->left);
if (node->right) tempqueue.push(node->right);
if (!(node->left || node->right)) {
return mindepth + 1;
}
}
mindepth++;
}
return mindepth;
}
};
翻转二叉树
我的思路:按照层序遍历的思路即可,调换左右子结点就可以。
代码随想录中使用前中后序遍历,迭代法,递归法有更深的思考与解析,明天上午啃掉这道题。
8.17日又复习查看了一遍统一迭代法的思路:前序遍历入栈顺序:右左中;后序遍历入栈顺序:中右左;中序遍历入栈顺序:右中左;保证访问节点与处理节点一致(访问节点是指栈顶的节点,栈顶结点为null则是要遍历的,栈顶结点不为null则是要继续压入的,此处的遍历和压入步骤即为处理步骤); 可背记:null总是和中间结点一块压入的。还是要多次实践理解其中的精髓。
无论使用递归法还是统一迭代法,用笔在纸上画一下流程就能清晰很多。
层序遍历
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
queue<TreeNode*> tempqueue;
if (root == nullptr) {
return root;
}
tempqueue.push(root);
while (!tempqueue.empty()) {
int size = tempqueue.size();
for (int i = 0; i < size; i++) {
TreeNode* node = tempqueue.front();
TreeNode* tempnode = node->left;
node->left = node->right;
node->right = tempnode;
tempqueue.pop();
if (node->left) tempqueue.push(node->left);
if (node->right) tempqueue.push(node->right);
}
}
return root;
}
};
这道题代码随想录中给出了递归(前中后序),统一迭代(深度优先遍历)(用栈)(前中后序),层序遍历(广度优先遍历(用队列))(一种)。尽量独立编写就当作复习,加深对二叉树的认识。
前序递归:(按照写递归的步骤来就行,多练就能熟悉)
class Solution {
public:
//递归
void traversalTurn(TreeNode* node) {
//1.终止条件
if (node == nullptr) {
return;
}
//2.顺序逻辑
//先来前序
TreeNode* tempnode;
tempnode = node->left;
node->left = node->right;
node->right = tempnode;
//再翻转左边
traversalTurn(node->left);
traversalTurn(node->right);
}
TreeNode* invertTree(TreeNode* root) {
//尝试7种方法,加深对于二叉树的理解,前中后序的递归,前中后序的统一迭代法,一个层序遍历
//中序最后再用,先用前序后序
//先用递归法(以前序遍历为例)
//步骤1:确定递归函数参数和返回值
traversalTurn(root);
return root;
}
};
后序递归,直接调转位置就好
class Solution {
public:
//递归
void traversalTurn(TreeNode* node) {
//1.终止条件
if (node == nullptr) {
return;
}
//2.顺序逻辑
//先来前序
traversalTurn(node->left);
traversalTurn(node->right);
TreeNode* tempnode;
tempnode = node->left;
node->left = node->right;
node->right = tempnode;
//再翻转左边
}
TreeNode* invertTree(TreeNode* root) {
//尝试7种方法,加深对于二叉树的理解,前中后序的递归,前中后序的统一迭代法,一个层序遍历
//中序最后再用,先用前序后序
//先用递归法(以前序遍历为例)
//步骤1:确定递归函数参数和返回值
traversalTurn(root);
return root;
}
};
前序统一迭代:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
//尝试7种方法,加深对于二叉树的理解,前中后序的递归,前中后序的统一迭代法,一个层序遍历
//中序最后再用,先用前序后序
//接下来用统一迭代法,用的是栈的思路,先在纸上理一下逻辑:
stack<TreeNode*> usestack;
//还是看了一下代码随想录的答案
if (root == nullptr) return root;
usestack.push(root);
while (!usestack.empty()) {
TreeNode* node = usestack.top();
if (node != nullptr) {
usestack.pop();
if(node->right) usestack.push(node->right);
if(node->left) usestack.push(node->left);
usestack.push(node);
usestack.push(nullptr);
}
else {
usestack.pop();
node = usestack.top();
TreeNode* turnNode = node->left;
node->left = node->right;
node->right = turnNode;
usestack.pop();
}
}
return root;
}
};
后序统一迭代:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
//尝试7种方法,加深对于二叉树的理解,前中后序的递归,前中后序的统一迭代法,一个层序遍历
//中序最后再用,先用前序后序
//接下来用统一迭代法,用的是栈的思路,先在纸上理一下逻辑:
stack<TreeNode*> usestack;
//还是看了一下代码随想录的答案
if (root == nullptr) return root;
usestack.push(root);
while (!usestack.empty()) {
TreeNode* node = usestack.top();
if (node != nullptr) {
usestack.pop();
usestack.push(node);
usestack.push(nullptr);
if(node->right) usestack.push(node->right);
if(node->left) usestack.push(node->left);
}
else {
usestack.pop();
node = usestack.top();
TreeNode* turnNode = node->left;
node->left = node->right;
node->right = turnNode;
usestack.pop();
}
}
return root;
}
};
中序统一迭代法也是可以的:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
//尝试7种方法,加深对于二叉树的理解,前中后序的递归,前中后序的统一迭代法,一个层序遍历
//中序最后再用,先用前序后序
//接下来用统一迭代法,用的是栈的思路,先在纸上理一下逻辑:
stack<TreeNode*> usestack;
//还是看了一下代码随想录的答案
if (root == nullptr) return root;
usestack.push(root);
while (!usestack.empty()) {
TreeNode* node = usestack.top();
if (node != nullptr) {
usestack.pop();
if(node->right) usestack.push(node->right);
usestack.push(node);
usestack.push(nullptr);
if(node->left) usestack.push(node->left);
}
else {
usestack.pop();
node = usestack.top();
TreeNode* turnNode = node->left;
node->left = node->right;
node->right = turnNode;
usestack.pop();
}
}
return root;
}
};
递归中序遍历也可以写,不过就写成左中左的形式,属于是观察规律得来的。
class Solution {
public:
//递归
void traversalTurn(TreeNode* node) {
//1.终止条件
if (node == nullptr) {
return;
}
//2.顺序逻辑
//先来前序
traversalTurn(node->left);
TreeNode* tempnode;
tempnode = node->left;
node->left = node->right;
node->right = tempnode;
traversalTurn(node->left);
//再翻转左边
}
TreeNode* invertTree(TreeNode* root) {
//尝试7种方法,加深对于二叉树的理解,前中后序的递归,前中后序的统一迭代法,一个层序遍历
//中序最后再用,先用前序后序
//先用递归法(以前序遍历为例)
//步骤1:确定递归函数参数和返回值
traversalTurn(root);
return root;
}
};
8.17日任务:对称二叉树
代码随想录依然给出了递归,迭代(使用数组队列栈)等方法,是一个有必要认真琢磨的一道题。
注意:一般都要考虑一下二叉树为空树的情况
针对代码随想录解析的疑问:
疑问1:本题为什么只能是后序遍历?遍历两棵树内外侧结点,两侧可以分别是中左右,中右左;左中右,右中左;左右中,右左中吗?
回答:这不是常规的遍历,只是逻辑上像。本质上就是一个不断循环的自上而下的比较。
疑问2:nullptr还能继续指向->left吗?用visualstudio检查一下
回答:nullptr指针不能再继续指向->left,->right,->val等等,即指针为nullptr就不能再访问类内元素了。
按照代码随想录自己写的递归,没什么大问题,和代码随想录的差不多。这并不是严格的后序遍历,就是自上而下的比较传递,可以多次练习加深理解。
class Solution {
public:
bool compare(TreeNode* leftnode, TreeNode* rightnode) {
//先考虑后序遍历,左侧为左右中,右侧为右左中
//第一步是终止条件
//终止条件就是两边值不相等或者一侧有值,一侧为空。我有疑问?结点为null,还能访问其中的val吗?假如不能
if (leftnode == nullptr && rightnode != nullptr) return false;
else if (leftnode != nullptr && rightnode == nullptr) return false;
else if (!(leftnode || rightnode)) return true;
else if (leftnode->val != rightnode->val) return false;
//开始顺序逻辑,只有两边都相等之后才能进行下一轮比较
bool flag = compare(leftnode->left, rightnode->right);
if (flag) {
flag = compare(leftnode->right, rightnode->left);
}
return flag;
}
bool isSymmetric(TreeNode* root) {
//先按照代码随想录的后序遍历,之后再尝试前序遍历和中序遍历
//使用递归,第一步应该是确定函数输入参数和返回值,输入的是根结点,返回的true or false;
if (root == NULL) return true;//考虑空结点的情况
return compare(root->left,root->right);
}
};
使用队列和栈进行迭代比较:并不是之前提到的前中后序遍历,跟前面递归的顺序逻辑是一致的。
队列和栈的使用没有区别,都是把要比对的结点两两拿出来比较就可以了。
class Solution {
public:
bool isSymmetric(TreeNode* root) {
//用队列来做,队列和栈的思路等同,这里只用队列编写就可以了
queue<TreeNode*> tempqueue;
if (root == nullptr) return true;
tempqueue.push(root->left);
tempqueue.push(root->right);
while (!tempqueue.empty()) {
TreeNode* leftnode = tempqueue.front(); tempqueue.pop();
TreeNode* rightnode = tempqueue.front(); tempqueue.pop();
//判断
if (leftnode == nullptr && rightnode != nullptr) return false;
else if (leftnode != nullptr && rightnode == nullptr) return false;
else if (leftnode && rightnode && leftnode->val != rightnode->val) return false;
else if (!(leftnode||rightnode)) continue; //这句话排除了leftnode和rightnode都为空的情况,其实就是对应二叉树的最底下一层,空指针是无法再引用->left和->right的
tempqueue.push(leftnode->left);
tempqueue.push(rightnode->right);
tempqueue.push(leftnode->right);
tempqueue.push(rightnode->left);
}
return true;
}
};
8.18日任务:二叉树的最大深度
其实除了层序遍历,代码随想录给出的递归方法并没有看懂,后期回看的时候,这个要加深注意一下。
这两道题在前面层序遍历过程中已经出现过了,使用层序遍历可以很快解决。
我在重新回忆编写这道题的时候,遇到了一个问题:
问题代码如下:
最后的部分我没有添加return语句,编译器提示报错。虽然我的程序中在实际运行中不会运行到最后的return,但是容易被编译器报错,一般情况下考虑把return添加到最后,而不是在中途return。
int minDepth(TreeNode* root) {
queue<TreeNode*> usequeue;
if (root == nullptr) {
return 0;
}
int mindepth = 0;
usequeue.push(root);
while (!usequeue.empty()) {
int size = usequeue.size();
mindepth += 1;
for (int i = 0; i < size; i++) {
TreeNode* tempnode = usequeue.front();
if(tempnode->left == nullptr && tempnode->right == nullptr) {
return mindepth;
}
usequeue.pop();
if(tempnode->left) usequeue.push(tempnode->left);
if(tempnode->right) usequeue.push(tempnode->right);
}
}
// return mindepth;
}
这道题代码随想录中另给出了递归法(前序和后序遍历来解这个问题),尝试独立理清思路,编写递归代码(当作练习)
重要的点:简单思考之后没有思路就赶紧看答案,因为你之前并没有学过数据结构,思维很受限,目前就是抓紧时间学习,直接看答案,但是要求自己理解清楚。
后序遍历(左右中)思路反馈:本质上思路逻辑是先求左子树深度,再求右子树深度,取两者最大值然后加1,根结点。
求最大深度部分:
后序遍历思路(左右中),还是需要深入理解,感觉理解的不够。
class Solution {
public:
int maxDepth(TreeNode* root) {
//使用递归的方法
//1.确定函数参数输入值和返回值
//输入参数是根节点,输出为int类型深度数值,maxDepth函数形式是满足这个需求的
//1.确定终止条件
if (root == nullptr) {
return 0;
}
//2.开始顺序逻辑
int leftNum = maxDepth(root->left);
int rightNum = maxDepth(root->right);
int maxNum = leftNum <= rightNum ? rightNum :leftNum;
return maxNum + 1;
}
};
代码随想录说这个方法充分体现了深度回溯过程,我还没有完全理解,后期复习回看再琢磨琢磨。
class Solution {
public:
int result;
void searchDepth(TreeNode* root, int depth) {
//更新深度
result = result >= depth ? result : depth;
//1.确定终止条件
if (root == nullptr) {
return;
}
//2.顺序逻辑
//先看左节点
if (root->left) {
depth++;
searchDepth(root->left, depth);
depth--;//减1是为了不和右节点计数冲突
}
if (root->right) {
depth++;
searchDepth(root->right, depth);
depth--;//减1是为了不和另外一个与父辈同行的左节点冲突
}
}
int maxDepth(TreeNode* root) {
//使用递归的方法
//使用前序遍历(中左右)自上向下计数
//1.确定函数入口参数和返回值
//入口参数为一个节点,和一个深度值depth,设置一个全局变量,就不需要返回值了
if (root == nullptr) {
return 0;
}//排除特例空节点
searchDepth(root, 1);//可以确保深度至少为1;
return result;
}
};
又根据思路自行编写加深理解:
理解导向:求高度,不要被后序这个名词误导了,其实就是由底部一直数到顶部。
本质思路:输入一个根节点,然后检索左右子树分别有多高,最后加上1个中节点即为本身树的高度
class Solution {
public:
int maxDepth(TreeNode* root) {
//讲了两种方法:分别是求高度(左右中后序递归)和求深度(中左右前序递归)
//先来求高度,从底部节点向上求,就像是左右中
//递归函数的输入值和返回值,输入一个根节点,然后检索左右子树分别有多高,最后加上1个中节点即为本身高度
//输入值:一个根节点,返回值:int类型的深度值
//1.确定终止条件
if (root == nullptr) {
return 0;
}
//2.顺序逻辑
int leftDepth = maxDepth(root->left);
int rightDepth = maxDepth(root->right);
int treeDepth = (leftDepth <= rightDepth ? rightDepth : leftDepth) + 1;
return treeDepth;
}
};
理解导向:求深度,不要被前序这个名词误导了,后期还得加深理解,就是从顶部一致数到底部,但是数数的过程一会要往下数,一会又往上数,会发生曲折数数。
代码随想录说这个方法充分体现了深度回溯过程。
class Solution {
public:
int result;
void searchDepth(TreeNode* node, int depth) {
//result用来记录目前数到的最深深度,depth指代的是目前数数的位置深度
result = result <= depth ? depth : result; //记录最深深度
//1.考虑终止条件
if (node == nullptr) {
return;
}
//2.考虑顺序逻辑
if (node->left) {
depth++;//深度+1
searchDepth(node->left, depth);
depth--;//回溯,深度-1
}
if (node->right) {
depth++;//深度+1
searchDepth(node->right,depth);
depth--;//回溯,深度-1
}
}
int maxDepth(TreeNode* root) {
//讲了两种方法:分别是求高度(左右中后序递归)和求深度(中左右前序递归)
//现在来求深度
//函数参数输入:输入一个根节点和一个用来数数的标识depth,这个depth的数值会加会减,最终的深度计数可以存储到一个全局变量中,或者返回实际深度计数
//1.尝试存储到全局变量中2.尝试设置返回值为深度计数
if (root == nullptr) {
return 0;
}
searchDepth(root, 1);
return result;
}
};
result也可以作为返回值,已经尝试过可以实现。
实现代码如下:思路一致,只不过换个写法。
class Solution {
public:
int searchDepth(TreeNode* node, int depth,int result) {
result = result <= depth ? depth : result;
//result用来记录目前数到的最深深度,depth指代的是目前数数的位置深度 //记录最深深度
//1.考虑终止条件
if (node == nullptr) {
return result;
}
//2.考虑顺序逻辑
if (node->left) {
depth++;//深度+1
result = searchDepth(node->left, depth, result);
depth--;//回溯,深度-1
}
if (node->right) {
depth++;//深度+1
result = searchDepth(node->right, depth, result);
depth--;//回溯,深度-1
}
return result;
}
int maxDepth(TreeNode* root) {
//讲了两种方法:分别是求高度(左右中后序递归)和求深度(中左右前序递归)
//现在来求深度
//函数参数输入:输入一个根节点和一个用来数数的标识depth,这个depth的数值会加会减,最终的深度计数可以存储到一个全局变量中,或者返回实际深度计数
//1.尝试存储到全局变量中2.尝试设置返回值为深度计数
if (root == nullptr) {
return 0;
}
int result = searchDepth(root, 1, 1);
return result;
}
};
对于深度回溯的思路,我暂时觉得如图所示:depth的增减反映了曲线的折回路径,result作为最深深度记录。
8.19日任务:二叉树的最小深度
1.使用后序遍历求高度时,if ((leftdepth && rightdepth) == 0)这句代码报错了,原因是&&优先级低于==从而造成程序报错。
2.对于第二种方法,思路和二叉树最大深度的逻辑思路差不多,我的注释写的比较清楚了。这里主要在于什么时候对resultDepth进行更新,一个是遇到叶子节点的时候再更新,选较小的值;另一个当depth>resultDepth时就可以直接退出了。
3.一会看一下代码随想录的思路。思路差不多,代码随想录在第二种方法中将resultDepth初始值设置为INT_MAX.这块自己也要学着用。
按照二叉树的最大深度的思路自己写。
第一种方法:左右中后序遍历求高度,主旨思想依然是比较左右两侧子树,选择较小的一边,特殊情况是对叶节点的理解(左右子树都为空才为叶节点)
class Solution {
public:
int minDepth(TreeNode* root) {
//代码随想录给出的是前序和后序遍历,思路导向应该是求深度还是求高度
//首先求高度(类似于左右中顺序后序遍历),在求最大高度的时候,后序遍历的核心是左右子树选最长的。
//现在是叶子节点到根节点最小高度。
//大概理清楚了,当一侧为空,另一侧不为空,不能把高度设置为0,代码随想录中也提到了这个特殊情况
//首先还是后序递归
//1.确定递归函数输入参数和返回值,返回此树最小深度,输入此树根节点。本质上依然是左右两棵树做比较,选最小深度的那颗树,同时把特例情况规避就好了
//minDepth函数的形式刚好满足要求
//2.确定终止条件
if (root == nullptr) {
return 0;
}
if (root->left == nullptr && root->right == nullptr) {
return 1;
}
//顺序执行
//先访问左节点
int leftdepth = minDepth(root->left);
//再访问右节点
int rightdepth = minDepth(root->right);
int resultdepth = 0;
//不可直接选择最小深度,判断该根节点是否是一侧为空,一侧不为空,如果是的话,那么选择最大深度的那一个,如果不是,选择最小深度的那一个
if ((leftdepth && rightdepth) == 0) {//只要有一个为0,则满足上述条件
//if (leftdepth == 0 || rightdepth == 0){ //只要有一个为0,则满足上述条件
resultdepth = leftdepth <= rightdepth ? rightdepth : leftdepth;
}
else {
resultdepth = leftdepth <= rightdepth ? leftdepth : rightdepth;
}
return resultdepth + 1;
}
};
第二种方法:求深度,中左右前序遍历,从上往下数,数的过程中曲折上下
class Solution {
public:
int resultDepth = 0;//只有当遇到叶子节点才会更新这一数值
//递归函数,有了公有成员resultDepth,递归函数就不考虑返回值了
void preorderGetDepth(TreeNode* root, int depth){//depth是用来曲折回溯的
//1.终止条件:左右节点都为nullptr的时候就该返回了,同时当depth大于更新过的resultDepth也该返回了
if (root->left == nullptr && root->right == nullptr) { //遇到叶子节点
if (resultDepth == 0) {//从未更新过
resultDepth = depth;
}
else {
resultDepth = resultDepth < depth ? resultDepth : depth;
}
return;
}
else if (resultDepth != 0 && depth > resultDepth) {
return;
}
//2.顺序逻辑
if(root->left) {
depth++;
preorderGetDepth(root->left,depth);
depth--;
}
if(root->right) {
depth++;
preorderGetDepth(root->right,depth);
depth--;
}
}
int minDepth(TreeNode* root) {
//代码随想录给出的是前序和后序遍历,思路导向应该是求深度还是求高度
//首先求高度(类似于左右中顺序后序遍历),在求最大高度的时候,后序遍历的核心是左右子树选最长的。
//现在是叶子节点到根节点最小高度。
//大概理清楚了,当一侧为空,另一侧不为空,不能把高度设置为0,代码随想录中也提到了这个特殊情况
//接下来是求深度回溯,如何考虑上一行提到的特殊情况呢?
//依然是递归
if (root == nullptr) {
return 0;
}
preorderGetDepth(root,1);
return resultDepth;
}
};