1.树与树算法
树(英语:tree)是一种抽象数据类型(ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。
它是由n(n>=1)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵
倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
每个节点有零个或多个子节点;
没有父节点的节点称为根节点;
每一个非根节点有且只有一个父节点;
除了根节点外,每个子节点可以分为多个不相交的子树;
节点的度:一个节点含有的子树的个数称为该节点的度;
树的度:一棵树中,最大的节点的度称为树的度;
二叉树就是广度不超过2的树
注意!:
二叉树中,第 i 层最多有 2i-1 个结点。
如果二叉树的深度为 K,那么此二叉树最多有 2K-1 个结点。
二叉树中,终端结点数(叶子结点数)为 n0,度为 2 的结点数为 n2,则 n0=n2+1。
二叉树的分类:
1.如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。
2.如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,
则此二叉树被称为完全二叉树。
顺序存储:将数据结构存储在固定的数组中,然在遍历速度上有一定的优势,但因所占空间比较大,是非主流二叉树。二叉树通常以链式存储。
树的创建
class Node(object):
"""节点类"""
def __init__(self, elem=-1, lchild=None, rchild=None):
self.elem = elem
self.lchild = lchild
self.rchild = rchild
class Tree(object):
"""树类"""
def __init__(self, root=None):
self.root = root
def add(self, elem):
"""为树添加节点"""
node = Node(elem)
#如果树是空的,则对根节点赋值
if self.root == None:
self.root = node
else:
queue = []
queue.append(self.root)
#对已有的节点进行层次遍历
while queue:
#弹出队列的第一个元素
cur = queue.pop(0)
if cur.lchild == None:
cur.lchild = node
return
elif cur.rchild == None:
cur.rchild = node
return
else:
#如果左右子树都不为空,加入队列继续判断
queue.append(cur.lchild)
queue.append(cur.rchild)
2.树的遍历
遍历是指对树中所有结点的信息的访问,即依次对树中每个结点访问一次且仅访问一次,我们把这种对所有节点的访问称为遍历(traversal)
1.广度优先(层次遍历):
代码:
def breadth_travel(self,root):
"""利用队列实现树的层次遍历"""
if root == None:
return
queue = []
queue.append(root)
while queue:def breadth_travel(self, root):
"""利用队列实现树的层次遍历"""
node = queue.pop(0)
print(node.elem,end=",")
if node.lchild != None:
queue.append(node.lchild)
if node.rchild != None:
queue.append(node.rchild)
print()
2.深度优先:
2.1先序遍历(根左右):
def preorder(self, root):
"""递归实现先序遍历"""
if root == None:
return
print(root.elem)
self.preorder(root.lchild)
self.preorder(root.rchild)
2.2中序遍历(左根右):
def inorder(self, root):
"""递归实现中序遍历"""
if root == None:
return
self.inorder(root.lchild)
print(root.elem, end=',')
self.inorder(root.rchild)
2.3后序遍历(左右根):
def postorder(self, root):
"""递归实现后续遍历"""
if root == None:
return
self.postorder(root.lchild)
self.postorder(root.rchild)
print(root.elem, end=',')
3.二叉排序树
二叉排序树要么是空二叉树,要么具有如下特点:
二叉排序树中,如果其根结点有左子树,那么左子树上所有结点的值都小于根结点的值;
二叉排序树中,如果其根结点有右子树,那么右子树上所有结点的值都大小根结点的值;
二叉排序树的左右子树也要求都是二叉排序树;
查找:对比节点的值和关键字,相等则表明找到了;小了则往节点的左子树去找,大了则往右子树去找,这么递归下去,最后返回布尔值或找到的节点。
插入:从根节点开始逐个与关键字进行对比,小了去左边,大了去右边,碰到子树为空的情况就将新的节点链接。
删除:如果要删除的节点是叶子,直接删;如果只有左子树或只有右子树,则删除节点后,将子树链接到父节点即可;如果同时有左右子树,则可以将二叉排序树进行中序遍历,取将要被删除的节点的前驱或者后继节点替代这个被删除的节点的位置。
插入:
删除:
如果要删除的节点是叶子,直接删;
如果只有左子树或只有右子树,则删除节点后,将子树链接到父节点即可;
如果同时有左右子树,(两种方式)则可以将二叉排序树进行中序遍历,取将要被删除的节
点的前驱或者后继节点替代这个被删除的节点的位置。
方法一: 令结点 p 的左子树为其双亲结点的左子树;结点 p 的右子树为其自身直接前驱结点的右子树
方法二: 用结点 p 的直接前驱(或直接后继)来代替结点 p,同时在二叉排序树中对其直接前驱(或直接后继)做删除操作。在对左图进行中序遍历时,得到的结点 p 的直接前驱结点为结点 s,所以直接用结点 s 覆盖结点 p
不同的排列顺序,构建出的二叉排序树大不相同。
表 {45,24,53,12,37,93}
表 {12,24,37,45,53,93}
为了弥补二叉排序树构造时产生上图所示的影响算法效率的因素,需要对二叉排序树做“平衡化”处理,使其成为一棵平衡二叉树。
4.平衡二叉树
平衡二叉树,又称为 AVL 树。遵循以下两个特点的二叉树:
每棵子树中的左子树和右子树的深度差不能超过 1;
二叉树中每棵子树都要求是平衡二叉树;
总结: 其实就是在二叉树的基础上,若树中每棵子树都满足其左子树和右子树的深度差都不超过 1,则这棵二叉树就是平衡二叉树。
平衡因子:每个结点都有其各自的平衡因子,表示的就是其左子树深度同右子树深度的差。
平衡二叉树中各结点平衡因子的取值只可能是:0、1 和 -1。
例:
- 表{13,24,37,90,53}构建二叉排序树时,当插入 13 和 24 时,二叉排序树此时还是平衡二叉树:
- 当继续插入 37 时,生成的二叉排序树如(a),平衡二叉树的结构被破坏,此时只需要对二叉排序树做“旋转”操作
平衡二叉树调整的规律
- 单向右旋平衡处理
- 单向左旋平衡处理
- 双向旋转(先左后右)平衡处理
- 双向旋转(先右后左)平衡处理
平衡二叉树可应用于搜索算法中, 进行查找操作的时间复杂度为O(logn)。