1. 二叉树的理论基础
二叉树的种类:
满二叉树
完全二叉树
二叉搜索树
平衡二叉搜索树
二叉树的存储方式:
链式(指针)、顺序存储(数组)->顺序存储图示
如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
二叉树遍历方式
深度优先(DFS) :前序、中序、后序
广度优先(BFS):层序遍历
二叉树的定义
struct TreeNode
{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(nullptr),right(nullptr){}
};
2. 二叉树的遍历(递归)
递归的三要素
1. 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
2. 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
3. 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程
前序遍历
/**
* Definition for a binary tree node.
* struct TreeNode {
* 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 preorder(TreeNode* root, vector<int>& res)
{
if(root==nullptr) return ;
res.push_back(root->val);
preorder(root->left, res);
preorder(root->right, res);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
preorder(root, res);
return res;
}
};
中序遍历:
class Solution {
public:
void postorder(TreeNode* root, vector<int>&res)
{
if(root==nullptr) return;
postorder(root->left, res);
postorder(root->right,res);
res.push_back(root->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
postorder(root,res);
return res;
}
};
后序遍历:
class Solution {
public:
void inorder(TreeNode* root, vector<int>&res)
{
if(root==nullptr) return;
inorder(root->left, res);
res.push_back(root->val);
inorder(root->right,res);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
inorder(root,res);
return res;
}
};
3. 二叉树的遍历(迭代)
前序遍历:
迭代思路是使用栈,先压入根节点,然后pop栈,接着pop出的节点的右节点和左节点压入,循环往复。
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
if(root==nullptr) return {};
stack<TreeNode*> s;
s.push(root);
while(!s.empty())
{
TreeNode* tmp = s.top();
s.pop();
res.push_back(tmp->val);
if(tmp->right) s.push(tmp->right);
if(tmp->left) s.push(tmp->left);
}
return res;
}
};
后序遍历:
后序遍历有个投巧的办法,就是先实现根右左(类似前序遍历),然后再reverse一下即可。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
if(root==nullptr) return {};
stack<TreeNode*> s;
s.push(root);
while(!s.empty())
{
TreeNode* tmp = s.top();
s.pop();
res.push_back(tmp->val);
if(tmp->left) s.push(tmp->left);
if(tmp->right) s.push(tmp->right);
}
reverse(res.begin(),res.end());
return res;
}
};
中序遍历:
中序遍历的难点在于,处理的节点和遍历到的节点不是同一个节点,思路不同于前序和后序。中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
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;
}
};