二叉树理论基础
二叉树的种类
满二叉树 (Full binary tree)
- A Binary Tree is full if every node has 0 or 2 children. Following are examples of a full binary tree.
18
/ \
15 20
/ \
40 50
/ \
30 50
完全二叉树 (Complete binary tree)
- 在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
- A complete binary tree of height h is a proper binary tree up to height h-1, and in the last level element are stored in left to right order.
18
/ \
15 30
/ \ / \
40 50 100 40
/ \ /
8 7 9
Perfect binary tree
- A Binary tree is Perfect Binary Tree in which all internal nodes have two children and all leaves are at same level. (full and complete)
18
/ \
15 30
/ \ / \
40 50 100 40
二叉搜索树 (Binary search tree)
前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树。(左小右大)
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
平衡二叉搜索树 (AVL tree)
- 它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
10
/ \
6 16
/ \
3 9
二叉树的存储方式
-
二叉树可以链式存储,也可以顺序存储。
- 那么链式存储方式就用指针, 顺序存储的方式就是用数组。
- 顾名思义就是顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在散落在各个地址的节点串联一起。
-
用数组来存储二叉树如何遍历的呢?
- 如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
-
但是用链式表示的二叉树,更有利于我们理解,所以一般我们都是用链式存储二叉树。
二叉树的遍历方式
深度优先遍历 (DFS)
-
前序遍历(递归法,迭代法)
- Root-left-right
-
中序遍历(递归法,迭代法)
- Left-root-right
-
后序遍历(递归法,迭代法)
- Left-right-root
5
/ \
4 6
/ \ / \
1 2 7 8
pre-order: 5-4-1-2-6-7-8
in-order: 1-4-2-5-7-6-8
post-order: 1-2-4-7-8-6-5
广度优先遍历 (BFS)
- 层次遍历(迭代法)
- 广度优先遍历的实现一般使用队列来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。
二叉树的定义
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
二叉树的递归遍历
-
Time complexity: O(N)
-
Space complexity: average case is O(logN), worst case is O(N)
-
这里帮助大家确定下来递归算法的三个要素。每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!
- 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
- 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
- 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
144. 二叉树的前序遍历
# 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]:
res = []
def recursion_helper(root, res):
if root == None:
return
res.append(root.val)
recursion_helper(root.left, res)
recursion_helper(root.right, res)
recursion_helper(root, res)
return res
145. 二叉树的后序遍历
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
def recursion_helper(root, res):
if root == None:
return
recursion_helper(root.left, res)
recursion_helper(root.right, res)
res.append(root.val)
recursion_helper(root, res)
return res
94. 二叉树的中序遍历
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
def recursion_helper(root, res):
if root == None:
return
recursion_helper(root.left, res)
res.append(root.val)
recursion_helper(root.right, res)
recursion_helper(root, res)
return res
二叉树的迭代遍历
144. 二叉树的前序遍历
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
res = []
stack = [root]
# root - left - right
while stack:
cur = stack.pop()
res.append(cur.val)
if cur.right:
stack.append(cur.right)
if cur.left:
stack.append(cur.left)
return res
145. 二叉树的后序遍历
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
res = []
stack = [root]
# left - right - root
while stack:
cur = stack.pop()
res.append(cur.val)
if cur.left:
stack.append(cur.left)
if cur.right:
stack.append(cur.right)
return res[::-1]
94. 二叉树的中序遍历
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
stack = []
cur = root
# left - root - right
while cur or stack:
# left
while cur:
stack.append(cur)
cur = cur.left
# root
cur = stack.pop()
res.append(cur.val)
# right
cur = cur.right
return res
二叉树的统一迭代法
- 使用栈的话,无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。
- 那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。
- 如何标记呢,就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法。
144. 二叉树的前序遍历
- 倒着顺序入栈,弹出时的顺序就是对的了
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
res = []
stack = [root]
# root - left - right
while stack:
cur = stack.pop()
if cur != None:
# right
if cur.right:
stack.append(cur.right)
# left
if cur.left:
stack.append(cur.left)
# root
stack.append(cur)
stack.append(None)
else:
cur = stack.pop()
res.append(cur.val)
return res
145. 二叉树的后序遍历
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
res = []
stack = [root]
# left - right - root
while stack:
cur = stack.pop()
if cur != None:
# root
stack.append(cur)
stack.append(None)
# right
if cur.right:
stack.append(cur.right)
# left
if cur.left:
stack.append(cur.left)
else:
cur = stack.pop()
res.append(cur.val)
return res
94. 二叉树的中序遍历
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
res = []
stack = [root]
# left - root - right
while stack:
cur = stack.pop()
if cur != None:
# right
if cur.right:
stack.append(cur.right)
# root
stack.append(cur)
stack.append(None)
# left
if cur.left:
stack.append(cur.left)
else:
cur = stack.pop()
res.append(cur.val)
return res