🌵数据结构中所说的‘树’,一般都是指二叉树,许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。
目录
- 1.初识树
- 2.树的术语及定义
- 2.1 树的术语
- 2.2 树的定义
- 3. 树的实现
- 3.1 列表实现法
- 3.2 面向对象法
- 4. 二叉树的应用
- 4.1 解析树
- 4.2 树的遍历
- 5. 参考书籍
1.初识树
树结构在我们生活中很常见,我们举一些例子:
生物物种分类树
从树的顶部开始,沿着由椭圆和箭头构成的路径,一直到底部。一个节点的所有子节点都与另一个节点的所有子节点无关,叶子节点都是独一无二。
2.树的术语及定义
2.1 树的术语
先介绍树的一些术语:
- 节点:节点是树的基础部分。它可以有自己的名字,我们称作“键”。节点也可以带有附加信息,我们称作“有效载荷”。有效载荷信息对于很多树算法来说不是重点,但它常常在使用树的应用中很重要。
- 边:边是树的另一个基础部分。两个节点通过一条边相连,表示它们之间存在关系。除了根节点以外,其他每个节点都仅有一条入边,出边则可能有多条。
- 根节点:根节点是树中唯一没有入边的节点(树的启始节点)
- 路径:路径是由边连接的有序节点列表。比如,哺乳纲→食肉目→猫科→猫属
- 子节点:一个节点通过出边与子节点相连。
- 父节点 :一个节点是其所有子节点的父节点。
- 兄弟节点 :具有同一父节点的节点互称为兄弟节点。
- 子树:一个父节点及其所有后代的节点和边构成一棵子树。
- 叶子节点 :叶子节点没有子节点。
- 层数:节点 n 的层数是从根节点到 n 的唯一路径长度。
- 高度 :树的高度是其中节点层数的最大值。
2.2 树的定义
树的定义有两种:
第一种:
- 有一个根节
- 除根节点外,其他每个节点都与其唯一的父节点相连
- 从根节点到其他每个节点都有且仅有一条路径
- 如果每个节点最多有两个子节点,我们就称这样的树为二叉树
第二种:
一棵树要么为空,要么由一个根节点和零棵或多棵子树构成,子树本身也是一棵树。 每棵子树的根节点通过一条边连到父树的根节点。从树的递归定义可知,图中的树至少有 4 个节点,因为三角形代表的子树必定有一个根节点。这棵树或许有更多 的节点,但必须更深入地查看子树后才能确定。
3. 树的实现
3.1 列表实现法
列表表示方式
树的根节点是 myTree[0],左子树是 myTree[1],右子树是 myTree[2]
表示方法如下:
#树
mytree=['a',['b',['d',[],[]],['e',[],[]]],['c',['f',[],[]],[]]]
#左子树
mytree[1]
#右子树
mytree[2]
创建树的函数
def binarytree(r):#创建一颗以r为根节点的树
return [r,[],[]]
def insertleftree(root,newbr):#向左节点插入一棵树
t=root.pop(1)
if len(t)>1:
root.insert(1,[newbr,t,[]])
else:
root.insert(1,[newbr,[],[]])
return root
def insertrighttree(root,newbr):#向右节点插入一颗树
t=root.pop(2)
if len(t)>1:
root.insert(2,[t,newbr,[]])
else:
root.insert(2,[t,newbr,[]])
return root
def getlefttree(r):#获取左子树
return r[1]
def getrighttree(r):#获取右子树
return r[2]
3.2 面向对象法
利用节点与引用。我们将定义一个类,其中有根节点和左右子树的属性。 这种表示法遵循面向对象编程范式,结构如下:
树的实现
class binarytree2:
def __init__(self,key):
self.root=key
self.left=None
self.right=None
def insertleft(self,nbran):#插入左节点
if self.left ==None:
self.left=binarytree2(nbran)
else:
t=binarytree2(nbran)
t.left=self.left
self.left=t
def insertright(self,nbran):#插入右节点
if self.right==None:
self.right=binarytree2(nbran)
else:
t=binarytree2(nbran)
t.right=self.right
self.right=t
def getrighttree(self):#获取右子树
return self.right
def getlefttree(self):#获取左子树
return self.left
def gettreeroot(self):#获取根节点
return self.root
4. 二叉树的应用
4.1 解析树
树的实现已经齐全了,现在来看看如何用树解决一些实际问题。这里介绍解析树,可以用它 来表示现实世界中像句子或数学表达式这样的构造。
句子解析树
数学表达式解析树
构建解析树的第一步是将表达式字符串拆分成标记列表。需要考虑 4 种标记:左括号、右括号、运算符和操作数。我们知道,左括号代表新表达式的起点,所以应该创建一棵对应该表达式的新树。反之,遇到右括号则意味着到达该表达式的终点。我们也知道,操作数既是叶子节点,也是其运算符的子节点。此外,每个运算符都有左右子节点。规则如下:
- 如果当前标记是(,就为当前节点添加一个左子节点,并下沉至该子节点;
- 如果当前标记在列表[’+’, ‘-’, ‘/’, ‘*’]中,就将当前节点的值设为当前标记对应的运算符;为当前节点添加一个右子节点,并下沉至该子节点;
- 如果当前标记是数字,就将当前节点的值设为这个数并返回至父节点;
- 如果当前标记是),就跳到当前节点的父节点。
解析树实现方式
rom pythonds.basic import Stack
from pythonds.trees import BinaryTree
def buildParseTree(teststring):#解析树创建
str=teststring.split()#将字符串拆分
pstack=Stack()#创建一个栈,方便访问树节点
etree=BinaryTree('')#创建树
pstack.push(etree) #将树压入栈中
currenttree = etree
for i in str: #依次判断每个字符所属情况
if i == '(':
currenttree.insertLeft('')
pstack.push(currenttree)
currenttree = currenttree.getLeftChild()
elif i in ['+','-','/','*']:#是运算符,创建右节点
currenttree.setRootVal(i)
currenttree.insertRight('')
pstack.push(currenttree)
currenttree=currenttree.getRightChild()
elif i not in ['+','-','/','*',')']:#是数字设置该节点的值
currenttree.setRootVal(eval(i))
currenttree=pstack.pop()
elif i ==')':
currenttree = pstack.pop()
else:
raise ValueError("Unknown Operator: " + i)
return etree
可通过如下调用:
4.2 树的遍历
树是创建好了,可是怎么遍历树呢?按节点的访问方式分为 3 种。我们将对所有节点的访问称为“遍历”,共有 3 种遍历方式,分别为前序遍历、中序遍历和后序遍历。
- 前序遍历:在前序遍历中,先访问根节点,然后递归地前序遍历左子树,最后递归地前序遍历右子树。
- 中序遍历:在中序遍历中,先递归地中序遍历左子树,然后访问根节点,最后递归地中序遍历右子树。
- 后序遍历:在后序遍历中,先递归地后序遍历右子树,然后递归地后序遍历左子树,最后访问根节。
三种遍历方式
#前序遍历
def preorder(tree):
if tree:
print(tree.getRootVal())
preorder(tree.getLeftChild())
preorder(tree.getRightChild())
#中序遍历
def inorder(tree):
if tree != None:
inorder(tree.getLeftChild())
print(tree.getRootVal())
inorder(tree.getRightChild())
#后序遍历
def postorder(tree):
if tree != None:
inorder(tree.getLeftChild())
inorder(tree.getRightChild())
print(tree.getRootVal())
效果如下:
5. 参考书籍
《python数据结构与算法》
《大话数据结构》