14代码随想录训练营第14天|二叉树part01| 理论基础、二叉树的迭代遍历、二叉树的统一迭代法
理论基础
二叉树结点的定义:
链表形式:
python
class TreeNode:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
二叉树的递归遍历
代码随想录
一看就会,一写就废
本篇将介绍前后中序的递归写法,一些同学可能会感觉很简单,其实不然,我们要通过简单题目把方法论确定下来,有了方法论,后面才能应付复杂的递归。
这里帮助大家确定下来递归算法的三个要素。每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!
-
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
-
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
-
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
以下以前序遍历为例:
- **确定递归函数的参数和返回值:**因为要打印出前序遍历节点的数值,所以参数里需要传入vector来放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值,所以递归函数返回类型就是void,代码如下:
void traversal(TreeNode* cur, vector<int>& vec)
- 确定终止条件:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要结束了,所以如果当前遍历的这个节点是空,就直接return,代码如下:
if (cur == NULL) return;
- 确定单层递归的逻辑:前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中节点的数值,代码如下:
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
单层递归的逻辑就是按照中左右的顺序来处理的,这样二叉树的前序遍历,基本就写完了,再看一下完整代码:
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
144. 二叉树的前序遍历
题目链接: 144. 二叉树的前序遍历
代码:
python
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def pre_orderTraversal(self, node, pre_list):
if node == None:
return None
pre_list.append(node.val)
self.pre_orderTraversal(node.left, pre_list)
self.pre_orderTraversal(node.right, pre_list)
def preorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
pre_list = []
self.pre_orderTraversal(root, pre_list)
return pre_list
145. 二叉树的后序遍历
题目链接: 145. 二叉树的后序遍历
仅需要调换输出值的位置,略过
94. 二叉树的中序遍历
题目链接: 二叉树的中序遍历
仅需要调换输出值的位置,略过
二叉树的迭代遍历
代码随想录
前序遍历(迭代法)
前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。
为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。
动画如下:
代码(自己做):
python
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def preorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
pre_list = []
stack = []
if root != None:
stack.append(root)
else:
return []
while len(stack):
# 先出栈再进栈
node = stack.pop()
pre_list.append(node.val)
if node.right != None:
stack.append(node.right)
if node.left != None:
stack.append(node.left)
return pre_list
中序遍历(迭代法)
前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码
而中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点
处理顺序和访问顺序是不一致
写代码之前一定要把逻辑想清楚:
处理流程自己表述:
当前指针指向根结点,如果根结点不空,那么当前指针走向其左孩子,一直循环到树的最左的孩子,此时当前指针为空,
那么出栈,当前指针回退并将值存入result列表同时当前指针指向其右孩子,
又回到开始,一直循环,直到栈空或者当前指针也为空
代码:
python
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def inorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
# 定义栈
stack = []
# 结果列表
result = []
#中序遍历 左中右
cur = root
while cur != None or len(stack):
if cur != None:
stack.append(cur)
cur = cur.left
else:
cur = stack.pop()
print(cur.val)
result.append(cur.val)
cur = cur.right
return result
后续遍历(迭代法)
左 -> 右 -> 中
先序遍历:中 -> 左 -> 右 —>>> 中 -> 右 -> 左—>>> result[::-1]反转 左 -> 右 -> 中
更改先序遍历左右结点入栈的顺序。先序遍历:先入右孩子再入左孩子;后续遍历:先入左孩子后入右孩子。
代码:
python
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def postorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
stack = []
result = []
if root != None:
stack.append(root)
else:
return []
while len(stack):
cur = stack[-1]
result.append(cur.val)
stack.pop()
if cur.left != None:
stack.append(cur.left)
if cur.right != None:
stack.append(cur.right)
result = result[::-1]
return result
二叉树的统一迭代法
代码随想录
那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。
中序遍历
python
前中后 代码 仅仅是入栈顺序不同
不理解这个代码,后序又时间再研究
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def inorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
stack = [] # 定义栈
result = [] # 定义结果列表
if root != None: stack.append(root) # 头结点如果不空,入栈
while len(stack): # 栈不空
cur = stack[-1] # 取栈顶元素
if cur != None: # 栈顶元素不是None
### 中序写法
stack.pop() # 先出栈
if(cur.right): stack.append(cur.right) # 因为是中序遍历,进栈顺序应为右中左
stack.append(cur)
stack.append(None) # 中节点访问过,但是还没有处理,加入空节点做为标记。
if(cur.left): stack.append(cur.left)
### 后序写法
'''
stack.pop() # 先出栈
stack.append(cur)
stack.append(None) # 中节点访问过,但是还没有处理,加入空节点做为标记。
if(cur.right): stack.append(cur.right)
if(cur.left): stack.append(cur.left)
'''
### 前序写法
'''
stack.pop() # 先出栈
if(cur.right): stack.append(cur.right)
if(cur.left): stack.append(cur.left)
stack.append(cur)
stack.append(None) # 中节点访问过,但是还没有处理,加入空节点做为标记。
'''
else: # 只有遇到空节点的时候,才将下一个节点放进结果集
stack.pop() # 将空节点弹出
cur = stack.pop() # 重新取出栈中元素并出栈
result.append(cur.val) # 加入到结果集
return result