6.二叉树.遍历
- 深度优先遍历
- 二叉树的递归遍历
- 二叉树的迭代遍历
- 广度优先遍历
深度优先遍历
二叉树的递归遍历
递归算法的三个要素:
- 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
- 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息。
- 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
以前序,中,后遍历为例子:
// 需要处理的参数是节点指针
void traversal(TreeNode* cur, vector<int>& vec) {// 若指针为空,则递归结束if (cur == NULL) return;vec.push_back(cur->val); // 中traversal(cur->left, vec); // 左traversal(cur->right, vec); // 右
}void traversal(TreeNode* cur, vector<int>& vec) {if (cur == NULL) return;traversal(cur->left, vec); // 左vec.push_back(cur->val); // 中traversal(cur->right, vec); // 右
}void traversal(TreeNode* cur, vector<int>& vec) {if (cur == NULL) return;traversal(cur->left, vec); // 左traversal(cur->right, vec); // 右vec.push_back(cur->val); // 中
}
二叉树的迭代遍历
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。因此我们用栈也可以实现二叉树的前中后序遍历。
一般迭代法
- 前序遍历:每次先处理的是中间节点,因此先将根节点压入栈中,没弹出一个节点,则将该节点的右,左子节点按顺序压入栈中(空节点不入栈)
- 中序遍历:前序排列因为要访问的元素和要处理的元素顺序是一致的,都是中间节点;而中序遍历,中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点。因此那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。基本原理是设计一个while循环,并将cur指针初始化指向根节点,当cur非空时,将cur压入栈中,然后将
cur
指向cur->left
,进行下一次循环;当cur
空时,将cur
更改为栈的top
(并将top
弹出),并且在结果数组中压入更新后cur->val
,随后将cur
指向cur->right
,进入下一次循环。
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;}
};
- 后序遍历:所以后序遍历只需要前序遍历的代码稍作修改就可以了,根据次序的不同,通过
reverse
对vector
数组的元素反转即可。
二叉树的统一迭代法
在2题中,无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。此时我们可以将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记——即要处理的节点放入栈之后,紧接着放入一个空指针作为标记。统一形式也会更好理解,while
每次遍历的节点是栈顶的节点,根据不同的遍历次序种类,将cur、cur->lef、cur->right
压入栈中,然后利用处理节点的条件cur为空时,进行赋值。
class Solution {
public:// 前序排列vector<int> preorderTraversal(TreeNode* root) {vector<int> result;stack<TreeNode*> st;if (root != NULL) st.push(root);while (!st.empty()) {TreeNode* node = st.top();if (node != NULL) {st.pop();if (node->right) st.push(node->right); // 右if (node->left) st.push(node->left); // 左st.push(node); // 中st.push(NULL);} else {st.pop();node = st.top();st.pop();result.push_back(node->val);}}return result;// 中序遍历vector<int> inorderTraversal(TreeNode* root) {vector<int> result;stack<TreeNode*> st;if (root != NULL) st.push(root);while (!st.empty()) {TreeNode* node = st.top();if (node != NULL) {st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中if (node->right) st.push(node->right); // 添加右节点(空节点不入栈)st.push(node); // 添加中节点st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。if (node->left) st.push(node->left); // 添加左节点(空节点不入栈)} else { // 只有遇到空节点的时候,才将下一个节点放进结果集st.pop(); // 将空节点弹出node = st.top(); // 重新取出栈中元素st.pop();result.push_back(node->val); // 加入到结果集}}return result;}
};
广度优先遍历
层序遍历又称为广度优先搜索,层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树,需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。
1.二叉树的层序遍历1
一般而言使用std::queue队列存储遍历到的元素,遵循先进先出的原则,当弹出front头部元素时,压入该节点的左,右非空节点,然后进行下一次遍历。
在这里插入代码片
2.二叉树的层序遍历2
给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)。这题答案是将二叉树的层序遍历1中使用std::reverse()
函数,反转里面的元素即可。
3.二叉树的右视图
定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值,也就是返回每层最右边的元素即可。在层序遍历的基础上,使用数组存储每层最后一个元素即可。
4.二叉数的层平均值
给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。在层序遍历的基础上,使用数组存储每层元素的平均值。
5.N叉树的层序遍历
给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
N叉树的层序遍历与二叉数的原理一致,在压入子字节点时需要使用一个for循环遍历所有的字节点接口。
6.在每个树行中找最大值
给定一个非空二叉树, 返回一个由每层节点最大值组成的数组。在层序遍历的基础上,使用数组存储每层元素的最大值,即在每层遍历时加入一个数值比较的环节。
7.填充每个节点的下一个右侧节点指针
给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
struct Node {int val;Node *left;Node *right;Node *next;
}
在层序遍历的基础上,在单层遍历的时候记录一下本层的头部节点,然后在遍历的时候让前一个节点指向本节点就可以了,类似双指针的方法,令L_p,R_p两个指针,R_p指针用于随层序遍历更新位置,L_p用于晚于R_p更新一步。
Node* connect(Node* root) {queue<Node*> que;if (root != NULL) que.push(root);while (!que.empty()) {int size = que.size(); // 获取每层的元素个数// vector<int> vec;Node* nodePre;Node* node;for (int i = 0; i < size; i++) {if (i == 0) {nodePre = que.front(); // 取出一层的头结点que.pop();node = nodePre;} else {node = que.front();que.pop();nodePre->next = node; // 本层前一个节点next指向本节点nodePre = nodePre->next;}if (node->left) que.push(node->left);if (node->right) que.push(node->right);}nodePre->next = NULL; // 本层最后一个节点指向NULL}return root;}
8.二叉树的最大深度
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数( 叶子节点是指没有子节点的节点)
使用层序遍历法来解决正好,每次完成一层的遍历,便计数一次。
int maxDepth(TreeNode* root) {if (root == NULL) return 0;int depth = 0;queue<TreeNode*> que;que.push(root);while(!que.empty()) {int size = que.size();depth++; // 记录深度for (int i = 0; i < size; i++) {TreeNode* node = que.front();que.pop();if (node->left) que.push(node->left);if (node->right) que.push(node->right);}}return depth;}
9.二叉树的最小深度
同样使用层序遍历,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点。这要遍历到该节点的左,右子节点都是nullptr,即可返回该层数。
int minDepth(TreeNode* root) {if (root == NULL) return 0;int depth = 0;queue<TreeNode*> que;que.push(root);while(!que.empty()) {int size = que.size();depth++; // 记录最小深度for (int i = 0; i < size; i++) {TreeNode* node = que.front();que.pop();if (node->left) que.push(node->left);if (node->right) que.push(node->right);if (!node->left && !node->right) { // 当左右孩子都为空的时候,说明是最低点的一层了,退出return depth;}}}return depth;