文章目录
一、树的概念
树是一种抽象数据类型(ADT)或是视作这种抽象数据类型的数据结构
1、特点
- 每个节点有零个或多个子节点
- 没有父节点的节点称为根节点
- 每一个非根节点有且只有一个父节点
- 除了根节点外,每个子节点可以分为**多个不相交(因为都只有一个父节点)**的子树
2、树的术语
- 节点的度:一个节点含有的子树的个数称为该节点的度
- 树的度:一棵树中,最大的节点的度称为树的度
- 叶节点或终端节点:度为零的节点
- 父亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点
- 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点
- 兄弟节点:具有相同父节点的节点互称为兄弟节点
- 节点的层次:从根节点开始定义,根为第一层,根的子节点为第二层,以此类推
- 树的高度或深度:树中节点的最大层次
- 堂兄弟节点:父节点在同一层的节点互为堂兄弟
- 节点的祖先:从根到该节点所经分支上的所有结点
- 子孙:以某节点为根的子树中任一结点都称为该节点的子孙
- 森林:由m(m>=0)棵互不相交的树的集合称为森林
3、树的种类
- 无序树:树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树
- 有序树:树中任意节点的子节点之间有顺序关系,这种树称为有序树
- 二叉树:每个节点最多含有两个子树的树称为二叉树
- 完全二叉树:对于一颗二叉树,假设其深度为d(d>1).除了第d层外,其他各层的节点数目均已达到最大值,且第d层所有节点从左向右连续的紧密排列,这样的二叉树被称为完全二叉树,其中满二叉树的定义是所有叶节点都在最底层的完全二叉树
- 平衡二叉树(AVL树):当且仅当任何节点的两棵子树的高度差不大于1的二叉树
- 排序二叉树(二叉查找树,也称二叉搜索树、有序二叉树):
- 霍夫曼树(用于信息编码):带权路径最短的二叉树称为哈夫曼树或最优二叉树
- B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多余两个子树
- 二叉树:每个节点最多含有两个子树的树称为二叉树
4、树的存储与表示
顺序存储:将数据结构存储在固定的数组中,在遍历速度上有一定的优势,但因所占空间比较大,是非主流二叉树。二叉树通常以链式存储
链式存储:可以存储,但是子节点个数无法掌握,因此通常把多叉树转换为二叉树,子节点个数最多为2
5、常见的树的应用场景
- xml,html等
- 路由协议使用了树的算法
- MySQL数据库索引
- 文件系统的目录结构
- 很多经典的AI算法,机器学习中的decision tree
二、二叉树
1、概念
二叉树是每个节点最多有两个子树的树结构,通常子树被称作‘左子树’和‘右子树’
2、性质
- 在二叉树的第i层上至多有2(i-1)个节点,i>0
- 深度为k的二叉树至多有2k-1个节点,k>0
- 对于任意一棵二叉树,如果其叶节点树为N0,而度数为2的节点总数为N2,则N0=N2+1
- 具有n个节点的完全二叉树的深度必为log2(n+1)
- 对完全二叉树,若从上至下、从左至右编号,则编号为i的节点,其左孩子编号必为2i,其右孩子编号必为2i+1,其双亲的编号必为i/2(i=1时为根,除外)
3、python实现二叉树的构建
# 定义节点
class Node(object):
def __init__(self,item):
self.elem=item
self.lchild=Node
self.rchild=Node
# 构建树
class Tree(object):
# 二叉树
def __init__(self):
self.root=None
def add(self,item):
node=Node(item)
if self.root is None:
self.root=node
return
# 因为要将新节点插入树中,树上的元素可以按照队列的方式操作,
# 那么就在树中构建一个队列,先遍历元素,然后再将元素放入队列,再进行元素的添加删除等
# 通过队列的方式查找新节点应该放在哪里
# queue=[] queue.append(self.root)
# 先将根节点放入队列中
queue=[self.root]
# 每次取出的元素都进行相同的操作,直到队列中无元素
# 这里使用列表的逻辑值判断,若为空返回False,只要有元素就返回True,即使是None
while queue:
# 创建一个游标从根节点开始遍历,此时队列中只有一个刚放进去的根节点,因此弹出的就是根节点
cur_node=queue.pop(0)
# 判断是否为空,为空时将新的元素挂到树上
if cur_node.lchild is None:
cur_node.lchild=node
return # 这里必须加,否则没有返回值
else:
# 不为空时,将此时左子树的元素放入队列
queue.append(cur_node.lchild)
if cur_node.rchild is None:
cur_node.rchild=node
return
else:
queue.append(cur_node.rchild)
4、二叉树的广度遍历
广度遍历(层次遍历)为从根节点,从上到下,从左到右进行遍历
def breath_travel(self):
"""广度遍历"""
if self.root is None:
return
queue=[self.root]
while queue:
cur_node=queue.pop(0)
print(cur_node.elem)
if cur_node.lchild is not None:
queue.append(cur_node.lchild)
if cur_node.rchild is not None:
queue.append(cur_node.rchild)
使用队列添加和遍历元素,所以元素的输出和输入特点是先进先出
测试及结果:
if __name__ == '__main__':
tree=Tree()
tree.add(1)
tree.add(2)
tree.add(3)
tree.add(4)
tree.add(5)
tree.breath_travel()
1
2
3
4
5
5、深度优先搜索
深度优先搜索是沿着树的深度遍历树的节点,尽可能深的搜索树的分支。
三种访问树的节点的方式:
- 先序遍历:根左右;先访问根节点,然后递归使用先序遍历访问左子树,再递归使用先序遍历访问右子树
- 中序遍历:左根右;递归使用中序遍历访问左子树、根节点、右子树
- 后序遍历:左右根;递归使用后序遍历访问左子树、右子树、根节点
def preoder(self,node):
"""先序遍历:根左右"""
if node is None:
return
print(node.elem,end=' ')
self.preoder(node.lchild)
self.preoder(node.rchild)
def midorder(self,node):
"""中序遍历,左根右"""
if node is None:
return
self.midorder(node.lchild)
print(node.elem, end=' ')
self.midorder(node.rchild)
def behind(self,node):
"""后序遍历;左右根"""
if node is None:
return
self.behind(node.lchild)
self.behind(node.rchild)
print(node.elem,end=' ')
if __name__ == '__main__':
tree=Tree()
tree.add(0)
tree.add(1)
tree.add(2)
tree.add(3)
tree.add(4)
tree.add(5)
tree.add(6)
tree.add(7)
tree.add(8)
tree.add(9)
tree.breath_travel()
# 由于使用队列添加和遍历元素,所以是先进先出
print(' ')
tree.preoder(tree.root)
print(' ')
tree.midorder(tree.root)
print(' ')
tree.behind(tree.root)
print(' ')
结果:
0 1 3 7 8 4 9 2 5 6
7 3 8 1 9 4 0 5 2 6
7 8 3 9 4 1 5 6 2 0
6、由遍历确定一棵树
给出遍历序列,确定树的结构
- 首先给出的序列,依据先序(根左右)、中序(左根右)、后序(左右根)的原则,找出根;
- 然后分出左右子树,再在此基础上,使用同样的方法;
- 分别找出左右子树的根,再继续向下找;
- 以此类推确定树;