树与树算法
一、树与树算法
1、树的概念
树(英语:tree)是一种抽象数据类型(ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。
它具有以下特点:
(1)每个节点有零个或多个子节点
(2)没有父节点的节点称为根结点
(3)每一个非根节点有且只有一个父节点
(4)除了根结点外,每个节点可以分为多个不相交的子树
2、树的术语
节点的度:一个节点含有的子树的个数称为该节点的度
树的度:一棵树中,最大的节点的度称为树的度
叶节点或终端节点:度为零的节点
父亲节点或父节点:若一个节点含有子节点,则称这个节点称为其子节点的父节点
孩子节点或子节点:一个节点含有的子树的根结点称为该结点的子节点
兄弟节点:具有相同父节点的节点互称兄弟节点
节点的层次:从根开始定义起,根为第一层,根的子节点为第二层,以此类推
树的高度或深度:树中节点的最大层次
3、树的种类
【1】无序树
树中任意节点的子节点之间没有顺序关系,称为无序树,自由树
【2】有序树
树中的任意节点的子节点之间有顺序关系
(1)二叉树
每个节点最多含有两个子树的树
(2)完全二叉树
对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其他各层的节点数目均以达最大值,且第d层所有节点从左向右连续地紧密排列
其中满二叉树的定义是所有的叶节点都在最底层的完全二叉树
(3)平衡二叉树(AVL树)
当且仅当任何节点的两颗子树的高度差不大于1的二叉树
(4)排序二叉树(二叉查找树)『Binary Search Tree』
又称二叉搜索树,有序二叉树
(5)霍夫曼树(用于信息编码)
带权路径最短的二叉树
(6)B树
一种对读写操作进行优化的自平衡的二叉查找树,能保持数据有序,拥有多余两个子树
4、树的储存与表示
顺序储存:将数据结构储存在固定的数组中,然后在遍历速度上有一定的优势,但因此所占空间比较大,是非主流二叉树。
二叉树通常以链式储存。
链式储存:
5、常见的一些树的应用场景
(1)xml,html等,编写这些东西的解析器的时候,不可避免用到树
(2)路由协议就使用了树的算法
(3)mysql数据库索引
(4)文件系统的目录结构
二、二叉树
1、概念:
二叉树是每个节点最多有两个子树的树结构。通常子树被称作左子树lchild ,右子树rchild
2、性质:
(1)在二叉树的第i层上至多有2^(i-1)个节点
(2)深度为k的二叉树至多有2^k-1个节点
(3)对于任意一颗二叉树,如果其叶子节点数为N0.而度为2的节点总数为N2,N0=N2+1
(4)具有n个结点的完全二叉数的深度必为log2(n+1)
(5)对于完全二叉数,若从上至下,从左至右编号,则编号为i的节点,其左孩子编号必为2i,其右孩子编号必为2i+1,
其双亲的编号必为i/2.
3、二叉数的节点表示以及树的创建
class Node(object):
"""二叉树节点对象的封装"""
def __init__(self,element):
self.element =element
self.lchild = None
self.rchild =None
def __str__(self):
"""是打印的节点更好的展示出来"""
return '<Node:%d>'%(self.element)
class BinarySortTree(object):
"""e二叉排序树的封装"""
def __init__(self):
self.root =None
def is_empty(self):
return self.root is None
def add(self,item):
"""树里面插入元素
1. 先判断树是否为空, 直接让根结点指向新的节点
2. 如果不为空:
从根结点开始访问, 判断一下item和节点的大小;
如果大于节点元素, 往右孩子节点添加;
如果小于节点元素, 往左孩子节点添加;
"""
node =Node(item)
if self.root is None:
self.root = node
bt = self.root
while True:
rootItem =bt.element
#判断添加的元素是否大于根结点
if item > rootItem:
#如果根的右子树为空,直接添加节点到右子树即可
if bt.rchild is None:
bt.rchild = node
# 如果根结点的右子树不为空, 将右子树节点作为新的根结点, 循环继续判断;;
bt =bt.rchild
elif item <rootItem:
if bt.lchild is None:
bt.lchild =node
bt =bt.lchild
# 如果插入的元素和根结点相等, 不做操作;
else:
return
def breadth_travel(self):
"""
利用队列实现树的层次遍历
:return:
"""
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 search(self,root,key):
"""搜索指定元素是否在树里面
key:用户搜索的元素"""
if root in None:
return False
# 如果找到节点元素和用户搜索的值相等, 直接返回节点对象
if root.element ==key:
return root
elif root.element >key:
return self.search(root.lchild,key)
else:
return self.search(root.rchild,key)
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). 如果只有左子树或只有右子树,则删除节点后,将子树链接到父节点即可;
"""
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:
if parent.lchild ==node:
parent.lchild =node.lchild
else:
parent.rchild =node.lchild
#只有右子树
if node.lchild is None and node.rchild is not None:
if parent.lchild ==node:
parent.lchild = node.rchild
else:
parent.rchild =node.rchild
# 3).如果有左右子树
# 方法一: 令结点 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
4、二叉树的遍历
树的遍历是树的一种重要的运算,所谓遍历是指对树中所有节点的信息的访问,即依次对树中每个节点访问一次且仅访问一次,我们把这种对所有节点的访问称为遍历(traversal)
树的两种重要的遍历模式是深度优先遍历和广度优先遍历。
深度优先一般用递归,广度优先一般用队列。
深度优先遍历(Depth First Search)
【1】先序遍历
def preorder(self,root):
"""先序遍历:根-左-右"""
if root == None:
return
print(root.element,end=',')
self.preorder(root.lchild)
self.preorder(root.rchild)
【2】中序遍历
def inorder(self,root):
"""中序遍历:左-根-右"""
if root == None:
return
self.inorder(root.lchild)
print(root.element, end=', ')
self.inorder(root.rchild)
【3】后序遍历
def lastorder(self,root):
"""后序遍历:左 -右-根"""
if root == None:
return
self.lastorder(root.lchild)
self.lastorder(root.rchild)
print(root.element, end=', ')
广度优先遍历(层次遍历)
从树的root开始,从上到下从左到右遍历整个树的节点
def breadth_travel(self):
"""
利用队列实现树的层次遍历
:return:
"""
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()