二叉树基础
本部分的图示来源:代码随想录
分类
满二叉树
如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
完全二叉树
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。
之前我们刚刚讲过优先级队列其实是一个堆,堆就是一棵完全二叉树,同时保证父子节点的顺序关系。
二叉搜索树
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
平衡二叉搜索树
它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
C++底层
1、C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn。
2、unordered_map、unordered_set,unordered_map、unordered_set底层实现是哈希表。
存储方式
用链式表示的二叉树,更有利于我们理解,所以一般我们都是用链式存储二叉树。
链式储存
类似于链表,直接使用指针遍历。
连续储存
父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
遍历方式
二叉树主要有两种遍历方式:
- 深度优先遍历:先往深走,遇到叶子节点再往回走。
- 广度优先遍历:一层一层的去遍历。
深度优先
- 前序遍历(递归法,迭代法)
- 中序遍历(递归法,迭代法)
- 后序遍历(递归法,迭代法)
这里前中后,其实指的就是中间节点的遍历顺序
- 前序遍历:中左右
- 中序遍历:左中右
- 后序遍历:左右中
广度优先
- 层次遍历(迭代法)
二叉树的代码定义
注意面试需要会手写代码的节点定义!!!!
class TreeNode:
def __init__(self, val, left = None, right = None):
self.val = val
# 相比链表,多出了一个指针指向另一个子节点
self.left = left
self.right = right
Leetcode二叉树题目索引
递归遍历(最基础)
写递归函数的方法
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
144. 二叉树的前序遍历 - 力扣(LeetCode)
# 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]
"""
# 终止条件
if not root:
# if root is None:
return []
# 单层处理
left = self.preorderTraversal(root.left) # 左节点一直找到最左边的节点
right = self.preorderTraversal(root.right) # 右节点一直找到最右边的节点
# 返回值: "中-->左-->右"(前序中间节点在前)
return [root.val] + left + right
94. 二叉树的中序遍历 - 力扣(LeetCode)
# 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]
"""
# 终止条件
if not root:
return []
# 单层处理
left = self.inorderTraversal(root.left)
right = self.inorderTraversal(root.right)
# 返回值:左-->中-->右(中序遍历根节点在中间)
return left + [root.val] + right
145. 二叉树的后序遍历 - 力扣(LeetCode)
# 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]
"""
# 终止条件:返回的节点是空的
if root is None:
return []
# 单层处理
left = self.postorderTraversal(root.left)
right = self.postorderTraversal(root.right)
# 返回值:左-->右-->中(后序遍历根节点在中间)
return left + right + [root.val]
迭代遍历
思路:模拟一个递归的过程,也就是在本次弹出上一次的栈内元素。
144. 二叉树的前序遍历 - 力扣(LeetCode)
处理完根节点后,先入栈右子节点,再入栈左子节点,因为返回的时候是先左节点,后右节点。
# 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]
"""
# 迭代法
if not root:
return []
stack = [root] # 前序遍历先把root放到res中
res = []
# 迭代栈内元素
while stack:
node = stack.pop()
# mid-->res
res.append(node.val)
# right-->stack
if node.right:
stack.append(node.right)
# left-->stack
if node.left:
stack.append(node.left)
return res
由于前序遍历是按照层来访问,处理的节点也是先root后左右,所以代码实现的顺序是一致的。
145. 二叉树的后序遍历 - 力扣(LeetCode)
后序遍历实际上是把前序的res倒叙输出即可。
# 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]
"""
# 迭代法
# 前序代码
if not root:
return []
stack = [root]
res = []
while stack:
node = stack.pop()
res.append(node.val)
if node.left: # 先入栈左节点
stack.append(node.left)
if node.right: # 后入栈右节点
stack.append(node.right)
return res[::-1] # 倒叙输出
94. 二叉树的中序遍历 - 力扣(LeetCode)
但是中序遍历需要先找到最左端的点,然后才是处理root,这就导致需要先按照指针来遍历到最左端的节点然后才能开始处理。中序遍历不像是递归法一样只需要改变返回值的顺序即可。
# 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]
"""
# 迭代法
if not root:
return []
stack = [] # 中序不需要先储存root节点,初始化为空
res = []
cur = root # 定义一个指针来找最左端
# 迭代遍历
while cur or stack:
# 如果当前节点非空,遍历到他的左节点
if cur:
stack.append(cur) # 同时遍历到的全部左节点入栈
cur = cur.left
# 如果遍历到最底层
else:
cur = stack.pop() # 此时stck储存的是最底层的root节点
res.append(cur.val)
cur = cur.right
return res
统一迭代(学一下思想)
把迭代法写成统一的格式需要面临的问题:使用栈的话,无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。
方法:那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。也就是放入root节点之后需要加上一个空指针。也叫做标记法。
我们将访问的节点直接加入到栈中,但如果是处理的节点则后面放入一个空节点, 这样只有空节点弹出的时候,才将下一个节点放进结果集。
这里给出中序遍历的实现:
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
result = []
st = []
if root:
st.append(root)
while st:
node = st.pop()
if node != None:
if node.right: #添加右节点(空节点不入栈)
st.append(node.right)
# 只需要改变这两行代码即可
st.append(node) #添加中节点
st.append(None) #中节点访问过,但是还没有处理,加入空节点做为标记。
# 只需要改变这两行代码即可
if node.left: #添加左节点(空节点不入栈)
st.append(node.left)
else: #只有遇到空节点的时候,才将下一个节点放进结果集
node = st.pop() #重新取出栈中元素
result.append(node.val) #加入到结果集
return result
第14天完结🎉