文章目录
- 树的概念
- 树的表示
- 二叉树
- 二叉树存储
- 顺序存储
- 链式存储
- 二叉树的遍历
- 中序遍历
- 前序遍历
- 后序遍历
- 练习
- 二叉树的恢复
- 构建链式二叉树
- 二叉排序树
- 平衡树
树的概念
- 一个前驱节点,多个后继节点的数据结构;
- 树是n个节点的有限集合(n>=0),n为0的树为空树;
- 非空树中,只有一个根节点,其余节点又可分为多个不相交的集合,形成子树;
- 每个节点的分支数,称为该节点的度;所有节点的度的最大值,为该树的度;
- 同一层为兄弟节点,上层节点为祖先节点或者根节点,下层节点为子孙节点;
- 从根节点开始,树的层数为树的深度;
树的表示
- 双亲表示
从根开始,用编号表示每个节点,节点对象存储数据和父节点 - 孩子表示
- 增加度(节点度)的孩子表示法
- 数组+单链表
数组存储每个节点,每个节点在指向包含所有子节点的单链表 - 孩子兄弟表示
二叉树
- 二叉树是树的一种特殊形式;使用比较多
- 树的度最多为2,即最多有两个分支;所有的树都可以转为唯一的二叉树
- 二叉树是有序树,所有节点都要区分是左子树、还是右子树;即使一个节点也要区分,而树在一个节点时 就不用区分;
- 5种基本形态:空二叉树、只有一个根的二叉树、根+左子树、根+右子树、根+左右子树;
- 性质
- 非空二叉树 叶子节点数 = 度为2的节点数
- 非空二叉树,第i层最多有个节点
- 在一个深度为k的二叉树中,最多有个节点(等比数列)
- n个节点的完全二叉树中,最大深度为
- n个节点的完全二叉树中,从上到下、从左到右开始编号(i=1开始),i=1表示根节点,i>1时,i//2 表示其双亲节点;若2i<=n 则左孩子为2i,否则i为叶子节点(无左孩子); 若2i+1 <=n 则 i 的右孩子为2i+1 ,否则无右孩子;
- 满二叉树
- 每个非叶子节点都有两个分支;
- 所有叶子节点都在同一层;
- 即深度为k的二叉树,有个节点的二叉树
- 完全二叉树
- 满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树;
- 完全二叉树从上到下,从左到右依次对节点编号,与满二叉树编号对应;
- 除最后一层外,以上所有层都是满的,最后一层叶子节点要么满、要么集中在最左边;所有叶子节点在最后一层或者倒数第二层。
- 完全二叉树可以使用数组存储,根节点索引为,左子节点为,右子节点为
- 最后一个非叶子的节点为,n为数组长度;
- 二叉树表示
二叉树存储
顺序存储
顺序存储只适合完全二叉树,如堆
,它是一个存在数组中的完全二叉树。
链式存储
二叉树中,一个节点存储数据、左孩子指针、右孩子指针
二叉树的遍历
中序遍历
- 遍历左子树,根节点,遍历右子树;
- 左子树中再递归地解决左子节点、左子树的根节点、右子节点;
递归实现
class TreeNode(object):
def __init__(self, data, left=None, right=None):
self.data = data
self.left = left
self.right = right
# 左子树
g = TreeNode("g")
h = TreeNode("h")
d = TreeNode('d', left=g, right=h)
b = TreeNode("b", left=d)
# 右子树
i = TreeNode("i")
e = TreeNode('e', right=i)
f = TreeNode("f")
c = TreeNode("c", left=e, right=f)
# 根节点
a = TreeNode("a", left=b, right=c)
# b
# d c
# g h e f
# i
# 中序遍历
# 左子树、root、右子树
def visit_tree(root):
if root is None: # java中为null
return
# 访问左子树
visit_tree(root.left)
# 访问根节点的值
print(root.data)
# 访问右子树
visit_tree(root.right)
if __name__ == '__main__':
visit_tree(a)
循环迭代
借助栈,每个root根节点压栈;处理时弹栈;
if __name__ == '__main__':
#
stack = []
root = a
while root or stack:
while root:
# 根节点入栈
stack.append(root)
root = root.left
root = stack.pop()
print(root.data)
root = root.right
中序遍历:
g,d,h,b,a,e,i,c,f
java循环
前序遍历
- 先访问根节点,再先序遍历左子树、最后先序遍历右子树;
- a,b,d,g,h,c,e,i,f
递归实现
# 先序遍历
def first_travel(root):
if root is None:
return
print(root.data)
first_travel(root.left)
first_travel(root.right)
循环迭代
def stack_first_travel(root):
if root is None:
return
stack = []
# 先序处理
while root or stack:
while root:
# 先序遍历根
print(root.data, end=",")
# 右子树 入栈
if root.right:
stack.append(root.right)
# 先序遍历左子树
root = root.left
# 右子树出栈
if stack:
root = stack.pop()
后序遍历
- 左子树、右子树、根节点
递归实现
def last_travel(root):
if root is None:
return
last_travel(root.left)
last_travel(root.right)
print(root.data)
练习
分别输出一下二叉树的先序遍历、中序遍历、后序遍历。
二叉树的恢复
- 任意一棵二叉树的先序遍历、中序遍历都是唯一的;
- 已知节点的先序序列和中序序列,则可以唯一确定这棵二叉树;
- 由先序序列,逐一获取每个根节点或子树的根节点;
- 在中序序列中,用每个根节点,划分左右子树;
- 再取一个根节点,递归地划分左右子树。
例如:
一棵二叉树的先序序列 abcdefghi; 中序序列bcaedghfi;恢复该二叉树。
先、中可以恢复;
后、中可以恢复;
编码实现恢复;
- 先序+中序 唯一恢复二叉树
# 递归恢复二叉树
def restore_btree(mid_seq): # 中序遍历序列或者子集
global first_seq # 先序遍历全局
if not mid_seq: # 中序遍历的分治部分
return None
elif len(mid_seq) == 1:
return TreeNode(mid_seq[0])
while first_seq:
first_data = first_seq.pop(0)
if first_data in mid_seq:
root = TreeNode(first_data)
idx = mid_seq.index(first_data)
mid_left = mid_seq[0 : idx]
mid_right = mid_seq[idx + 1 : ]
root.left = restore_btree(mid_left) # 根据中序左子集创建 左子树
root.right = restore_btree(mid_right) # 根据中序右子集创建 右子树
return root
if __name__ == '__main__':
first_seq = list("abcdefghi")
mid_seq = list("bcaedghfi")
root = restore_btree(mid_seq)
first_travel(root)
- 先序遍历统计二叉树叶子节点数;
def count_leaf(root):
global num
if root is None:
return num
elif root.left is None and root.right is None:
num += 1
return num
if root.left:
count_leaf(root.left)
if root.right:
count_leaf(root.right)
return num
num = 0
count_leaf(root)
print("total leafs:", num)
构建链式二叉树
根据列表数据,构建链式二叉树;
[“a”, “b”, “d”, None, “f”, None, None, None, “c”, “e”, None, None, None]
class Node:
def __init__(self, data, left=None, right=None):
self.data = data
self.left = left
self.right = right
def create_bt(): # 创建二叉树
global alist
# 先序 递归 创建二叉树
if alist:
data = alist.pop(0)
if data is None:
return root # 空树
else:
return None
# 先创建根节点
root = Node(data)
# 创建左子树
root.left = create_bt()
# 最后创建右子树
root.right = create_bt()
return root
# 先序遍历
def first_travel(root):
if root is None:
return
print(root.data)
first_travel(root.left)
first_travel(root.right)
if __name__ == '__main__':
alist = ["a", "b", "d", None, "f", None, None, None, "c", "e", None, None, None]
root = create_bt()
first_travel(root)
创建的二叉树如图:
先序遍历:a b d f c e
中序遍历:d,f,b,a,e,c
后序遍历:f,d,b,e,c,a
二叉排序树
Binary Search Tree,也叫二叉搜索树;没有重复值;
- 左子节点的值 小于根节点,根节点值小于右节点;
- 左子树中同样满足;
- 右子树中同样满足;
- 二叉搜索树 查找的时间复杂度为
- 假设树为完全二叉树,且查找次才找到目标,则相当于查的树深度为,则树节点数n满足且
- 通过取以2为底的对数得到
- 所以时间复杂度;也可以每次除2的方式理解,,即到达叶子节点;
- 当二叉搜索树为倾斜树时,最坏时间复杂度为
- 所以二叉搜索树的性能与树高有关;
# 定义二叉排序树的节点
class TreeNode:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
# 构建二叉排序树
def insert_node(root, val):
if root is None:
return TreeNode(val)
if val > root.val:
root.right = insert_node(root.right, val)
else:
root.left = insert_node(root.left, val)
return root
# 中序遍历二叉排序树(升序)
def in_order_traversal(root):
if root is None:
return []
res = []
res.extend(in_order_traversal(root.left))
res.append(root.val)
res.extend(in_order_traversal(root.right))
return res
# 示例:构建一个二叉排序树
root = None
nums = [5, 2, 7, 1, 3, 6, 8, 4, 9]
for num in nums:
root = insert_node(root, num)
# 输出二叉排序树的中序遍历结果
print(in_order_traversal(root))
平衡树
为了保证二叉排序树的中序遍历有序性,会导致树的不平衡;
为了提高查询性能,需要避免极度不平衡的树(倾斜树);
- 理想平衡,左右子树的高度一样,需要耗费时间调整平衡;
- 适度平衡,左右子树的高度大致一样;
- AVL平衡二叉(查找)树,是一种特殊的二叉排序树;左右子树的高度差绝对值;左右子树也是平衡二叉树;
- 平衡因子,左子树的高度减去右子树的高度;
- 当插入数据导致不平衡时,只需从下向上依次调整最小的不平衡子树;
- 调整平衡方法,LL型、RR型、LR型、RL型;
- LL型,当一个节点的平衡因子不在(-1,0, 1)时,即出现了不平衡,向其左子树方向(高度大的一侧)走两次,以中间节点为轴,以走过的边为左右子树的指针,顺时针旋转,断开的节点连接到初始的树根节点。
- LR型,向高度大的左子树走一步L,向导致不平衡的右子树走一步R,(L边先断开)以LR中间的节点逆时针旋转(断开的节点连接初始的根),旋转后的树根挂载到L边;然后再LL调整。(LR->逆时针->LL->顺时针)
- RL型,(RL->顺时针->RR-逆时针)
- 平衡二叉树插入
如下完成平衡二叉树的插入,思路:从下往上调整最小不平衡子树
将最小不平衡子树调整为: - 平衡二叉树删除
1.如果平衡二叉树为空,则返回;
2.如果T.data==x,查找成功,若T节点有一个孩子为空,删除T节点后,其孩子代替其位置;若T有两个孩子,则删除T后,令T的直接前驱(直接后驱)代替它,并删除其直接前驱(直接后驱);直接前驱-左子树的最右叶子节点;直接后驱-右子树的最左叶子节点
3.如果T.data<x 或者>x 则到对应的右/左子树中删除;
4.调整平衡;
删除案例1:直接删除该节点,调整平衡;
最后树变为:
删除案例2
删除后右子树的结果: - 树堆Treap,特殊的二叉排序树;
- 红黑树(理解),二叉排序树中左右子树高度差不超2倍;
- 根黑,叶子(Null)黑,内部节点为黑或者红色;红父必黑孩子;任意节点到所有叶子的路径上黑节点数相同;
- 构建红黑树练习
- 思路:根为黑,父节点为黑,直接插入;父节(p)点为红,且父节点的兄弟(u)为红,即双红修正–将p&u变黑,p&u的父节点(g)变红,并将g看作是新插入的节点,继续向上处理;若p红u黑,查看g->x路径执行LL/RR/LR/RL旋转,新根变黑,两个孩子变红;
1.插入12,为黑色根; - 2.插入16,根黑,则直接插入(红节点);
- 3.根黑,直接插入2(红色);
- 4.输入30,p&u 为双红,双红修正
- 5.输入28,非双红要旋转,新根变黑,双子变红;
- 6.插入20, 双红修正,p&u变黑,g变红,并将g作为一个新插入节点,继续向上处理;父节点为黑,则直接插入g;
- 7.插入60、29,父节点为黑,直接插入;
- 8.插入85,双红修正;
- 最终结果: