1.基本概念
1.1 树的定义
树 :是一种非线性结构,是具有树形结构的数据集合;它是由 n 个(n>=0)个节点组成的一个具有层次结构的集合;它具有以下特点:
- 根节点没有父节点
- 除根节点以外的节点有且只有一个父节点
- 每个节点可以有0个或多个子节点,没有子节点的为叶子节点
- 除根节点以外,每个子节点可以分为多个不相交的子节点

1.2 树的术语

2.书的种类与存储
2.1 树的种类
- 无序树 :树中任意节点的子节点之间没有顺序关系,这种树称为无序树
- 有序树 :树中任意节点的子节点之间有顺序关系,这种树称为有序树
有序树又分为:
- 二叉树:每个节点最多含有两个子树,并且二叉树的子树有左右之分(次序不能颠倒)
- 哈夫曼树:带权路径最短的二叉树称为哈夫曼树或最优二叉树
- B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个的子树
2.2 二叉树(重点)
2.2.1 二叉树的概念
二叉树 :每个节点最多含有两个子树,并且二叉树的**子树有左右之分(**次序不能颠倒)
几种特殊的二叉树:
满二叉树 :一棵高度为 h,且有 个结点的二叉树称为满二叉树;即二叉树中的每层都含有最多的结点;满二叉树的叶结点都集中在二叉树的最下一层,并且除叶结点之外的每个结点度数均为 2
可以对满二叉树按层序编号:约定编号从根结点(根结点编号为 1)起,自上而下,自左向右;这样,每个结点对应一个编号,对于编号为 i 的结点,若有双亲,则其双亲为,若有左孩子,则左孩子为 2i;若有右孩子,则右孩子为 2i+1
完全二叉树:高度为h、有n个结点的二叉树,当且仅当其每个结点都与高度为h的满二叉树中编号为1~n 的结点一一对应时,称为完全二叉树,其特点如下:
- 若
,则结点i为分支结点,否则为叶结点
- 叶结点只可能在层次最大的两层上出现;对于最大层次中的叶结点,都依次排列在该层最左边的位置上
- 若有度为 1 的结点,则最多只可能有一个,且该结点只有左孩子而无右孩子
- 按层序编号后,一旦出现某结点(编号为i)为叶结点或只有左孩子,则编号大于 i 的结点均为叶结点
- 若n为奇数,则每个分支结点都有左孩子和右孩子;若n为偶数,则编号最大的分支结点(编号为
只有左孩子,没有右孩子,其余分支结点左、右孩子都有
二叉排序树:
二叉排序树要么是一个空树,要么是一个具有以下性质的树:
- 若左子树非空,则左子树上所有节点的值均小于根节点的值
- 若右子树非空,则右子树上所有节点的值均大于根节点的值
- 左,右子树也分别是一颗二叉排序树
二叉排序树目的:提高查找,插入和删除关键字的速度,二叉排序树又叫二叉查找树
平衡二叉树:树中任意一个结点的左子树和右子树的高度之差的绝对值不超过 1
注意:平衡二叉树是二叉排序树
为什么需要平衡二叉树:防止在插入数据时树的高度增长过快,降低二叉排序树的性能
正则二叉树:树中每个分支结点都有 2 个孩子,即树中只有度为 0 或 2 的结点。

2.2.1 二叉树的性质


2.2.2 二叉树的存储
2.2.2.1 顺序存储
顺序存储:将二叉树存储在固定的数组中,虽然在遍历速度上有一定优势,但因所占空间较大,是非主流二叉树存储方式

2.2.2.2 链式存储
链式存储 :由于对子节点的个数无法掌握,常见树的存储表示都转换成了二叉树进行处理,子节点个数最多为2

链式存储和顺序存储的优缺点:
链式存储:容易找到子节点
顺序存储:直接存储,没有其他冗余信息,节省空间
2.2.3 二叉树的遍历
2.2.3.1 广度优先遍历
广度优先遍历 = 层序遍历 ,一层一层、从上到下,同层从左往右访问结点,借助队列实现
核心思想:
- 先把根结点入队
- 循环:队不为空
- 队头结点出队,访问该结点
- 如果它有左孩子,左孩子入队
- 如果它有右孩子,右孩子入队
代码实现:
#1.定义二叉树的节点类
class Node(object):
def __init__(self, item):
self.left = None
self.item = item
self.right = None
def __str__(self):
return self.item
#2.定义二叉树类
class BinaryTree(object):
def __init__(self, root: Node=None):
self.root = root
def add(self, item):
"""向二叉树中添加节点,有序添加"""
node = Node(item)
if self.root is None:
self.root = node
else:
cur = self.root
while True:
if item < cur.item: # 如果小于当前节点,则往左子树走
if cur.left is None: # 如果当前节点的左子树为空
cur.left = node
return
else:
cur = cur.left
else: # 如果大于等于当前节点,则往右子树走
if cur.right is None:
cur.right = node
return
else:
cur = cur.right
def breadth_travel(self):
"""广度遍历二叉树"""
if self.root is None:
return
queue = [self.root]
while queue:
cur = queue.pop(0)
print(cur.item)
if cur.left is not None:
queue.append(cur.left)
if cur.right is not None:
queue.append(cur.right)
def test1_breadth_travel():
node1 = Node(1)
tree = BinaryTree(node1)
tree.add(2)
tree.add(4)
tree.add(-1)
tree.breadth_travel()
#3.测试
if __name__ == '__main__':
test1_breadth_travel()
注意:抽取方法的快捷键为:Ctrl + Alt + m
2.2.3.2 深度优先遍历
深度优先就是一路向下挖到叶子结点,再回溯,分为 3 种:
- 先序遍历:根 → 左 → 右
- 中序遍历:左 → 根 → 右
- 后序遍历:左 → 右 → 根
代码实现如下:
import random
#1.定义二叉树的节点类
class Node(object):
def __init__(self, item):
self.left = None
self.item = item
self.right = None
def __str__(self):
return self.item
#2.定义二叉树类
class BinaryTree(object):
def __init__(self, root: Node=None):
self.root = root
def add(self, item):
"""向二叉树中添加节点,有序添加"""
node = Node(item)
if self.root is None:
self.root = node
else:
cur = self.root
while True:
if item < cur.item: # 如果小于当前节点,则往左子树走
if cur.left is None: # 如果当前节点的左子树为空
cur.left = node
return
else:
cur = cur.left
else: # 如果大于等于当前节点,则往右子树走
if cur.right is None:
cur.right = node
return
else:
cur = cur.right
def breadth_travel(self):
"""广度遍历二叉树"""
if self.root is None:
return
queue = [self.root] #根节点入队
while queue: #循环退出条件:根节点为空
cur = queue.pop(0) #根节点先出队
print(cur.item, end=" ")
if cur.left is not None: #左子树不为空
queue.append(cur.left)
if cur.right is not None: #右子树不为空
queue.append(cur.right)
# 先序遍历:根 左 右
def preorder(self, root):
if root is None:
return
print(root.item, end=" ") # 访问根
self.preorder(root.left) # 递归左子树
self.preorder(root.right) # 递归右子树
# 中序遍历:左 根 右
def inorder(self, root):
if root is None:
return
self.inorder(root.left) # 递归左子树
print(root.item, end=" ") # 访问根
self.inorder(root.right) # 递归右子树
# 后序遍历:左 右 根
def postorder(self, root):
if root is None:
return
self.postorder(root.left) # 递归左子树
self.postorder(root.right) # 递归右子树
print(root.item, end=" ") # 访问根
def tree_generator():
node1 = Node(random.randint(1, 100))
tree = BinaryTree(node1)
for i in range(5):
tree.add(random.randint(1, 100))
return tree
def test1_breadth_travel():
tree = tree_generator()
tree.breadth_travel()
def test2_preorder():
tree = tree_generator()
tree.preorder(tree.root)
def test3_inorder():
tree = tree_generator()
tree.inorder(tree.root)
def test4_postorder():
tree = tree_generator()
tree.postorder(tree.root)
#3.测试
if __name__ == '__main__':
test1_breadth_travel()
print("\n" + "-" * 30 )
test2_preorder()
print("\n" + "-" * 30 )
test3_inorder()
print("\n" + "-" * 30 )
test4_postorder()