注:博客内容只限于个人学习。图片和内容可能整合了多渠道的信息来源,如侵联系可删。
在我们解题过程中二叉树有两种主要的形式:满二叉树和完全二叉树。
满二叉树
这棵二叉树为满二叉树,深度为k,有2^k-1个节点
完全二叉树
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
二叉搜索树
前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,「二叉搜索树是一个有序树」。
下面这两棵树都是搜索树
二叉搜索树的中序遍历是递增有序的
平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
深度优先遍历
class Solution {
public:
void traversal(TreeNode* cur, vector& vec) {//为什么要另外写一个函数,因为要迭代保存vec,函数内部定义vec的话,递归返回时内存会被释放掉。
if (cur == NULL) return;
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
}
vector preorderTraversal(TreeNode* root) {
vector result;
traversal(root,result);
return result;
}
};
class Solution {
public:
vector preorderTraversal(TreeNode* root) {
stack st;
vector result;
if(!root) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top(); st.pop();
result.push_back(node->val); // 中
if(node->right) st.push(node->right); // 右
if(node->left) st.push(node->left); // 左
}
return result;
}
};
void traversal(TreeNode* cur, vector& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
vec.push_back(cur->val); // 中
traversal(cur->right, vec); // 右
}
vector midorderTraversal(TreeNode* root) {
vector result;
traversal(root,result);
return result;
}
};
/*
因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。
再看看中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。
所以前面写的前序遍历的迭代代码,不能和中序遍历通用。(例子:5,4,6,1,2)
*/
class Solution {
public:
vector midorderTraversal(TreeNode* root) {
vector res;
stack stk;
while (root != nullptr || !stk.empty()) {//指针遍历来帮助访问节点,栈则用来处理节点上的元素。
while (root != nullptr) {//指针用来访问节点。访问到最底层
stk.push(root);//将访问的节点放进栈
root = root->left;//左
}
root = stk.top();// 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
stk.pop();
res.push_back(root->val);//中
root = root->right;//右
}
return res;
}
};
//中序遍历的时候栈的深度取决于二叉搜索树的高度
//Morris 中序遍历
class Solution {
public:
vector inorderTraversal(TreeNode* root) {
vector res;
TreeNode *predecessor = nullptr;
while (root != nullptr) {
if (root->left != nullptr) {
// predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止
predecessor = root->left;
while (predecessor->right != nullptr && predecessor->right != root) {
predecessor = predecessor->right;
}
// 让 predecessor 的右指针指向 root,继续遍历左子树
if (predecessor->right == nullptr) {
predecessor->right = root;
root = root->left;
}
// 说明左子树已经访问完了,我们需要断开链接
else {
res.push_back(root->val);
predecessor->right = nullptr;
root = root->right;
}
}
// 如果没有左孩子,则直接访问右孩子
else {
res.push_back(root->val);
root = root->right;
}
}
return res;
}
};
void traversal(TreeNode* cur, vector& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
vec.push_back(cur->val); // 中
}
vector postorderTraversal(TreeNode* root) {
vector result;
traversal(root,result);
return result;
}
};
class Solution {
public:
vector preorderTraversal(TreeNode* root) {
stack st;
vector result;
if(!root) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top(); st.pop(); // 中
result.push_back(node->val);
if(node->left) st.push(node->left); // 左
if(node->right) st.push(node->right); // 右
}
reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
return result;
}
};
广度优先遍历
class Solution {
public:
vector> levelOrder(TreeNode* root) {
queue que;
vector> result;
if(root=NULL) return result;
que.push(root);
while(!que.empty()){
int size= que.size();//一层一层的遍历每层节点
vector tem;
for(int i=0;ival);
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
result.push_back(tem);
}
return result;
}
};
可以解决很多问题:树的深度,树的节点数量等
104.二叉树的最大深度
199.二叉树的右视图
637.二叉树的层平均值
429.N叉树的层序遍历]
513.找树左下角的值
另外一些,类似层序遍历,但是空节点NULL也进入队列进行计算如:
101.对称二叉树
100.相同的树
和做验证是否是对称树一样 类似层序遍历,使用一个队列,一次压入两个节点,然后弹出判断,最后再指定后续节点进入队列顺序
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
queue que;
if(!p&&!q) return true;
que.push(p);
que.push(q);
while(!que.empty()){
TreeNode* first=que.front();que.pop();
TreeNode* second = que.front(); que.pop();
if(!first&&!second) continue;
if(!first||!second||first->val!=second->val) return false;
que.push(first->left);
que.push(second->left);
que.push(first->right);
que.push(second->right);
}
return true;
}
};
判断树一样也可递归:
class Solution {
public:
bool check(TreeNode *o, TreeNode *t) {
if (!o && !t) return true;
if ((o && !t) || (!o && t) || (o->val != t->val)) return false;
return check(o->left, t->left) && check(o->right, t->right);
}
};
另外再扩展一下,判断一棵树是否是另一棵树的子树,可以再上面代码的基础上,再遍历第一个树的节点,然后和第二棵树依次比较
bool isSubtree(TreeNode *o, TreeNode *t) {
if (!o) return false;
return check(o, t) ||isSubtree(o->left, t) || isSubtree(o->right, t);
}
递归三要素
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。
「但leetcode中强调的深度和高度很明显是按照节点来计算的」。
关于根节点的深度究竟是1 还是 0,不同的地方有不一样的标准,leetcode的题目中都是以节点为一度,即根节点深度是1
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root == NULL) return 0;
int maxleft=maxDepth(root->left);
int maxright=maxDepth(root->right);
return 1+max(maxleft,maxright);
}
};
迭代法模板:使用层序遍历是最为合适,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。
一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度。
题号:
104.二叉树的最大深度
559.N叉树的最大深度
全部路径模板:path参数用于递归传递单条path paths引用参数用于保存所有path
class Solution {
public:
void construct_paths(TreeNode* root, string path, vector& paths) {
if (root == nullptr) return ;
path += to_string(root->val);
if (root->left == nullptr && root->right == nullptr) { // 当前节点是叶子节点
paths.push_back(path); // 把路径加入到答案中
}
path += "->"; // 当前节点不是叶子节点,继续递归遍历
construct_paths(root->left, path, paths);
construct_paths(root->right, path ,paths);
}
vector binaryTreePaths(TreeNode* root) {
vector paths;
construct_paths(root, "",paths);
return paths;
}
};
还没解决
110.平衡二叉树
未完待续…