树
树的定义
- 定义:树是n个节点的有限集。n=0时称为空树。在任何一棵非空树中:(1)有且仅有一个特定的节点称为根节点(root);(2)当n>1时,除根节点外的其他节点可分为m个互不相交的有限集T1,T2,…,Tm,其中每个集合本身又是一棵树,称为根(root)的子树(SubTree)。
- 特点:树是一种一对多的数据结构。
- 其他概念
结点拥有的子树数称为结点的度(Degree),树的度是树内结点的度的最大值。度为0的结点称为树的叶子结点(当树只有一个节点时,这个节点既是根节点也是叶子结点)。
结点的子树的根称为结点的孩子节点,该结点称为孩子节点的双亲结点,同一个双亲结点的孩子节点互称为兄弟结点。
结点的祖先是从根结点到该结点所经分支上的所有节点,反之,以某结点为根结点的子树中的所有节点都称为该结点的子孙。 - 结点分类
- 根节点:无双亲结点,当树不为空时唯一存在
- 中间节点:一个双亲结点,多个孩子结点
- 叶子节点:一个双亲结点,无孩子结点,度为0
- 高度、深度和层数
- 结点的高度:结点到叶子结点的最长路径,树的高度=根节点的高度(最底层叶子结点的高度为0)
- 结点的深度:根节点到这个结点所经历的边的个数(根节点的深度为0)
- 结点的层数:结点的深度+1(根节点为第一层)
树的抽象数据类型
ADT tree
Data
有一个根节点和若干子树构成,结点具有相同的数据结构和层次关系。
Operation
init_tree():初始化树
create_tree():构造树
is_empty(T):判断树T是否为空
depth(T):返回树的深度
root(T):返回树的根节点
get_value(T, node):返回树T结点node的值
set_value(T, node, value):将树T结点node的值设为value
parent(T, node):返回树T结点node的双亲结点
left_child(T, node):返回树T结点node的第一个孩子节点
right_sibling(T, node):返回树T结点node的第一个右兄弟结点
insert_child(T, node, i, c):将c插入到树T的node结点的第i棵子树
delete_child(T, node, i):删除树T结点node的第I棵子树
树的存储结构
- 双亲表示法:
- 用数组保存树的结点,对每一个节点,使用parent指针指向其双亲的位置(在数组中的下标),根结点的parent设为-1。
- 查找双亲复杂度为O(1),查找孩子复杂度为O(n)。
- 孩子表示法:
- 多重链表表示法:对每个结点设置多个指针域,每个指针指向一棵子树的根结点
- 方案一:指针域的个数等于树的度数
- 树的各个结点的度相差很小时比较有效,各节点的度相差很大时浪费空间。
- 方案二:指针域的个数等于结点的度,需要开辟一个位置存储指针域的个数
- 提高了空间利用率,但维护起来困难。
- 孩子表示法:使用顺序表存储每个节点,使用单链表存储每个节点的孩子节点
- 查找某个节点的兄弟:O(1)
- 查找某个节点的孩子:O(1)
- 查找某个节点的双亲:O(n)
- 双亲孩子表示法:在孩子表示法的基础上为每个节点增加一个parent指针
- 孩子兄弟表示法:
- 对任意一棵树中的任意一个结点,它的第一个孩子如果存在则唯一,它的右兄弟如果存在也唯一.使用firstchild和rightlib指针分别指向它们.
- 查找某个节点的右兄弟:O(1)
- 查找某个节点的左兄弟:O(n)
- 查找某个节点的孩子:O(1)
- 查找某个节点的双亲:O(n)
- 好处:将一颗复杂的树变为了一颗二叉树
二叉树
- 定义:每个结点最多有两个子结点
- 特点:
- 每个节点最多有2棵子树,每个节点的度最大为2。
- 每个结点的两个子树,称为左子树和右子树。
- 特殊二叉树
- 斜树:每一层只有一个结点,结点个数和树的深度相等,分为左斜树和右斜树
- 满二叉树:所有非叶子节点都存在左子树和右子树,所有叶子节点都位于最后一层
- 完全二叉树:若对于有n个节点的二叉树按层编号,其编号与同样深度的满二叉树的编号位置相同,则成为完全二叉树
- 扩充二叉树:对于一般的二叉树,需要知道其前(后)序遍历顺序和中序遍历顺序才能确定一棵二叉树。扩充二叉树是将所有节点的度都设置为2,对于度为1的结点添加一个空节点,对于度为0的叶子结点添加两个空节点。可以从扩充二叉树的前(后)序遍历顺序确定一棵二叉树。
- 平衡二叉树
- 二叉树的性质:
- 第
i
层最多有 2 i − 1 2^{i-1} 2i−1个结点 - 深度为
k
的二叉树最多有 2 k − 1 2^k-1 2k−1个节点,即满二叉树 - 设
n0,n1,n2
为二叉树T中度为0,1,2的结点个数,树T
结点总数为n
,则满足以下条件:n0+n1+n2=n
- 总结点数=度数之和+1,
n1+2*n2 = n-1
- 故
n0=n2+1
- 第
- 完全二叉树的性质:
- 具有
n
个结点的完全二叉树的深度为:floor(log2(n))+1
。 - 对于结点
i
,若i=1
,则为根节点无双亲;若i>1
,其双亲为结点floor(i/2)
。 - 对于结点
i
,若i>n/2
,则结点i
为叶子节点;若i<=(n-1)/2
,则结点i
为非叶子结点。 - 对于非叶子结点
i
,其左孩子结点为2i
,右孩子结点为2i+1
;若(n-1)/2<i<=n/2
,则结点i
只有左孩子2i
,无右孩子。 - 对于完全二叉树T来说,其第i层的结点范围为 [ 2 i − 1 , 2 i − 1 ] [2^{i-1}, 2^i-1] [2i−1,2i−1]。
- 二叉树的存储结构
- 基于数组的顺序存储法:适合于完全二叉树的存储,根节点存储在下标
i=1
的位置,左子节点存储在2i
的位置,右子结点存储在2i+1
的位置。 - 基于二叉链表的l链式存储法:适合非完全二叉树的存储,使用数据域
data
存储每个结点的数据,left
,right
作为指针域存储每个结点的左右孩子。
- 基于数组的顺序存储法:适合于完全二叉树的存储,根节点存储在下标
二叉树的遍历
二叉树的遍历是指从根节点开始,按照某种次序依次访问二叉树的所有结点,使每个结点被访问且仅被访问一次。二叉树的遍历分为深度优先遍历和广度优先遍历,其中深度优先遍历包括前中后序遍历,是一个递归的过程;广度优先遍历为层序遍历,采用队列实现。
- 前序遍历:根结点->左子树->右子树
- 中序遍历:左子树->根结点->右子树
- 后序遍历:左子树->右子树->根结点
- 层序遍历:从上到下,从左到右
from typing import List
from queue import Queue
class TreeNode:
"""二叉树结点"""
def __init__(self, value):
self.val = value
self.left = TreeNode(None)
self.right = TreeNode(None)
class Tree:
def __init__(self):
pass
def create_binary_tree_with_array(self, nodes: List[int]):
"""从数组构建二叉树"""
pass
def create_binary_tree_with_linked_list(self, nodes: List[int]):
"""从链表构建二叉树,链表节点顺序为二叉树前序遍历的结果"""
if not nodes:
return None
def preorder_traversal(self, root: TreeNode) -> List[int]:
"""递归实现前序遍历
打印遍历结果的话不需要帮助函数,返回遍历结果的话需要帮助函数
"""
res = []
def helper(root):
if not root:
return
res.append(root.val)
helper(root.left)
helper(root.right)
helper(root)
return res
def preorder_traversal_with_stack(self, root: TreeNode) -> List[int]:
"""非递归实现前序遍历
从根节点开始,弹出栈顶元素,并将其孩子节点入栈,先入右孩子,再入左孩子
"""
if root == None:
return []
stack = []
res = []
stack.append(root)
while stack:
root = stack.pop()
res.append(root.val)
if root.right:
stack.append(root.right)
if root.left:
stack.append(root.left)
return res
def inorder_traversal(self, root: TreeNode) -> List[int]:
"""递归实现中序遍历"""
res = []
def helper(root):
if not root:
return
helper(root.left)
res.append(root.val)
helper(root.right)
helper(root)
return res
def inorder_traversal_with_stack(self, root: TreeNode) -> List[int]:
"""非递归实现中序遍历
根节点入栈,左孩子节点入栈,当无左孩子时栈顶元素出栈,当有右孩子时右孩子入栈,无右孩子时
"""
stack = []
res = []
while root or stack:
# 根节点及其左子节点进栈
while root:
stack.append(root)
root = root.left
# 左子节点及根节点出栈
root = stack.pop()
res.append(root.val)
# 遍历当前根结点的右子树
root = root.right
return res
def postorder_traversal(self, root: TreeNode) -> List[int]:
"""递归实现后序遍历"""
res = []
def helper(root):
if not root:
return
helper(root.left)
helper(root.right)
res.append(root.val)
helper(root)
return res
def postorder_traversal_with_stack(self, root: TreeNode) -> List[int]:
"""非递归实现后序遍历
类似前序遍历(先入左孩子,再入右孩子),遍历结果逆序
"""
if root == None:
return []
stack = []
res = []
stack.append(root)
while stack:
root = stack.pop()
res.append(root.val)
if root.right:
stack.append(root.right)
if root.left:
stack.append(root.left)
return res
def level_order_with_queue(self, root: TreeNode) -> List[List[int]]:
"""层序遍历"""
queue = Queue() # 保存要遍历的结点
res = []
queue.put(root)
while queue:
node = queue.get()
res.append(node.val)
if node.left:
queue.put(node.left)
if node.left:
queue.put(node.right)
return res
二叉树经典题目
- 前中后序遍历(递归实现和基于栈的非递归实现),Leetcode 94,144,145
- 使用队列实现二叉树的层序遍历和锯齿形层序遍历,leetcode 102,103
- 翻转二叉树(递归实现和基于队列的非递归实现),leetcode 226
- 中序遍历和后(前)序遍历树构造二叉树(递归实现),leetcode 105,106