代码随想录算法训练营
二叉树理论基础
二叉树的种类
1. 满二叉树
满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
性质:深度为K ,节点数: 2^k-1
2. 完全二叉数
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。
底层从左到右是连续的。
3. 二叉搜索树
二叉搜索树是有数值的,二叉搜索树是一个有序树。
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
3. 平衡二叉搜索树
又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn,注意我这里没有说unordered_map、unordered_set,unordered_map、unordered_set底层实现是哈希表。
二叉树的存储方式
二叉树可以链式存储,也可以顺序存储。
那么链式存储方式就用指针, 顺序存储的方式就是用数组。
链式存储:
顺序存储:
用数组来存储二叉树如何遍历的呢?
如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
但是用链式表示的二叉树,更有利于我们理解,所以一般我们都是用链式存储二叉树。
二叉树遍历方式
二叉树主要有两种遍历方式:
- 深度优先遍历:先往深走,遇到叶子节点再往回走。(前中后序遍历)------递归、迭代法
- 广度优先遍历:一层一层的去遍历。(层序遍历)------迭代法
前序遍历:中左右
中序遍历:左中右
后序遍历:左右中
二叉树的定义
c++
struct TreeNode{
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x):val(x),left(NULL),right(NULL) {}
};
二叉树的递归遍历
递归三步走:
- 确定递归函数的参数和返回值
- 确定终止条件
- 确定单层递归的逻辑
144.前序遍历
中 左 右
c++
//构造递归函数,确定传入的参数,前序遍历,顺序:中左右,传入节点,
void preorder(TreeNode* root, vector<int>& result)
{
if(root==nullptr) return;
//中
result.push_back(root->val);
//左
preorder(root->left,result);
//右
preorder(root->right, result);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
preorder(root, result);
return result;
}
145.后序遍历
c++
//递归函数
void backorder(TreeNode* root, vector<int>& result)
{
//1. 定义递归终止条件
if(root==nullptr) return;
//左 右 中
backorder(root->left, result);
backorder(root->right, result);
result.push_back(root->val);
}
vector<int> postorderTraversal(TreeNode* root) {
//数组 ,存储结果
vector<int> result;
backorder(root, result);
return result;
}
94.中序遍历
c++
//中序遍历
void midorder(TreeNode* cur, vector<int>& result)
{
//递归结束
if(cur==nullptr) return;
//左
midorder(cur->left,result);
//中
result.push_back(cur->val);
//右
midorder(cur->right, result);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
midorder(root, result);
return result;
}
二叉树迭代遍历
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
用栈实现前中后序遍历
前序遍历(迭代法)
c++
vector<int> preorderTraversal(TreeNode* root) {
//迭代法实现前序遍历
//数组 result存储 遍历结果
vector<int> result;
//栈模拟实现递归过程,栈的数据类型是二叉树节点(指针)
stack<TreeNode*> st;
if(root==nullptr) return result;
//root根节点入栈
st.push(root);
//中 左 右
while(!st.empty())
{
//取出栈中元素,将其放入到result数组中
TreeNode* cur = st.top();
st.pop();
result.push_back(cur->val);
//先右后左,出栈顺序为左、右,符合前序遍历逻辑
if(cur->right) st.push(cur->right);
if(cur->left) st.push(cur->left);
}
return result;
}
后序遍历(迭代法)
c++
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
//前序遍历 中左右 -> 中右左-> 翻转reverse -> 左右中
vector<int> result;
stack<TreeNode*> st;
//root为nullptr 返回result
if(root==nullptr) 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;
}
};
中序遍历(迭代法)
c++
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode* cur = root;
while (cur != NULL || !st.empty()) {
if (cur != NULL) { // 指针来访问节点,访问到最底层
st.push(cur); // 将访问的节点放进栈
cur = cur->left; // 左
} else {
cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
st.pop();
result.push_back(cur->val); // 中
cur = cur->right; // 右
}
}
return result;
}
};
二叉树层序遍历(广度优先搜索)
解题思路:
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。
流程:
- 判断根节点是否为空,否则存入队列中;
- 遍历的终止条件,queue中为空,则停止遍历;
- 控制每一层的弹出节点的个数,size存当前queue中的元素,当size=1时,则为第一层,循环条件为一次;将queue中节点弹出并存放到vector一维数组中;
- 向下移动节点,根节点的左节点和右节点入队;
- 得到当前队列的size = 2;循环终止2次;弹出左节点,并存放到一维数组中,并将左节点的左子节点和右子节点存放到队列中;第二次循环,弹出右节点,并将右左子节点和右右子节点插入队列中;
- 获得当前队列的长度为size = 4;弹出队列中元素存放到一维数组中;
- 最后将一位数组结果保存到二维数组中,返回result;
核心需要记录size,记录的是本层的需要弹出的节点数量
c++
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
//存储二维数组,最终结果
vector<vector<int>> result;
//1 判断root不为空,将第一个节点加入到队列中
if(root!=nullptr) que.push(root);
//2 遍历终止条件,队列中没有元素,!que.empty();
while(!que.empty())
{
// 3 控制当前节点数量 size ,控制弹出元素的数量
int size = que.size();
// 4 定义一维数组,存放队列中元素,最终结果需要用二维数组显示
vector<int> vec;
//5 遍历队列
for(int i = 0; i<size;i++)
{
// 获取队列中的元素,弹出
TreeNode* node = que.front();
que.pop();
// 存储结果,记录完本层结果
vec.push_back(node->val);
//节点移到下一层
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
//一维数组保存后
result.push_back(vec);
}
return result;
}
};