树
树的概念
树(英语:tree)是一种抽象数据类型(ADT),用来模拟具有树状结构性质的数据集合。它是由n(n>=0)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
-
当n=0时,为空树,在任何一棵空树中:
-
有且仅有一个特定的称为根(Root)的节点
-
当n>1时:
- 除根节点外,其余节点可分为m(m>0)个互不相交的有限集T1、T2、…、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。
- 每个节点有零个或多个子节点;
- 没有父节点的节点称为根节点;
- 每一个非根节点有且只有一个父节点;
- 除了根节点外,每个子节点可以分为多个不相交的子树;
树的术语
- 节点的度:一个节点含有的子节点的个数称为该节点的度。
- B的子节点为D、E、F, 因此,节点B的度为3。
- 树的度:一棵树中,最大的节点的度称为树的度。
- 最大的节点的度为B节点的度,因此该树的度为3。
- 叶节点或终端节点:度为零的节点。
- 不能往下再分的节点:K、J、F、L、O、P。
- 父亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点。
- 如:K的父节点为I。
- 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点。
- B的孩子节点或子节点为:D、E、F。
- 兄弟节点:具有相同父节点的节点互称为兄弟节点。
- 具有相同父节点的节点,即同一层次:D的兄弟节点为E、F。
- 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。
- 第一层:A,第二层:B、C,第三层:D、E、F、G、H。
- 树的高度或深度:树中节点的最大层次。
- 最深的树为A、B、D、I、K或者A、C、H、N、P,最大深度为5。
- 节点的祖先:从根到该节点所经分支上的所有节点。
- O的祖先为A、C、G、M。
- 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
- G的子孙为:L、M、O。
- 森林:由m(m>=0)棵互不相交的树的集合称为森林。
树的种类
无序树
树中任意节点的子节点之间没有顺序关系,这种树称为无序树,也称为自由树;几乎没有什么研究意义。
有序树
树中任意节点的子节点之间有顺序关系,这种树称为有序树;
树的存储结构
顺序存储
将数据结构存储在固定的数组中,然在遍历速度上有一定的优势,但因所占空间比较大,是非主流二叉树。
链式存储
链式存储的缺陷是指针的个数不定,因此常见树的存储表示都转换成二叉树进行处理,子节点个数最多为2。
应用场景
-
xml,html等,那么编写这些东西的解析器的时候,不可避免用到树。
-
路由协议就是使用了树的算法。
-
mysql数据库索引。
-
文件系统的目录结构。
-
所以很多经典的AI算法其实都是树搜索,此外机器学习中的decision tree也是树结构。
二叉树
二叉树的概念
子树
二叉树是每个节点最多有两个子树的树结构,通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。
路径和路径长度
在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。
该二叉树结点A到结点D的路径长度为2,结点A到达结点C的路径长度为1。
结点的权及带权路径长度
若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。
树的带权路径长度
树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。
上图的WPL = 6 * 2 + 3 * 2 + 8 * 2 = 34。
二叉树的性质
-
在二叉树的第i层上至多有2^(i-1)个结点(i>0)。
-
深度为k的二叉树至多有2^k - 1个结点(k>0)。
-
对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1。
- 一棵深度k且有2^{k}-1个结点的二叉树称为满二叉树。深度为k,结点数为n的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号为1至n的结点一一对应时,称之为完全二叉树。在下图2中,(a)为满二叉树,(b)为完全二叉树。
-
具有n个结点的完全二叉树的深度必为 log2(n+1)。
-
对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子编号必为2i+1;其双亲的编号必为i/2(i=1 时为根,除外)。
- 如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲结点为1/2。
- 如果2i>n,则结点i无左孩子;否则其左孩子是结点2i。
- 如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。
二叉树的种类
完全二叉树
对于一棵二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树。
特点:
-
叶子结点只能出现在最下层和次下层。
-
最下层的叶子结点集中在树的左部。
-
倒数第二层若存在叶子结点,一定在右部连续位置。
-
如果结点度为1,则该结点只有左孩子,即没有右子树。
-
同样结点数目的二叉树,完全二叉树深度最小。
注:满二叉树一定是完全二叉树,但反过来不一定成立。
满二叉树
满二叉树的定义是所有叶节点都在最底层的完全二叉树。
特点:
- 叶子只能出现在最下一层。出现在其它层就不可能达成平衡。
- 非叶子结点的度一定是2。
- 在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。
二叉排序树
二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),也称二叉搜索树。二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
特点
- 若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值;
- 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
- 左、右子树也分别为二叉排序树;
平衡二叉树(AVL树)
平衡二叉树(AVL树):当且仅当任何节点的两棵子树的高度差不大于1的二叉排序树(二叉查找树);
一棵AVL树有如下必要条件:
- 它必须是二叉查找树。
- 每个节点的左子树和右子树的高度差至多为1。
图一中左边二叉树的节点45的左孩子46比45大,不满足二叉搜索树的条件,因此它也不是一棵平衡二叉树。
右边二叉树满足二叉搜索树的条件,同时它满足条件二,因此它是一棵平衡二叉树。
斜树
斜树:所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。
- 左斜树
- 右斜树
霍夫曼树
霍夫曼树:给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为霍夫曼树(Huffman Tree)。
叶子结点为A、B、C、D,对应权值分别为7、5、2、4。
3.1.a树的WPL = 7 * 2 + 5 * 2 + 2 * 2 + 4 * 2 = 36
3.1.b树的WPL = 7 * 1 + 5 * 2 + 2 * 3 + 4 * 3 = 35
由ABCD构成叶子结点的二叉树形态有许多种,但是WPL最小的树只有3.1.b所示的形态。则3.1.b树为一棵霍夫曼树。
二叉树的创建(链表)
节点的创建
-
代码思路
一个二叉树的节点包含数据域,即本身的值,还有左孩子和右孩子。
-
代码实现
class BTree(object): def __init__(self, data=None, left=None, right=None): """ 定义一个二叉树的节点 :param data:数据域 :param left: 左孩子 :param right: 右孩子 """ self.data = data self.left = left self.right = right self.dot = Digraph(comment='Binary Tree')# 绘制节点
判断二叉树是否为空
-
代码实现
def is_empty(self): """ 判断二叉树是否为空 :return: """ return self.data is None
叶子节点
-
代码实现
def leaves(self): """ 递归遍历二叉树的节点 :return: """ if self.data is None: return None elif self.left is None and self.right is None: print(self.data,end = " ") elif self.left is None and self.right is not None: self.right.leaves() elif self.left is not None and self.right is None: self.left.leaves() else: self.left.leaves() self.right.leaves()
二叉树的高度
-
代码实现
def height(self): """ 二叉树的高度 空树的高度为0,只有节点的树的高度为1 :return: """ if self.data is None: return 0 elif self.left is None and self.right is None: return 1 elif self.left is None and self.right is not None: return 1 + self.right.height() elif self.left is not None and self.right is None: return 1 + self.left.height() else: return 1 + max(self.left.height(), self.right.height())
二叉树的遍历
先、中、后均指的是根节点的遍历先后顺序,遍历子节点的时候都是先左后右。
先序遍历
若二叉树为空,为空操作;否则
- 访问根节点;
- 先序遍历左子树;
- 先序遍历右子树。
-
代码实现
def preorder(self, root): """ 递归实现先序遍历 :param root: :return: """ if root is None: return print(root.item, end=" ") self.preorder(root.left) self.preorder(root.right)
中序遍历
若二叉树为空,为空操作;否则
- 中序遍历左子树;
- 访问根结点;
- 中序遍历右子树。
-
代码实现
def inorder(self, root): """ 递归实现中序遍历 :param root: :return: """ if root is None: return self.inorder(root.left) print(root.item, end=" ") self.inorder(root.right)
后序遍历
若二叉树为空,为空操作;否则
- 后序遍历左子树;
- 后序遍历右子树;
- 访问根结点。
-
代码实现
def postorder(self, root): """ 递归实现后序遍历 :param root: :return: """ if root is None: return self.postorder(root.left) self.postorder(root.right) print(root.item, end=" ")
广度优先遍历(层序遍历)
若二叉树为空,为空操作;否则从上到下、从左到右按层次进行访问。
-
代码实现
def levelorder(self): """ 层序遍历 :return: """ if self is None: return queue = [self] while queue: cur_node = queue.pop(0) print(cur_node.data, end=" ") if cur_node.left is not None: queue.append(cur_node.left) if cur_node.right is not None: queue.append(cur_node.right)
绘制二叉树
-
代码实现
def print_tree(self, save_path='./Binary_Tree.gv', label=False): """ 利用Graphviz实现二叉树的可视化 :param save_path:文件保存路径 :param label: 标签 :return: """ # colors for labels of nodes colors = [ 'skyblue', 'tomato', 'orange', 'purple', 'green', 'yellow', 'pink', 'red'] # 绘制以某个节点为根节点的二叉树 def print_node(node, node_tag): # 节点颜色 color = sample(colors, 1)[0] if node.left is not None: left_tag = str(uuid.uuid1()) # 左节点的数据 self.dot.node(left_tag, str(node.left.data), style='filled', color=color) # 左节点 label_string = 'L' if label else '' # 是否在连接线上写上标签,表明为左子树 self.dot.edge( node_tag, left_tag, label=label_string) # 左节点与其父节点的连线 print_node(node.left, left_tag) if node.right is not None: right_tag = str(uuid.uuid1()) self.dot.node(right_tag, str(node.right.data), style='filled', color=color) label_string = 'R' if label else '' # 是否在连接线上写上标签,表明为右子树 self.dot.edge(node_tag, right_tag, label=label_string) print_node(node.right, right_tag) # 如果树非空 if self.data is not None: root_tag = str(uuid.uuid1()) # 根节点标签 self.dot.node( root_tag, str( self.data), style='filled', color=sample( colors, 1)[0]) # 创建根节点 print_node(self, root_tag) self.dot.render(save_path) # 保存文件为指定文件
测试
-
代码实现
if __name__ == '__main__': # 构造二叉树 right_tree = BTree(6) right_tree.left = BTree(2) right_tree.right = BTree(4) left_tree = BTree(5) left_tree.left = BTree(1) left_tree.right = BTree(3) tree = BTree(11) tree.left = left_tree tree.right = right_tree left_tree = BTree(7) left_tree.left = BTree(3) left_tree.right = BTree(4) right_tree = tree # 增加新的变量 tree = BTree(18) tree.left = left_tree tree.right = right_tree print('先序遍历为:') tree.preorder() print() print('中序遍历为:') tree.inorder() print() print('后序遍历为:') tree.postorder() print() print('层序遍历为:') tree.levelorder() print() height = tree.height() print('树的高度为%s.' % height) print('叶子节点为:') tree.leaves() print() tree.print_tree(save_path='./BTree.gv', label=True) print("二叉树已保存")
-
测试结果
先序遍历为: 18 7 3 4 11 5 1 3 6 2 4 中序遍历为: 3 7 4 18 1 5 3 11 2 6 4 后序遍历为: 3 4 7 1 3 5 2 4 6 11 18 层序遍历为: 18 7 11 3 4 5 6 1 3 2 4 树的高度为4. 叶子节点为: 3 4 1 3 2 4 二叉树已保存
-
构造的二叉树如下:
二叉树的创建(顺序表)
-
代码实现
def create_BTree_By_List(array): i = 1 # 将原数组拆成层次遍历的数组,每一项都储存这一层所有的节点的数据 level_order = [] sum = 1 while sum < len(array): level_order.append(array[i-1:2*i-1]) i *= 2 sum += i level_order.append(array[i-1:]) # print(level_order) # BTree_list: 这一层所有的节点组成的列表 # forword_level: 上一层节点的数据组成的列表 def Create_BTree_One_Step_Up(BTree_list, forword_level): new_BTree_list = [] i = 0 for elem in forword_level: root = BTree(elem) if 2*i < len(BTree_list): root.left = BTree_list[2*i] if 2*i+1 < len(BTree_list): root.right = BTree_list[2*i+1] new_BTree_list.append(root) i += 1 return new_BTree_list # 如果只有一个节点 if len(level_order) == 1: return BTree(level_order[0][0]) else: # 二叉树的层数大于1 # 创建最后一层的节点列表 BTree_list = [BTree(elem) for elem in level_order[-1]] # 从下往上,逐层创建二叉树 for i in range(len(level_order)-2, -1, -1): BTree_list = Create_BTree_One_Step_Up(BTree_list, level_order[i]) return BTree_list[0]
-
测试
array = list(range(1,16)) # array = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' tree = create_BTree_By_List(array) print('先序遍历为:') tree.preorder() print() height = tree.height() print('\n树的高度为%s.\n'%height) print('层序遍历为:') level_order = tree.levelorder() print(level_order) print() print('叶子节点为:') tree.leaves() print() # 利用Graphviz进行二叉树的可视化 tree.print_tree(save_path='./create_btree_by_list.gv', label=True)
-
测试结果
先序遍历为: 1 2 4 8 9 5 10 11 3 6 12 13 7 14 15 树的高度为4. 层序遍历为: [[1], [2, 3], [4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14, 15]] 叶子节点为: 8 9 10 11 12 13 14 15
参考资料
二叉树的Python实现:https://mp.weixin.qq.com/s/kmTDlq4gNmAeH5SDfhdoGA
二叉查找树(BST)、平衡二叉树(AVL树): https://www.cnblogs.com/sgatbl/p/9426394.html
深入学习二叉树(一) 二叉树基础:https://www.jianshu.com/p/bf73c8d50dc2
深入学习二叉树(二) 线索二叉树:https://www.jianshu.com/p/3965a6e424f5
深入学习二叉树(三) 霍夫曼树:https://www.jianshu.com/p/5ad3e97d54a3
深入学习二叉树(四) 二叉排序树:https://www.jianshu.com/p/bbe133625c73
小甲鱼数据结构与算法:https://www.bilibili.com/video/av2975983?from=search&seid=9609762438302042957
个人总结 ,如有错误,请批评指正!
来源:https://blog.csdn.net/Heitao5200/article/details/102534806