树与树算法
一 树
树(tree)是一种抽象数据类型(ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。
树的特点:
- 每个结点有0个或多个子结点
- 没有父结点的结点称为根结点
- 每一个非根结点有且只有一个父结点
- 除了根结点外,每个子结点可以分为多个不相交的子树
树的术语:
结点的度:一个结点含有的子树的个数称为该结点的度。
树的度:一棵树中,最大的结点的度称为树的度。
二 二叉树
二叉树的性质:
-
二叉树中,第 i 层最多有 2i-1 个结点。
-
如果二叉树的深度为 K,那么此二叉树最多有 2K-1 个结点。
-
二叉树中,终端结点数(叶子结点数)为 n0,度为 2 的结点数为 n2,则 n0=n2+1。
区分满二叉树和完全二叉树:
如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。
如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,
则此二叉树被称为完全二叉树。
树的创建:
class Node(object):
"""二叉树节点对象封装的类"""
def __init__(self,element,lchild=None,rchild=None):
self.element=element
self.lchild=lchild
self.rchild=rchild
def __str__(self):
return self.element
def __repr__(self):
return self.element
class Tree(object):
'''二叉树的封装'''
def __init__(self,root=None):
self.root=root
def add(self,item):
"""往二叉树里面添加元素"""
node=Node(item)
#如果是空的,就对根结点赋值。
if self.root == None:
self.root=node
else:
#先找树的根结点,储蓄在变量queue中
queue=[]
queue.append(self.root)
#对已有的节点进行层次遍历
while queue:
item=queue.pop(0)
if not item.lchild:
item.lchild=node
return
if not item.rchild:
item.rchild=node
return
else:
queue.append(item.lchild)
queue.append(item.rchild)
二叉树的遍历
遍历是指对树中所有结点的信息的访问,即依次对树中每个结点访问一次且仅访问一次,我们把这种对所有节点的访问称为遍历(traversal)。
- 广度优先遍历(层次遍历)
def breadth_travel(self):
"""利用队列实现树的层次遍历"""
if not self.root:
return
else:
queue=[]
queue.append(self.root)
while queue:
node=queue.pop(0)
print(node.element,end=', ')
if node.lchild:
queue.append(node.lchild)
if node.rchild:
queue.append(node.rchild)
print()
-
深度优先遍历
-
先序遍历:每遇到一个节点,先访问,然后再遍历其左右子树(根左右)
-
中序遍历:第一次经过时不访问根节点,等遍历完左子树之后再访问,然后遍历右子树(左根右)
-
后续遍历:第一次和第二次经过时都不访问,等遍历完该节点的左右子树之后,最后访问该节点(左右根)
def preorder(self, root):
"""先序遍历: 根左右"""
if root == None:
return
print(root.element, end=', ')
self.preorder(root.lchild)#递归
self.preorder(root.rchild)
def inorder(self, root):
"""递归实现中序遍历:左根右"""
if root == None:
return
self.inorder(root.lchild)
print(root.element, end=', ')
self.inorder(root.rchild)
def lastorder(self,root):
"""递归实现后续遍历:左右根"""
if root == None:
return
self.lastorder(root.lchild)
self.lastorder(root.rchild)
print(root.element, end=', ')
完整代码:
class Node(object):
"""二叉树节点对象封装的类"""
def __init__(self,element,lchild=None,rchild=None):
self.element=element
self.lchild=lchild
self.rchild=rchild
def __str__(self):
return self.element
def __repr__(self):
return self.element
class Tree(object):
'''二叉树的封装'''
def __init__(self,root=None):
self.root=root
def add(self,item):
"""往二叉树里面添加元素"""
node=Node(item)
#如果是空的,就对根结点赋值。
if self.root == None:
self.root=node
else:
#先找树的根结点,储蓄在变量queue中
queue=[]
queue.append(self.root)
#对已有的节点进行层次遍历
while queue:
item=queue.pop(0)
if not item.lchild:
item.lchild=node
return
if not item.rchild:
item.rchild=node
return
else:
queue.append(item.lchild)
queue.append(item.rchild)
def breadth_travel(self):
"""利用队列实现树的层次遍历"""
if not self.root:
return
else:
queue=[]
queue.append(self.root)
while queue:
node=queue.pop(0)
print(node.element,end=', ')
if node.lchild:
queue.append(node.lchild)
if node.rchild:
queue.append(node.rchild)
print()
def preorder(self, root):
"""先序遍历: 根左右"""
if root == None:
return
print(root.element, end=', ')
self.preorder(root.lchild)#递归
self.preorder(root.rchild)
def inorder(self, root):
"""递归实现中序遍历:左根右"""
if root == None:
return
self.inorder(root.lchild)
print(root.element, end=', ')
self.inorder(root.rchild)
def lastorder(self,root):
"""递归实现后续遍历:左右根"""
if root == None:
return
self.lastorder(root.lchild)
self.lastorder(root.rchild)
print(root.element, end=', ')
if __name__ == '__main__':
tree = Tree()
for item in range(10):
tree.add(item + 1)
print("创建树成功!")
#
print("层次遍历".center(30, '*'))
tree.breadth_travel()
print("先序遍历")
root = tree.root
tree.preorder(root)
print("\n中序遍历")
root = tree.root
tree.inorder(root)
print('\n后序遍历')
root = tree.root
tree.lastorder(root)
------------------------------------------------------------
树成功!
*************层次遍历*************
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
先序遍历
1, 2, 4, 8, 9, 5, 10, 3, 6, 7,
中序遍历
8, 4, 9, 2, 10, 5, 1, 6, 3, 7,
后序遍历
8, 9, 4, 10, 5, 2, 6, 7, 3, 1,
二叉排序树
二叉排序树要么是空二叉树要么具有如下特点:
- 二叉排序树中,如果其根结点有左子树,那么左子树上所有结点的值都小于根结点的值;如果其根结点有右子树,那么右子树上所有结点的值都大于根结点的值。
- 二叉排序树的左右子树也要求都是二叉排序树。
二叉排序树的常用操作:
- 查找:对比节点的值和关键字,相等则表明找到了;小了则往节点的左子树去找,大了则往右子树去找,这么递归下去,最后返回布尔值或找到的节点。
def search(self,root,key):
"""
搜索指定元素是否在树里面
key:用户搜索的元素
:param root:
:param key:
:return:
"""
if root is None:
return False
if root.element == key:
return root
elif key<root.element:
return self.search(root.lchild,key)
else:
return self.search(root.rchild,key)
- 插入:从根节点开始逐个与关键字进行对比,小了去左边,大了去右边,碰到子树为空的情况就将新的节点链接。
"""二叉排序树的封装"""
class BinarySortTree(object):
def __init__(self):
self.root=None
def is_empty(self):#为啥此时要有这一步
return self.root is None
def add(self,item):
"""
树里面出入元素
1。先判断树是否为空,直接让根结点指向新的结点
2。如果不为空:
从根结点开始访问,判断一下item和结点的大小;
如果大于结点元素,往右孩子结点添加
如果小于结点元素,往左孩子结点添加
:param item:
:return:
"""
node=Node(item)
if self.root is None:
self.root=node#让根结点指向新的结点
bt=self.root#此时的根结点为bt
#else???
while True:
rootItem=bt.element
"""判断添加的元素是否大于根结点元素"""
if item>rootItem:
if bt.rchild is None:
bt.rchild=node#如果右子树为空,直接添加结点到右子树
else:#如果右子树不为空,将右子树结点作为新的根结点,循环继续判断
bt=bt.rchild
elif item < rootItem:
if bt.lchild is None:
bt.lchild=node
bt=bt.lchild
else:#如果插入的元素和根结点相等,不做操作
return
- 删除:如果要删除的节点是叶子,直接删;如果只有左子树或只有右子树,则删除节点后,将子树链接到父节点即可;如果同时有左右子树,则可以将二叉排序树进行中序遍历,取将要被删除的节点的前驱或者后继节点替代这个被删除的节点的位置。
def searchDetail(self, root, parent, key):
"""
搜索指定元素是否在树里面, 如果有, 则
返回3个值:
1. Bool: 是否找到该元素;
2. node: 找到元素对应的节点
3. parent: 找到的元素对应的父节点;
"""
if root is None:
return False, root, parent
# 如果找到节点元素和用户搜索的值相等, 直接返回节点对象
if root.element == key:
return True, root, parent
# 如果找到节点元素大于用户搜索的值, 直接返回节点对象
elif root.element > key:
return self.searchDetail(root.lchild, root, key)
else:
return self.searchDetail(root.rchild, root, key)
def delete(self, key):
"""
删除二叉排序树的节点元素:
1). 如果要删除的节点是叶子,直接删;
2). 如果只有左子树或只有右子树,则删除节点后,将子树链接到父节点即可;
:param key:
:return:
"""
# 1. 查找删除元素对应的节点, isExists是否找到该节点元素, node是找到的节点对象, parent:元素节点的父节点
isExists, node, parent = self.searchDetail(self.root, None, key)
if not isExists:
print("要删除的元素%d不存在" % (key))
return
# 如果深处的是根节点, 不处理
if node == self.root:
print("不能删除根结点")
return
# 1). 如果要删除的节点是叶子,直接删;
if node.lchild is None and node.rchild is None:
# 判断删除的节点是父节点左孩子还是右孩子
if parent.lchild == node:
parent.lchild = None
else:
parent.rchild = None
# 2). 如果只有左子树或只有右子树,则删除节点后,将子树链接到父节点即可;
# 如果只有左子树
if node.lchild is not None and node.rchild is None:
# 如果node是parent的左子树:
if parent.lchild == node:
parent.lchild = node.lchild
else:
parent.rchild = node.lchild
# 如果只有右子树
if node.rchild is not None and node.lchild is None:
# 如果node是parent的左子树:
if parent.lchild == node:
parent.lchild = node.rchild
else:
parent.rchild = node.rchild
# 如果有左右子树
# 方法一: 令结点 node 的左子树为其双亲结点的左子树;结点 node的右子树为其自身直接前驱结点的右子树
if node.lchild is not None and node.rchild is not None:
# 如何找到node的直接前驱节点, 找到后, 将直接前驱节点的右子树指向node的右子树;
# 当前节点左子树的最右节点
# 分类讨论, 删除的是父节点的左孩子还是右孩子
if parent.lchild == node:
parent.lchild = node.lchild
else:
parent.rchild = node.lchild
prevNode = node.lchild
while prevNode.rchild:
prevNode = prevNode.rchild
# prev指的是node节点的直接前驱(中徐遍历时,node节点前面的节点)
prevNode.rchild = node.rchild
平衡二叉树
平衡二叉树,又称为 AVL 树。遵循以下两个特点的二叉树:
- 每棵子树中的左子树和右子树的深度差不能超过 1;
- 二叉树中每棵子树都要求是平衡二叉树;
总结: 其实就是在二叉树的基础上,若树中每棵子树都满足其左子树和右子树的深度差都不超过 1,则这棵二叉树就是平衡二叉树。
平衡因子:每个结点都有其各自的平衡因子,表示的就是其左子树深度同右子树深度的差。
平衡二叉树中各结点平衡因子的取值只可能是:0、1 和 -1。