代码随想录:二叉树的遍历方式
二叉树的递归遍历
递归有说法的:
递归的实现就是,每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈(后进先出)中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
一看就会,一写就废!
递归算法的三个要素:
-
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
-
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
-
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
栗子🌰:使用前中后后序遍历一个树并将结果返回,返回一个list/vector。
思路:
首先确定参数、返回值即函数返回类型:指向节点的指针,数组vector的引用,因为结果加到数组中,所以不需要返回其它东西,函数返回类型为空。
1 | void traversal(TreeNode* cur, vector< int > res) |
第二确定终止条件:当前节点为空就停
1 | if (cur==NULL) return ; |
最后确定单层逻辑,即怎么引用自己:考虑到三种不同方式,前中后序,三个版本语句不同
void traversal(TreeNode* cur, vector<int> res){ if(cur==NULL) return; //前序遍历 res.push(cur->val); traversal(cur->left, res); traversal(cur->right, res); //中序遍历 /* traversal(cur->left, res); res.push(cur->val); traversal(cur->right, res); */ //后序遍历 /* traversal(cur->left, res); traversal(cur->right, res); res.push(cur->val); */ }
PYTHON版本二叉树的递归遍历
用一个列表生成二叉树:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from typing import List class TreeNode: def __init__( self , val = 0 , left = None , right = None ): self .val = val self .left = left self .right = right def list_to_binarytree(nums): def level(index): if index > = len (nums) or nums[index] is None : return None root = TreeNode(nums[index]) root.left = level( 2 * index + 1 ) root.right = level( 2 * index + 2 ) return root return level( 0 ) |
# 前序遍历-递归-LC144_二叉树的前序遍历 class Solution0: def preorderTraversal(self, root: TreeNode) -> List[int]: # 保存结果 result = [] def traversal(root: TreeNode): if root == None: return result.append(root.val) # 前序 traversal(root.left) # 左 traversal(root.right) # 右 traversal(root) return result # 中序遍历-递归-LC94_二叉树的中序遍历 class Solution1: def inorderTraversal(self, root: TreeNode) -> List[int]: result = [] def traversal(root: TreeNode): if root == None: return traversal(root.left) # 左 result.append(root.val) # 中序 traversal(root.right) # 右 traversal(root) return result # 后序遍历-递归-LC145_二叉树的后序遍历 class Solution2: def postorderTraversal(self, root: TreeNode) -> List[int]: result = [] def traversal(root: TreeNode): if root == None: return traversal(root.left) # 左 traversal(root.right) # 右 result.append(root.val) # 后序 traversal(root) return result binary_tree = list_to_binarytree([3,9,20,None,None,15,7]) task0 = Solution0() task1 = Solution1() task2 = Solution2() print(task0.preorderTraversal(binary_tree)) print(task1.inorderTraversal(binary_tree)) print(task2.postorderTraversal(binary_tree)) 输出: [3, 9, 20, 15, 7] [9, 3, 15, 20, 7] [9, 15, 7, 20, 3]
二叉树的迭代遍历
既然递归是用了递归调用的特点(栈),那么直接用栈做可不可以呢?当然!这就是下面要说的迭代遍历方法。
迭代前序遍历
思路:
- 前序遍历先处理中间节点,那么先把根节点压入栈中
- 按理说是 中 - 左 - 右的顺序处理,由于使用了栈,先进后出,所以应该先处理右节点部分,再处理左节点部分
- 遇到空节点,空节点不入栈
vector<int> preorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> res; st.push(root); while (!st.empty()) { //节点不为空,一定要出栈 前序,先做中间节点,再做 右 左 TreeNode* tempNode = st.top(); st.pop(); res.push_back(tempNode->val); if (tempNode->right) st.push(tempNode->right); if (tempNode->left) st.push(tempNode->left); } return res; }
迭代后序遍历
思路:后序遍历迭代法和前序遍历差不多,稍微改下顺序即可,
后序左右中,但是用栈,看图
//后序遍历 vector<int> postorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> res; st.push(root); while (!st.empty()) { //节点不为空,一定要出栈 前序,先做中间节点,再做 右 左 TreeNode* tempNode = st.top(); st.pop(); res.push_back(tempNode->val); if (tempNode->left) st.push(tempNode->left); if (tempNode->right) st.push(tempNode->right); } reverse(res.begin(), res.end()); return res; }
迭代中序遍历
思路:
中序遍历和上面两种很不相同,因为要先把中间节点输出,即出栈,那么他就要后入栈,但是又是最先访问的,即处理顺序和访问顺序不一致(迭代先序遍历和迭代后序遍历的处理顺序和访问顺序一致,所以代码简洁)。
在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
//中序遍历 左中右 vector<int> inorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> res; TreeNode* cur = root; while (cur != NULL || !st.empty()) { if (cur != NULL) {//使用指针访问节点,访问到最底层 st.push(cur); //处理左边节点 cur = cur->left; } else { //左边处理完毕,处理中,也就是上一个cur,即栈顶元素 cur = st.top(); st.pop(); res.push_back(cur->val); //中间处理完毕,开始处理右侧 cur = cur->right; } } return res; }
总的来说,相比于递归法,迭代法不能够稍微改点代码就实现前中后序遍历,是因为用栈处理的时候中序遍历中访问顺序和处理顺序不同,带来的问题。
那么有没有什么方法能使迭代二叉树方法的代码统一起来?说白了就是搞定上面这个访问和处理顺序不一致的问题:
如果每访问一个栈顶节点不空,先把这个节点从栈顶pop出来,按想要的处理顺序往栈中插入节点(由于是栈,前序应变为 右左中),但是要为中间节点做个标记,即插入一个空节点NULL
每次处理的时候,当访问到一个栈顶节点为NULL,就把NULL弹出,把下一个栈顶元素作为输出处理。
统一迭代前序
vector<int> uniformpreorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> res; if(root != NULL) st.push(root); //当栈为空时,结束 while (!st.empty()) { TreeNode* tempNode = st.top(); if (tempNode != NULL) {//一直访问到最底层 st.pop(); if (tempNode->right) st.push(tempNode->right); if (tempNode->left) st.push(tempNode->left); st.push(tempNode); // 添加中节点 st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。 } else { //栈顶是空元素,先将栈顶元素弹出 st.pop(); tempNode = st.top();//不可能两个NULL连着,重新取出栈顶元素,为 st.pop(); res.push_back(tempNode->val); } } return res; }
统一迭代中序
vector<int> uniforminorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> res; if (root != NULL) st.push(root); //当栈为空时,结束 while (!st.empty()) { TreeNode* tempNode = st.top(); if (tempNode != NULL) {//一直访问到最底层 st.pop(); if (tempNode->right) st.push(tempNode->right); st.push(tempNode); // 添加中节点 st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。 if (tempNode->left) st.push(tempNode->left); } else { //栈顶是空元素,先将栈顶元素弹出 st.pop(); tempNode = st.top();//不可能两个NULL连着,重新取出栈顶元素,为 st.pop(); res.push_back(tempNode->val); } } return res; }
统一迭代后序
vector<int> uniformpostorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> res; if (root != NULL) st.push(root); //当栈为空时,结束 while (!st.empty()) { TreeNode* tempNode = st.top(); if (tempNode != NULL) {//一直访问到最底层 st.pop(); st.push(tempNode); // 添加中节点 st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。 if (tempNode->right) st.push(tempNode->right); if (tempNode->left) st.push(tempNode->left); } else { //栈顶是空元素,先将栈顶元素弹出 st.pop(); tempNode = st.top();//不可能两个NULL连着,重新取出栈顶元素,为 st.pop(); res.push_back(tempNode->val); } } return res; }
二叉树的层序遍历-BFS-广度优先搜索
顾名思义,就是一层一层遍历二叉树。用队列queue来实现!
层序遍历一个二叉树
102. 二叉树的层序遍历 - 力扣(LeetCode) (leetcode-cn.com)
给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
思路:
- 直接上层序遍历BFS
- 使用队列做辅助,队列为空的时候遍历结束
- 对于每一层,一层的节点多少个在进入while时候确定,后面会改变,所以for循环中不能用size(),而应该用固定长度
- 使用中间vector来保存一层的值最后在push_back到最后输出
- 每一层的for循环,先弹出队首元素,在确定他的左右儿子,不为NULL就加入到队尾
class Solution { public: vector<vector<int>> levelOrder(TreeNode* root) { queue<TreeNode*> queueNode; if(root != NULL) queueNode.push(root); //空二叉树拳头警告 vector<vector<int>> res; while(!queueNode.empty()){ //每次遍历一层,一层的节点在进入while时候确定,后面会改变,所以for循环中不能用size(),而应该用固定长度 int layerSize = queueNode.size(); vector<int> layerRes; for(int i =0;i<layerSize;i++){ //先弹出队首元素,再check他的左右儿子 TreeNode *tempNode = queueNode.front(); queueNode.pop(); layerRes.push_back(tempNode->val); if(tempNode->left) queueNode.push(tempNode->left); if(tempNode->right) queueNode.push(tempNode->right); } res.push_back(layerRes); } return res; } };
这个例子可以作为层序遍历的模板来使用。
二叉树的层次遍历 II
107. 二叉树的层序遍历 II - 力扣(LeetCode) (leetcode-cn.com)
给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
思路:就是把上面一题的结果反转一下就好了
1 | reverse(res.begin(),res.end()); //二维链表也可以反转 |
python中:
results.reverse()
二叉树的右视图
199. 二叉树的右视图 - 力扣(LeetCode) (leetcode-cn.com)
给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的(表层)节点值。
思路:右视图,每一层的最右边一个元素,从顶至底输出。
也就是说,使用层序遍历判断是不是每一层的最后一个节点即可
/** * 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: vector<int> rightSideView(TreeNode* root) { queue<TreeNode*> queueNode; if(root != NULL) queueNode.push(root); //空二叉树拳头警告 vector<int> res; while(!queueNode.empty()){ //每次遍历一层,一层的节点在进入while时候确定,后面会改变,所以for循环中不能用size(),而应该用固定长度 int layerSize = queueNode.size(); for(int i =0;i<layerSize;i++){ //先弹出队首元素,再check他的左右儿子 TreeNode *tempNode = queueNode.front(); queueNode.pop(); if(i==layerSize-1) res.push_back(tempNode->val); if(tempNode->left) queueNode.push(tempNode->left); if(tempNode->right) queueNode.push(tempNode->right); } } return res; } };
注意,在python中最好是用deque,因为deque的popleft复杂度为$O(1)$,而list的复杂度为$O(n)$,而且用切片,方便取到最后一个元素
二叉树的层平均值
637. 二叉树的层平均值 - 力扣(LeetCode) (leetcode-cn.com)
给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。
思路,层序遍历,把每层的值累加到一个layerSum,最后除去每一层的节点数,把得到的元素加入res
class Solution { public: vector<double> averageOfLevels(TreeNode* root) { queue<TreeNode*> queueNode; if(root != NULL) queueNode.push(root); //空二叉树拳头警告 vector<double> res; while(!queueNode.empty()){ //每次遍历一层,一层的节点在进入while时候确定,后面会改变,所以for循环中不能用size(),而应该用固定长度 int layerSize = queueNode.size(); double layerSum = 0.0; for(int i =0;i<layerSize;i++){ //先弹出队首元素,再check他的左右儿子 TreeNode *tempNode = queueNode.front(); queueNode.pop(); layerSum += tempNode->val; if(tempNode->left) queueNode.push(tempNode->left); if(tempNode->right) queueNode.push(tempNode->right); } res.push_back(layerSum/layerSize); } return res; } };
N叉树的层序遍历
429. N 叉树的层序遍历 - 力扣(LeetCode) (leetcode-cn.com)
给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
思路,N叉不M叉,没那么恐怖,把check所有儿子,变为检查所有的N个儿子节点就可以:
查看一下他给出的N叉节点结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /* // Definition for a Node. class Node { public: int val; vector<Node*> children; Node() {} Node(int _val) { val = _val; } Node(int _val, vector<Node*> _children) { val = _val; children = _children; } }; */ |
题解:使用的节点是Node, 所有的子节点在一个vector中了
class Solution { public: vector<vector<int>> levelOrder(Node* root) { queue<Node*> queueNode; if(root != NULL) queueNode.push(root); //空二叉树拳头警告 vector<vector<int>> res; while(!queueNode.empty()){ //每次遍历一层,一层的节点在进入while时候确定,后面会改变,所以for循环中不能用size(),而应该用固定长度 int layerSize = queueNode.size(); vector<int> layerRes; for(int i =0;i<layerSize;i++){ //先弹出队首元素,再check他的左右儿子 Node *tempNode = queueNode.front(); queueNode.pop(); layerRes.push_back(tempNode->val); for(int i=0;i<tempNode->children.size();i++){ if(tempNode->children[i]) queueNode.push(tempNode->children[i]); } } res.push_back(layerRes); } return res; } };
在每个树行中找最大值
515. 在每个树行中找最大值 - 力扣(LeetCode) (leetcode-cn.com)
给定一棵二叉树的根节点 root
,请找出该二叉树中每一层的最大值。
思路,处理每一层的代码块中,维护一个最大值即可.
/** * 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: vector<int> largestValues(TreeNode* root) { queue<TreeNode*> queueNode; if(root != NULL) queueNode.push(root); //空二叉树拳头警告 vector<int> res; while(!queueNode.empty()){ //每次遍历一层,一层的节点在进入while时候确定,后面会改变,所以for循环中不能用size(),而应该用固定长度 int layerSize = queueNode.size(); int maxi = INT_MIN ; for(int i =0;i<layerSize;i++){ //先弹出队首元素,再check他的左右儿子 TreeNode *tempNode = queueNode.front(); queueNode.pop(); maxi = max(maxi,tempNode->val); if(tempNode->left) queueNode.push(tempNode->left); if(tempNode->right) queueNode.push(tempNode->right); } res.push_back(maxi); } return res; } };
填充每个节点的next指针指向同层的右侧节点
116. 填充每个节点的下一个右侧节点指针 - 力扣(LeetCode) (leetcode-cn.com)
给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
1 2 3 4 5 6 | struct Node { int val; Node *left; Node *right; Node *next; } |
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
思路:由于二叉树是满二叉树,所以每一层依次遍历两个节点,前面指向后面即可
如果是最开始,注意初始步骤,以及使用的结构体Node,和返回值
/* // Definition for a Node. class Node { public: int val; Node* left; Node* right; Node* next; Node() : val(0), left(NULL), right(NULL), next(NULL) {} Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {} Node(int _val, Node* _left, Node* _right, Node* _next) : val(_val), left(_left), right(_right), next(_next) {} }; */ class Solution { public: Node* connect(Node* root) { queue<Node*> queueNode; if(root != NULL) queueNode.push(root); //空二叉树拳头警告 //vector<vector<int>> res; while(!queueNode.empty()){ //每次遍历一层,一层的节点在进入while时候确定,后面会改变,所以for循环中不能用size(),而应该用固定长度 int layerSize = queueNode.size(); Node * pre; Node * cur; for(int i =0;i<layerSize;i++){ //初始和后面的不一样 if(i==0){ pre = queueNode.front(); queueNode.pop(); cur= pre; }else{ cur = queueNode.front(); queueNode.pop(); pre->next = cur; pre = cur; } if(cur->left) queueNode.push(cur->left); if(cur->right) queueNode.push(cur->right); } } return root; } };
P.S. 那如果不是满二叉树,而是普普通通的二叉树呢,思路:最后一个节点指向NULL
class Solution { public: Node* connect(Node* root) { queue<Node*> queueNode; if(root != NULL) queueNode.push(root); //空二叉树拳头警告 //vector<vector<int>> res; while(!queueNode.empty()){ //每次遍历一层,一层的节点在进入while时候确定,后面会改变,所以for循环中不能用size(),而应该用固定长度 int layerSize = queueNode.size(); Node * pre; Node * cur; for(int i =0;i<layerSize;i++){ //初始和后面的不一样 if(i==0){ pre = queueNode.front(); queueNode.pop(); cur= pre; }else{ cur = queueNode.front(); queueNode.pop(); pre->next = cur; pre = cur; } if(cur->left) queueNode.push(cur->left); if(cur->right) queueNode.push(cur->right); } } return root; } };
二叉树的最大深度
104. 二叉树的最大深度 - 力扣(LeetCode) (leetcode-cn.com)
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点
思路:层序遍历,当每遍历一层,就把深度加1,当遍历完最后一层,队列为空,退出。
/** * 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: int maxDepth(TreeNode* root) { queue<TreeNode*> queueNode; if(root != NULL) queueNode.push(root); //空二叉树拳头警告 //vector<vector<int>> res; int depth = 0; while(!queueNode.empty()){ //每次遍历一层,一层的节点在进入while时候确定,后面会改变,所以for循环中不能用size(),而应该用固定长度 int layerSize = queueNode.size(); for(int i =0;i<layerSize;i++){ TreeNode *tempNode = queueNode.front(); queueNode.pop(); if(tempNode->left) queueNode.push(tempNode->left); if(tempNode->right) queueNode.push(tempNode->right); } depth+=1; } return depth; } };
二叉树的最小深度
111. 二叉树的最小深度 - 力扣(LeetCode) (leetcode-cn.com)
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
错的思路:当某层节点数不为2^(k-1)时,就可能为最小深度
注意,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点
所以只有当某个节点左右孩子都指向NULL,才作为最小深度的层的节点
/** * 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: int minDepth(TreeNode* root) { queue<TreeNode*> queueNode; if(root != NULL) queueNode.push(root); if(root == NULL) return 0; //空二叉树拳头警告 //vector<vector<int>> res; int minDepth = 1; while(!queueNode.empty()){ //每次遍历一层,一层的节点在进入while时候确定,后面会改变,所以for循环中不能用size(),而应该用固定长度 int layerSize = queueNode.size(); for(int i =0;i<layerSize;i++){ TreeNode *tempNode = queueNode.front(); queueNode.pop(); if(tempNode->left) queueNode.push(tempNode->left); if(tempNode->right) queueNode.push(tempNode->right); if(!tempNode->left && !tempNode->right) return minDepth; } minDepth+=1; } return minDepth; } };
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2021-03-03 7K7K小游戏
2021-03-03 Linux+Gurobi-python调用