理论基础
- 二叉树的种类
- 满二叉树:只有度为0和2,且度为0的结点在同一层
- 完全二叉树:只有最底层可能没填满,且该层结点集中在左侧
- 二叉搜索树:有序树,左子树所有值 < 根节点值 < 右子树所有值
- 平衡二叉搜索树:空树或者左右子树高度差绝对值不超过1,且左右子树都是平衡二叉树
- map、set、multimap、multiset底层都是平衡二叉树
- 二叉树的存储方式
- 链式存储:指针
- 顺序存储:数组,如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2
- 遍历方式
- 深度优先遍历(递归,迭代)
- 前序:中左右
- 中序:左中右
- 后序:左右中
- 广度优先遍历(迭代)
- 深度优先遍历(递归,迭代)
- 二叉树的定义(链式存储)
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
题目:Leetcode144,145,94
-
递归遍历
- 学会递归算法步骤:
- 确定递归函数的参数和返回值
- 确定终止条件
- 确定单层递归的逻辑
- 思路:当前节点为空时结束本层递归,然后根据遍历方法编写单层递归逻辑
- 前序遍历
-
class Solution { public: void travalsal(TreeNode* cur, vector<int>& vec) { //1.确定参数(当前节点、存储结果的数组),确定返回值(void) //2.确定终止条件:当前节点为空,则停止 if(cur == NULL) return; //3.确定单层逻辑 vec.push_back(cur->val);//中 travalsal(cur->left, vec);//左 travalsal(cur->right, vec);//右 } vector<int> preorderTraversal(TreeNode* root) { vector<int> res; travalsal(root, res); return res; } };
- 中序遍历
-
class Solution { public: void travalsal(TreeNode* cur, vector<int>& vec) { //1.确定参数(当前节点、存储结果的数组),确定返回值(void) //2.确定终止条件:当前节点为空,则停止 if(cur == NULL) return; //3.确定单层逻辑 travalsal(cur->left, vec);//左 vec.push_back(cur->val);//中 travalsal(cur->right, vec);//右 } vector<int> inorderTraversal(TreeNode* root) { vector<int> res; travalsal(root, res); return res; } };
- 后序遍历
-
class Solution { public: void travalsal(TreeNode* cur, vector<int>& vec) { //1.确定参数(当前节点、存储结果的数组),确定返回值(void) //2.确定终止条件:当前节点为空,则停止 if(cur == NULL) return; //3.确定单层逻辑 travalsal(cur->left, vec);//左 travalsal(cur->right, vec);//右 vec.push_back(cur->val);//中 } vector<int> postorderTraversal(TreeNode* root) { vector<int> res; travalsal(root, res); return res; } };
- 学会递归算法步骤:
-
迭代遍历
- 思路:
- 对于前序遍历来说,每次先处理中间节点,先将根节点放入栈中,再将右孩子、左孩子入栈(栈的特性)。
- 而中序遍历时,无法直接复用前序的逻辑,因为在迭代过程中有两个操作,分别是处理:将元素放进result数组中,访问:遍历节点。在前序遍历时,要访问的元素和要处理的元素顺序是一致的,都是中间节点;而中序遍历时,就出现了处理顺序和访问顺序是不一致的情况。因此,使用迭代法进行中序遍历时,需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
- 后序遍历只需调整前序的代码顺序,变成中右左遍历,最后反转结果数组。
- 前序遍历
-
class Solution { public: vector<int> preorderTraversal(TreeNode* root) { stack<TreeNode*> st;//遍历节点的栈 vector<int> res;//存放遍历结果 if(root == nullptr) return res; st.push(root); while(!st.empty()) { TreeNode* node = st.top();//中 st.pop(); res.push_back(node->val); //由于栈先进后出的特性,所以处理时要先(右)后(左) if(node->right) st.push(node->right);//右 if(node->left) st.push(node->left);//左 } return res; } };
- 中序遍历
-
class Solution { public: vector<int> inorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> res; TreeNode* cur = root; while(cur || !st.empty()) { if(cur) { st.push(cur); cur = cur->left; } else { cur = st.top(); st.pop(); res.push_back(cur->val); cur = cur->right; } } return res; } };
- 后序遍历
-
class Solution { public: vector<int> postorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> res; if(root == nullptr) return res; st.push(root); while(!st.empty()) { //依据栈的特性,while结束时res的遍历结果是(中右左),只需再进行反转 TreeNode* node = st.top();//中 st.pop(); res.push_back(node->val); if(node->left) st.push(node->left);//左 if(node->right) st.push(node->right);//右 } //中右左,反转得到后序遍历(左右中) reverse(res.begin(), res.end()); return res; } };
- 思路:
-
统一迭代
- 思路:前面的迭代方法无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况,那就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记(在要处理的节点放入栈之后,紧接着放入一个空指针作为标记)。
- 前序遍历
-
class Solution { public: vector<int> preorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> res; if(root == nullptr) return res; st.push(root); while(!st.empty()) { TreeNode* node = st.top(); if(node) { st.pop(); if(node->right) st.push(node->right);//右 if(node->left) st.push(node->left);//左 st.push(node);//中 st.push(nullptr); } else { st.pop(); node = st.top(); st.pop(); res.push_back(node->val); } } return res; } };
- 中序遍历
-
class Solution { public: vector<int> inorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> res; if(root == nullptr) return res; st.push(root); while(!st.empty()) { TreeNode* node = st.top(); if(node) { //若节点不为空 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();//弹出空节点 node = st.top();//获取要放进结果集的节点 st.pop(); res.push_back(node->val); } } return res; } };
- 后序遍历
-
class Solution { public: vector<int> postorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> res; if(root == nullptr) return res; st.push(root); while(!st.empty()) { TreeNode* node = st.top(); if(node) { st.pop(); st.push(node);//中 st.push(nullptr); if(node->right) st.push(node->right);//右 if(node->left) st.push(node->left);//左 } else { st.pop(); node = st.top(); st.pop(); res.push_back(node->val); } } return res; } };
总结
迭代方法还需要多理解