1.二叉树概念
二叉树又一个个结点组成,结点包括自身的数值和指向左右孩子的指针(可能为空)。
树结点定义如下:
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) {}
};
2.二叉树的遍历(递归法)
题目链接:. - 力扣(LeetCode). - 力扣(LeetCode). - 力扣(LeetCode)
讲解链接:代码随想录 (programmercarl.com)
视频链接:每次写递归都要靠直觉? 这次带你学透二叉树的递归遍历!| LeetCode:144.前序遍历,145.后序遍历,94.中序遍历_哔哩哔哩_bilibili
第一次接触递归法,要注意递归三要素。这里以前序遍历为例。
第一是返回值与传入参数。本体递归函数不需要返回任何值,数值存储在事先新建的数组中。传入参数首先由树节点的指针,不管是root还是各个结点的左右孩子都是指针类型。还要有我们的结果数组,共两个参数。
第二是终止条件。本题的递归要在遇到空节点时终止。
第三是单层逻辑。本题的递归函数需要每次做到:将当前结点的值加入数组、递归左孩子、递归右孩子。
中序和后序遍历只需要交换递归本节点、左孩子、右孩子的顺序即可。
代码如下:
class Solution1 {
public:
void traversal(TreeNode* cur, vector<int>& vec){
if(cur == nullptr) return;
vec.push_back(cur->val);
traversal(cur->left, vec);
traversal(cur->right, vec);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> vec;
traversal(root, vec);
return vec;
}
};
class Solution1 {
public:
void traversal(TreeNode* cur, vector<int>& vec){
if(cur == nullptr) return;
traversal(cur->left, vec);
vec.push_back(cur->val);
traversal(cur->right, vec);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> vec;
traversal(root, vec);
return vec;
}
};
class Solution1 {
public:
void traversal(TreeNode* cur, vector<int>& vec){
if(cur == nullptr) return;
traversal(cur->left, vec);
traversal(cur->right, vec);
vec.push_back(cur->val);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> vec;
traversal(root, vec);
return vec;
}
};
3.二叉树的遍历(迭代法)
题目链接:. - 力扣(LeetCode). - 力扣(LeetCode). - 力扣(LeetCode)
讲解链接:代码随想录 (programmercarl.com)
视频链接:写出二叉树的非递归遍历很难么?这次让你不再害怕非递归!|二叉树的非递归遍历 | 二叉树的遍历迭代法 | 前序与中序_哔哩哔哩_bilibili
写出二叉树的非递归遍历很难么?这次再带你写出中序遍历的迭代法!|二叉树的非递归遍历 | 二叉树的遍历迭代法_哔哩哔哩_bilibili
前序遍历:
迭代法的好处在于时间复杂度与递归相同,但空间复杂度较低。
前序遍历需要使用到栈辅助,每次加入中结点,输出其值,将其出栈,再加入右孩子与左孩子(如果有的话)。先右后左出栈时就是先左后右,这样循环就能实现前序遍历。
中序遍历:
中序遍历用迭代法较为复杂,因为中序遍历的遍历顺序与处理结点循序(中在左之前处理,而左要在中之前输出)不同。
使用指针来遍历,用栈来输出。指针从根节点开始,向左孩子移动,直到遇到空节点。当遇到空节点时,指针回溯到上个结点(栈顶结点),输出栈顶结点并弹出,指针向所指结点(原栈顶结点)右孩子移动。这样循环直到栈为空且指针所指结点也为空时(循环过程中可能栈为空,但指针不为空),就遍历完了整个树。
后序遍历:
后序遍历的顺序是左右中,前序遍历是中左右。而前序遍历只要交换两行代码顺序就能将顺序变为中右左,正好是后序遍历的倒序。所以我们最后将结果数组反转即可。
代码如下:
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if(root == nullptr) return result;
st.push(root);
while (!st.empty()){
result.push_back(st.top()->val);
TreeNode* cur = st.top();
st.pop();
if (cur->right != nullptr)
st.push(cur->right);
if (cur->left != nullptr)
st.push(cur->left);
}
return result;
}
vector<int> inorderTraversal(TreeNode* root){
stack<TreeNode*> st;
vector<int> result;
TreeNode* cur = root;
while (cur != nullptr || !st.empty()){
if (cur != nullptr){
st.push(cur);
cur = cur->left;
} else{
cur = st.top();
st.pop();
result.push_back(cur->val);
cur = cur->right;
}
}
return result;
}
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if(root == nullptr) return result;
st.push(root);
while (!st.empty()){
result.push_back(st.top()->val);
TreeNode* temp = st.top();
st.pop();
if (temp->left != nullptr)
st.push(temp->left);
if (temp->right != nullptr)
st.push(temp->right);
}
for (int i = 0, j = result.size()-1; i < j; ++i, --j) {
swap(result[i], result[j]);
}
return result;
}
};
4.统一的迭代遍历
题目链接:. - 力扣(LeetCode). - 力扣(LeetCode). - 力扣(LeetCode)
讲解链接:代码随想录 (programmercarl.com)
上一节的前中后遍历代码形式并不统一,没有递归遍历方便好记。
但是还存在另一种能统一代码形式的迭代方式。以前序遍历为例。
主要思想是将马上要输出但是被压入栈(因为它的左右孩子需要入栈)的结点后加上空指针来标记,每次遇到空指针才输出下面的结点。
每次循环临时指针都等于栈顶结点,先弹出栈顶结点,再将左右孩子入栈,再将临时指针所指结点入栈并标记。如果它没有左右孩子,第二次循环临时指针就会等于空指针,这样就进行输出,并将空指针下的结点弹出。
而中序和后序只需要将左右孩子入栈与原栈顶结点再次入栈并标记的顺序调换即可(与遍历顺序正好相反)。
代码如下:
class Solution {
public:
vector<int> inorderTraversal(TreeNode *root) {
stack<TreeNode*> st;
vector<int> result;
if (root != nullptr)
st.push(root);
while (!st.empty()){
TreeNode* cur = st.top();
if (cur != nullptr){
st.pop();
if (cur->right) st.push(cur->right);
st.push(cur);
st.push(nullptr);
if (cur->left) st.push(cur->left);
} else{
st.pop();
cur = st.top();
st.pop();
result.push_back(cur->val);
}
}
return result;
}
};
5.层序遍历
题目链接:. - 力扣(LeetCode)
讲解链接:代码随想录 (programmercarl.com)
视频链接:讲透二叉树的层序遍历 | 广度优先搜索 | LeetCode:102.二叉树的层序遍历_哔哩哔哩_bilibili
二叉树的层序遍历是队列的经典应用,对于每一层的结点,将他们都加入队列,然后一一弹出,弹出时输出结点内容并将左右孩子加入队列,直到队列为空。
如果输出需要将每一层的内容分开输出,可以再每一层循环的开始时记录队列长度,根据长度再进行相应次数的子循环来输出和加入下一层的结点。
代码如下:
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root){
queue<TreeNode*> que;
vector<vector<int>> result;
if (root != nullptr)
que.push(root);
while (!que.empty()){
int size = que.size();
vector<int> temp;
for (int i = 0; i < size; ++i) {
TreeNode* cur = que.front();
temp.push_back(cur->val);
que.pop();
if (cur->left) que.push(cur->left);
if (cur->right) que.push(cur->right);
}
result.push_back(temp);
}
return result;
}
};