一、二叉树理论基础
二叉树有哪些类型?
二叉树主要分为:
1、满二叉树:只有度为0和2的结点,且度为0的结点在同一层上(深度为k,节点个数为2^k-1)
2、完全二叉树:除了底层节点,每层节点都达到了最大值(保证父子节点的顺序关系)
两者都是无值的,谈不上是不是有序树,就是个形
· 二叉搜索树(/二叉排序树):有值的,有序树
① 左子树不为空,则左子树的值均小于根节点
② 右子树不为空,则右子树的值均大于根节点
③ 子树也是二叉搜索树
· 平衡二叉搜索树(/AVL树):有值的,有序树
① 左右子树高度差值绝对值<1
② 子树也是平衡二叉树
③ 可以是空树
怎么存储二叉树?
存储方式分为:
1、链式存储:即指针 (一般选这个)
2、顺序存储:即数组
(数组的父节点是i,左孩子就是2i+1,右孩子就是2i+2)
如何遍历二叉树?
二叉树遍历方式主要分为
1、深度优先遍历
- 前序遍历(递归法、迭代法) 中左右
- 中序遍历(递归法、迭代法) 左中右
- 后序遍历(递归法、迭代法) 左右中
2、广度优先遍历
- 层序遍历(迭代法)
迭代的实现使用栈,栈的结构先进后出符合迭代思想
广度遍历的实现使用队列,队列先进先出满足层级遍历思想
迭代和递归区别:
递归是重复调用函数自身
迭代是循环内保存的变量作为参数再次进入循环
如何定义一个二叉树?
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
二、二叉树的递归遍历和栗子
用栈表示递归,进行深度优先遍历
写递归前,先确定递归三要素:
1、我这个递归函数什么时候结束
2、我这个递归函数的流程是什么
3、我这个递归函数要传入什么参数,有返回的结果吗
例如前序遍历:
1、当遍历结束为空时,这个函数就结束了if (root == None): return
2、前序遍历思想中左右
result.append(root.val) traversal(root.left) traversal(root.right)
3、递归传入的参数就是某个节点,因为是做遍历所以没有返回值def traversal(root)
栗子有leetcode 145,完整前序遍历如下:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
result = []
def traversal(root):
if root == None:
return
result.append(root.val)
traversal(root.left)
traversal(root.right)
traversal(root)
return result
中序遍历的栗子leetcode 94
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
result = []
def traversal(root):
if root == None:
return
traversal(root.left)
result.append(root.val)
traversal(root.right)
traversal(root)
return result
后序遍历的栗子 leetcode 145
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
result = []
def travelsal(root):
if root == None:
return
travelsal(root.left)
travelsal(root.right)
result.append(root.val)
travelsal(root)
return result
三、二叉树的迭代遍历和栗子
二叉树深度优先遍历的非递归法,就是迭代法(说白了就是一套访问节点再处理节点的过程)
依旧用前序遍历举例:先让root入栈。root出栈,再先让右节点入栈,再让左节点入栈,这样栈出时就是中左右,此为一个循环。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if root == None:
return []
result = []
stack = [root]
while stack:
node = stack.pop()
result.append(node.val)
#一定要注意先判空,不能直接append
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
return result
前序相对好理解,因为访问的顺序是和处理节点(放入result)的顺序是一致的,(先访问中,先处理中)
后序遍历与前序一致(同先访问中,先处理中,无非是再倒序输出),除了迭代是先入左节点,再入右节点,最后是倒序输出,代码如下:
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if root == None:
return []
result = []
stack = [root]
while stack:
node = stack.pop()
result.append(node.val)
if node.left:
stack.append(node.left)
if node.right:
stack.append(node.right)
return result[::-1]
但是中序有点不同,因为访问的顺序和处理的顺序不一样。此时采取的方式是用指针访问节点,用栈处理节点。so,怎么做呢?
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if root == None:
return []
result = [] #放结果的地方
#不能把root先放入栈,否则会先做处理
stack = [] # 一个处理节点的栈,存储访问过的节点
cur = root # 一个遍历用的指针
while cur or stack:
#迭代访问最左边的节点
if cur:
stack.append(cur)
cur = cur.left
#处理访问过的节点的右节点
else:
cur = stack.pop()
result.append(cur.val)
cur = cur.right
return result
附赠:统一迭代写法
规则:访问节点和处理节点均入栈,处理的节点放入栈之后,紧接着放入一个空指针作为标记
比如[1,null,2,3],入栈顺序为[1,2 1 null ]
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if root == None:
return []
result = [] #放结果的地方
stack = [root]
while stack:
node = stack.pop()
if node:
if node.right:
stack.append(node.right)
stack.append(node)
stack.append(None)
if node.left:
stack.append(node.left)
else:
node = stack.pop()
result.append(node.val)
return result