二叉树
树是一种重要的非线性数据结构,主要的用途是用来提高查找效率,对于要重复查找的情况效果更佳,如二叉排序树、FP-树。另外可以用来提高编码效率,如哈弗曼树。一切具有层次关系的问题都可用树来描述。
树结构的特点是:
- 它的每一个结点都可以有不止一个直接后继
- 除根结点外的所有结点都有且只有一个直接前驱。
树的递归定义如下:
- 至少有一个结点(称为根)
- 其它是互不相交的子树
二叉树结构
- 节点值
- 左孩子
- 右孩子
1
2
3
4
5
6class Node:
# 创建节点类,包含左右两个指针,一个节点
def __init__(self,elem=None):
self.elem=elem
self.right=None
self.left=None
二叉树遍历
先序遍历:1,2,4,5,3,6,7
中序遍历:4,2,5,1,6,3,7
后序遍历:4,5,2,6,7,3,1
还有:中右左、右中左、右左中三中不常见的遍历
层序遍历:1,2,3,4,5,6,7
按层遍历二叉树实际上是宽度优先搜索的应用。
使用递归实现二叉树的遍历:
1 | class printTree: |
非递归实现二叉树的遍历:
1 | def pre_stack(self, root): |
递归和非递归的方法时间复杂度都是O(N),N为二叉树节点数,额外空间复杂度为O(L),L为二叉树的层数。
实现二叉树的层序遍历:
1 | def level_queue(self, root): |
根据遍历顺序还原二叉树
例如给定前序和中序的打印顺序,求后续遍历。
前序遍历有个特点,第一个肯定为根。然后根据根,在中序遍历可以区分出来左子树和右子树。递归的查找,就可以还原了。1
2
3
4
5
6
7
8
9
10
11
12def permid_post(perstr, midstr):
if not perstr or not midstr:
return
val = perstr[0]
node = Node(val)
index = midstr.find(val)
permid_post(perstr[1:index+1],midstr[:index])
permid_post(perstr[index+1:],midstr[index+1:])
print(node.elem,end=' ')
perstr = 'GDAFEMHZ'
midstr = 'ADEFGHMZ'
permid_post(perstr,midstr)
二叉树序列化和反序列化
有时我们需要用文件的形式将二叉树记录下来,下次要重构二叉树的时候只需将按照文件重新建立二叉树即可。这个过程叫做序列化过程,也叫持久化过程。
序列化方式:
- 先序遍历序列化
- 中序遍历序列化
- 后序遍历序列化
- 按层序列化
先序遍历二叉树过程与中序后序相同,步骤如下:
- 假设序列化结果为str,初始时str为空字符串
- 先序遍历二叉树时如果遇到空节点,在str末尾加上“#!”
- 如果遇到不为空的节点,假设节点值为3,就在str末尾增加‘3!’
先序遍历反序列化过程: - 按照!分割字符串
- 按照先序遍历的顺序构建二叉树,遇到#设置为空节点
1 | def Serialize(self, root): # 把对象转换为字节序列的过程称为对象的序列化 |
当然,用什么方法序列化,要用同样的方法反序列化才行。
二叉树的一些概念
二叉的子树
在二叉树中以任何一个节点为头部的整棵树称为二叉树的子树。
上图不是子树,因为不是整棵树。只是一个联通的子结构
平衡二叉树(AVL树)
- 空树是平衡二叉树
- 一棵树不为空,并且其中所有子树都满足各自的左子树与有字数的高度差都不超过1。
这不是一颗平衡二叉树,以②为节点划分,左子树高度为2,右子树高度为0,高度差超过了1。
判断一个树是否是平衡二叉树,采用递归的后续遍历:
- 首先判断左子树是否为平衡二叉树返回false,则结束。
- 计算左子树最深到哪一层,LH
- 判断右子树是否为平衡二叉树返回false,则结束。
- 计算右子树最深到哪一层,RH
- 判断LH和RH是否相等。
1
2
3
4
5
6
7
8
9
10def is_balanced2(self, root):
if not root:#空树是平衡二叉树
return True,0
left_info=self.is_balanced(root.left)
right_info=self.is_balanced(root.right)
if left_info[0] and right_info[0]:
diff=left_info[1]-right_info[1]
if -1<=diff<=1:
return True,max(left_info[1],right_info[1])+1
return False,max(left_info[1],right_info[1])+1
不平衡二叉树旋转
首先旋转的二叉树是二叉查找树,讨论的是其平衡问题。这样很好的解决了二叉查找树退化成链表的问题。使得插入、查找删除的最好最坏时间复杂度都为O(logN)。
左左或者右右,可以使用单旋转:
左右或者右左,可以使用双旋转
平衡二叉树查找
平衡二叉树插入
平衡二叉树删除
二叉查找树
是满足以下条件的二叉树:
- 左子树上所有节点值均小于根节点值
- 右子树上所有节点值均不小于根节点值
- 左右子树也满足上述两个条件
二叉查找树的查找过程
- 若当前节点cur的值小于p的值,查找cur的左子树
- 若当前节点cur的值不小于p的值,查找cur的右子树
- 递归上述过程,直到cur==p或者cur为空
二叉查找树的插入
二叉查找树的删除
满二叉树
除了最后一层的节点无任何子节点外,剩下的每一层节点都有两个子节点。
满二叉树层数为L,节点数为N,则$N=2^L-1$,$L=\log_2^{(N+1)}$
完全二叉树
除了最后一层外,其他的每一层节点都是满的。最后一层慢了那就是满二叉树。最后一层不满,缺少的节点也全部集中在右边,那也是完全二叉树。
上面两个图均为完全二叉树。
这就不是一个完全二叉树。
判断是否完全二叉树