二叉树
基本概念:数据结构与算法-树_Evan_L的博客-CSDN博客_数据结构与算法 树
树的算法求解本质上:是递归运算
树的遍历:前序:根左右;中序:左根右;后序:左右根
完全二叉树: 叶子节点所在的层,总是靠左连续的。
满二叉树: 每个节点度为2,除了叶子节点。
二叉查找树/二叉搜索树:左小于根,右大于根。
平衡二叉树:左右子树的深度差不超过1
二叉树算法题模板
1: 使用递归完成树的前中后序遍历
main(TreeNode* root) { // 根节点
// 1: 结果集,存放每个节点的元素
// vector res;
// 2: 判断root
// if (root == nullptr) return res;
// 3:递归进行遍历
// df(root, res)
}
void df(TreeNode* root, vector &res)
{
// 结束递归
if (root == nullptr)
return;
// 递归遍历
// res.push_back(root->val); // 前序遍历,根左右, 先存放根节点数据
// 左子树 递归
if (root->left)
df(root->left, res);
// res.push_back(root->val); // 中序遍历,左右根,中间存放
// 右子树 递归
if (root->right)
df(root->right, res);
// res.push_back(root->val); // 后序遍历, 左右根
}
2: 使用非递归-栈完成树的前中序遍历
main(TreeNode* root) { // 根节点
// 1: 结果集,存放每个节点的元素
// vector res;
// 2: 判断root
// if (root == nullptr) return res;
// 3: 栈stack
stack s;
// 4: cur_node 指向树每次遍历的节点,不断变化搜索
TreeNode* cur_tree_node = nullptr;
cur_node = root; // 初始值指向root
// 5: while 判断cur_node和s
// if判断cur_node, 为空说明树的一侧搜索完毕,开始弹出父节点,搜索树的另一侧。
//
while(cur_node != nullptr || !s.empty()) {
if (cur_node != nullptr) {
s.push(cur_node); // 当前节点进栈
//前序遍历
// res.push_back(cur_node->val)
// cur_node 左
cur_node = cur_node->left;
} else {
cur_node = s.top();
s.pop(); // 出栈父节点
// 中序遍历
// res.push_back(cur_node->val)
// cur_node 右
cur_node = cur_node->right
}
}
// 6
return res;
}
3: 基于queue的层次遍历, 层次遍历也可以实现从上到下打印元素
main(TreeNode* root)
{
// 1: 结果集,存放每个节点的元素
// vector res;
// 2: 判断root
// if (root == nullptr) return res;
// 3: 队列q存放当前层的节点
queue q;
q.push(root);
// 4: 队列的出队入队
while(!q.empty()) {
// 当前层元素个数
int cur_le_size = q.size();
for (int i = 0; i < cur_le_size ; i++) {
// 指向当前队首,之后出对
TreeNode* cur_node = q.front();
queue.pop();
// 当前元素
res.push_back(cur_node->val);
// 下一层元素进入队列,左右子树
if (cur_node->left)
q.push(cur_node->left);
if (cur_node->right)
qu.push(cur_node->right);
}
}
return res;
}
4: 二叉树的最大深度/当前节点的高度(比较左右子树中最大深度的)
利用递归求深度,每递归一次意味着树的深度+1
int max_depth(TreeNode* root)
{
// 1: 判断root
if (root == nullptr) return 0;
// 2: 求左右子树的高度,每次递归+1
return max(max_depth(root->left), max_depth(root->right)) + 1;
}
5: 求根节点到目标节点的路径
利用二叉搜索树,左<根<右
void get_path(TreeNode* root, TreeNode target_node)
{
// 存放路径
vector res;
//
if (root == nullptr || target_node == nullptr)
return res;
//
TreeNode* cur_node = root;
while(cur_node) {
if (cur_node->val == target_node->val) {
res.push_back(cur_node);
break;
} else if (cur_node->val > target_node->val) {
res.push_back(cur_node);
cur_node = cur_node->left;
} else {
res.push_back(cur_node);
cur_node = cur_node->right;
}
}
return res;
}
leetcode算法题:
1: 二叉树的中序遍历
思路: 递归、 左根右
class Solution {
public:
vector inorderTraversal(TreeNode* root) {
// 用数组来存储树的元素
vector res;
backtreak(root, res); // 建立一个递归函数 去
return res;
}
void backtreak(TreeNode* root, vector& res)
{
if (root == nullptr) // 递归结束
return;
if (root->left)
backtreak(root->left, res); // 左子树递归
res.push_back(root->val); // 当前根节点存放
if (root->right)
backtreak(root->right, res); // 左子树递归
return;
}
};
2: 二叉树的前序遍历
思路
class Solution {
public:
vector preorderTraversal(TreeNode* root) {
vector res;
// 递归求解
backtrack(root, res);
return res;
}
void backtrack(TreeNode* root, vector& res)
{
if (root == nullptr)
return;
res.push_back(root->val); // 将当前根节点存放
if (root->left) // 左子树递归
backtrack(root->left, res);
if (root->right) // 右子树递归
backtrack(root->right, res);
return;
}
};
3: 二叉树的后续遍历
class Solution {
public:
vector postorderTraversal(TreeNode* root) {
vector res;
backtrack(root, res);
return res;
}
void backtrack(TreeNode* root, vector& res)
{
if (root == nullptr)
return;
if (root->left)
backtrack(root->left, res);
if (root->right)
backtrack(root->right, res);
res.push_back(root->val);
return;
}
};
4: 二叉树的层序遍历
思路: 队列构造
class Solution {
public:
vector> levelOrder(TreeNode* root) {
vector> res;
if (root == nullptr) return res;
// 利用队列先进先出特点存放每一层的树的节点
queue q;
q.push(root);
while (!q.empty()) { // 队列不为空 说明还有树的节点
int curLevelNodeNum = q.size(); // 当前层的节点总数
vector cur_res; // 存储当前层节点值
for (int i = 0; i < curLevelNodeNum; i++) { // for循环让该层所有节点入对列
auto node = q.front();
q.pop(); // 出队列
cur_res.push_back(node->val);
if (node->left) q.push(node->left); // 左节点入队列
if (node->right) q.push(node->right); // 右节点入队列
}
res.push_back(cur_res); // 将当前层的结果赋值给res
}
return res;
}
};
5: 判断是否为平衡二叉树
(平衡: 任意一个子树节点高度差的绝对值<=1)(树的高度概念)
思路: 使用递归,自顶向下判断每个左右子树是否平衡
求解高度: 使用递归,自顶向下求当前子树的高度。
class Solution {
public:
bool isBalanced(TreeNode* root) {
// 递归结束条件
if (root == nullptr)
return true;
// 递归去判断每一层左右子树是否满足平衡 isBalance递归调用
return abs(height(root->left)-height(root->right)) <= 1&& isBalanced(root->left) && isBalanced(root->right);
}
// 求当前节点的高度(节点本身的高度是1) 队规求解
int height(TreeNode* root)
{
// 递归结束条件
if (root == nullptr)
return 0;
// +1 是包括节点本身 求左右子树最大的一个高度
// 自顶向下 递归的去求解高度 每递归一层 树的高度+1 左右子树,使用max只去考虑一种
int cur_node_height = max(height(root->left),height(root->right)) + 1;
return cur_node_height;
}
};
6: 求解二叉树的最大深度
思路: 递归 自顶向下去当前节点的深度
class Solution {
public:
int maxDepth(TreeNode* root) {
// 自顶向下 递归求解
if (root == nullptr)
return 0;
// 树的高度(深度)包括节点本身(+1)
return max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
};
7: 前序遍历非递归实现
思路: 利用栈实现当前节点(一定是左右子树的父节点)入栈和出栈。根左右的遍历顺序。
class Solution {
public:
vector preorderTraversal(TreeNode* root) {
// 非递归求解二叉树前序遍历
// 使用栈数据结构来遍历二叉树
stack s;
vector res;
if (root == nullptr) return res;
// 将根节点压入栈;
TreeNode* cur_node = root;
// 循环变量栈,访问二叉树
while (cur_node != nullptr || !s.empty()) {
// 判断当前节点是否存在
if (cur_node != nullptr) {
s.push(cur_node); // 当前节点入栈
res.push_back(cur_node->val); //获取当前节点的值
cur_node = cur_node->left; //遍历左子树,当前节点更新
} else {
// 迭代的当前节点为空
// 获取当前的节点的父节点,并且出栈父节点
cur_node = s.top();
s.pop(); // 出栈
cur_node = cur_node->right;//遍历右子树,当前节点更新
}
}
return res;
}
};
8: 非递归实现二叉树的中序遍历
class Solution {
public:
vector inorderTraversal(TreeNode* root) {
// 用栈实现二叉树的非递归中序遍历
vector res;
if (root == nullptr)
return res;
// 定义一个游标来指向当前的节点
TreeNode* cur_node;
cur_node = root;
// 定义一个栈来访问存放当前父节点
stack s;
while(cur_node != nullptr || !s.empty()) {
// 当前(父)节点非空
if (cur_node != nullptr) {
s.push(cur_node); // 入栈
cur_node = cur_node->left; // 左子树遍历 更新当前节点
} else {
// 当前节点为空
cur_node = s.top(); // 获取当前父节点
s.pop(); // 出栈
res.push_back(cur_node->val); // 左根右的遍历 所有在这里
cur_node = cur_node->right; // 右子树遍历 更新当前节点
}
}
return res;
}
};
9: 二叉树剪枝
思路:(1) dfs 递归判断当前节点作为根节点的子树是否满足剪枝条件
(2)剪枝条件: 当前子树为空;当前左右子树没有包含1的节点;
class Solution {
public:
TreeNode* pruneTree(TreeNode* root) {
if (root == nullptr)
return nullptr;
if (dfs(root))
return root;
return nullptr;
}
// 深度优先搜索算法 以当前节点为子树的根节点 只有子树包括本身有1个等于1的这个根节点就不能删
bool dfs(TreeNode* root)
{
// 当前节点为空 当然就不包括等于1的节点
if (root == nullptr) {
return false;
}
// 深度遍历 左子树 和 右子树
bool left_ = dfs(root->left);
bool right_ = dfs(root->right);
// 符合剪枝条件的
if (left_ == false) {
root->left = nullptr; // 左子树置空 剪枝
}
if (right_ == false) {
root->right = nullptr; // 右子树置空 剪枝
}
// 只要都是false 才会返回false. (当前子树根节点为不等于1 左右子树都为false)
return root->val == 1 || left_ || right_;
}
};
10: 合并二叉树
(1)dfs遍历
(2)
两个二叉树的对应节点可能存在以下三种情况,对于每种情况使用不同的合并方式。
如果两个二叉树的对应节点都为空,则合并后的二叉树的对应节点也为空;
如果两个二叉树的对应节点只有一个为空,则合并后的二叉树的对应节点为其中的非空节点;
如果两个二叉树的对应节点都不为空,则合并后的二叉树的对应节点的值为两个二叉树的对应节点的值之和,此时需要显性合并两个节点。
对一个节点进行合并之后,还要对该节点的左右子树分别进行合并。这是一个递归的过程
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
// dfs (一层层遍历)
// 只要有1方为空 就选取另一方
if (root1 == nullptr)
return root2;
else if (root2 == nullptr)
return root1;
// 两个子树都不为空
TreeNode* merged = new TreeNode(root1->val + root2->val); // 创建一个节点来存放 合并生成一个新的节点
merged->left = mergeTrees(root1->left, root2->left);// 左子树递归生成
merged->right = mergeTrees(root1->right, root2->right); // 右子树递归生成
return merged;
}
};
11: 从上到下打印二叉树
思路:利用层序遍历,打印没一层的节点
class Solution {
public:
vector levelOrder(TreeNode* root) {
vector res;
if (root == nullptr) {
return res;
}
// 本质上做层次遍历
// 是一个队列来存放遍历的树的节点
// dfs
queue q;
q.push(root);
while(!q.empty()) {
for (int i = 0; i < q.size(); i++) { // 这里迭代结束条件是当前层的节点总数
TreeNode* cur_node = q.front();
q.pop();
res.push_back(cur_node->val);
if (cur_node->left) q.push(cur_node->left); // 左子树
if (cur_node->right) q.push(cur_node->right); // 右子树
}
}
return res;
}
};
12: 搜索二叉树的最近公共祖先
思路:
(1)求路径
(2)求分叉点
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
// 求解每个节点的路径
vector res_p = GetPath(root, p);
vector res_q = GetPath(root, q);
TreeNode* res;
// 遍历节点的路径,当两者不同时候那么就是分叉点,那么就是最近公共祖先
for (int i = 0; i < min(res_p.size(), res_q.size()); i++) {
if (res_q[i] == res_p[i]) { // i的下标一一对应
res = res_q[i];
} else {
break;
}
}
return res;
}
// 利用二叉搜索树的性质 求解从根 节点到目标节点的路径,路径中的所有节点存放在数组中去
vector GetPath(TreeNode* root_node, TreeNode* tnode)
{
vector res;
TreeNode* root = root_node;
while (root!= NULL) {
if (root->val == tnode->val) {
res.push_back(root);
break;
} else if (root->val > tnode->val) {
res.push_back(root);
root = root->left;
} else if (root->val < tnode->val) {
res.push_back(root);
root = root->right;
}
}
return res;
}
};