二叉树基础
-
种类
-
满二叉树
如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。有2^k -1个节点。 -
完全二叉树
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。
【图源:代码随想录】 -
二叉搜索树
前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树。- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
-
平衡二叉搜索树
又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn,注意我这里没有说unordered_map、unordered_set,unordered_map、unordered_set底层实现是哈希表。
-
-
二叉树的存储方式
- 链式储存:指针
通过指针把分布在各个地址的节点串联一起 - 顺序储存:数组
元素在内存是连续分布的。如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
- 链式储存:指针
-
二叉树的遍历方式
- 深度优先:先往深走,遇到叶子结点再往回走
- 前序遍历 (preorder traversal):递归,迭代
中左右:访问根结点的操作发生在遍历其左右子树之前 - 中序遍历 (inorder traversal):递归,迭代
左中右:访问根结点的操作发生在遍历其左右子树之中(间) - 后序遍历 (postorder traversal):递归,迭代
左右中:访问根结点的操作发生在遍历其左右子树之后 - 深度优先一般使用递归来遍历,栈就是一种递归的实现结构,可以借助栈使用递归来深度遍历
- 前序遍历 (preorder traversal):递归,迭代
- 广度优先:一层一层遍历
- 层次遍历:迭代
- 广度优先一般使用队列,先进先出的结构可以一层一层来遍历。
- 深度优先:先往深走,遇到叶子结点再往回走
-
链式储存节点定义
class TreeNode:
def __init__(self, val, left = None, right = None):
self.val = val
self.left = left
self.right = right
【除了递归,我们还可以用迭代法。递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。所以用栈也可以是实现二叉树的前后中序遍历了。】
144 二叉树的前序遍历
递归法
class Solution(object):
def preorderTraversal(self, root):
if not root:
return []
root_val = [root.val]
left = self.preorderTraversal(root.left)
right = self.preorderTraversal(root.right)
return root_val + left + right
迭代法:
- 前序是中左右,现将根节点(中)放入,接下来以右左的顺序放入栈,出来为中左右
- 因为要访问的元素和要处理的元素顺序是一致的,都是中间节点,代码相对简洁
class Solution(object):
def preorderTraversal(self, root):
if not root:
return []
stack = [root]
res = []
while stack:
node = stack.pop()
res.append(node.val)
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
return res
94 二叉树的中序遍历
class Solution(object):
def inorderTraversal(self, root):
if not root:
return []
root_val = [root.val]
left = self.inorderTraversal(root.left)
right = self.inorderTraversal(root.right)
return left + root_val + right
迭代法:
- 中序是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。
- 那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
class Solution(object):
def inorderTraversal(self, root):
if not root:
return []
stack = []
res = []
cur = root
while cur or stack:
if cur:
# 遍历,一直向左,到达左面的最底部
stack.append(cur)
cur = cur.left
else:
# 处理
# 这里是到达树或子树最左边的底部后,cur == None
# 这个时候要往最近的(上一级的)root和它的右边找了
cur = stack.pop() # 指向上一级root (中)
res.append(cur.val)
cur = cur.right # (右)
return res
145 二叉树的后序遍历
class Solution(object):
def postorderTraversal(self, root):
if not root:
return []
root_val = [root.val]
left = self.postorderTraversal(root.left)
right = self.postorderTraversal(root.right)
return left + right + root_val
迭代法:
- 后序遍历是左右中。我们可以调整前序遍历的逻辑,前序是中左右,调整左右顺序就是中右左,将最后的结果反转,即可得到左中右。
class Solution(object):
def postorderTraversal(self, root):
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]