树的概念
相较于之前说明的栈、队列、表等线性结构,本次说明的“树”是一种非线性结构。树的结构被分成三部分:根、枝和叶。树当中有很多概念,在这里我们列表说明。
定义 | 说明 |
---|---|
节点 Node | 每个节点都有名称,每个节点还可以保存数据 |
边 Edge | 边具有出入方向,父节点到子节点,子节点到父节点不一样 |
根 Root | 唯一一个没有入边的节点 |
路径 path | 一个节点到根节点,由边依次连接在一起的节点的“有序列表” |
子节点 Children / 父节点 parent | 这两个概念是相互的,连着的两个节点,上面的是下面的父节点,下面的是上面的子节点 |
兄弟节点 Sibling | 具有同一个父节点的两个节点 |
子树 Subtree | 一个节点和其所有的子孙节点,以及相关边的集合 |
叶节点 Leaf | 没有子节点的节点 |
层级 Level | 从根节点开始到达一个节点的路径所包含的边的数量 |
高度 Height | 书中所有节点的最大层级 |
树的实现
列表实现
树的每一个节点可以表示成:[root, left, right]。
root:根节点
left:左子树
right:右子树
实现代码如下:
def BinaryTree(r):
return [r, [], []]
def insertLeft(root, newBranch):
t = root.pop(1)
# t有子树
if len(t) > 1:
root.insert(1,[newBranch, t, []])
else:
root.insert(1, [newBranch, [],[]])
return root
def insertRight(root, newBranch):
t = root.pop(2)
if len(t) > 1:
root.insert(2, [newBranch, [], t])
else:
root.insert(2, [newBranch, [],[]])
return root
def getRootVal(root):
return root[0]
def setRootVal(root, newVal):
root[0] = newVal
def getLeftChild(root):
return root[1]
def getRightChild(root):
return root[2]
链表实现
链表实现树的 方式更常见,每一个节点包含三个部分:key, leftchild, rightchild。构建出节点之后,将这些节点连接起来就形成了树。
代码实现如下:
# 链表实现树
class BinaryTree:
def __init__(self, rootObj):
self.key = rootObj
self.leftChild = None
self.rightChild = None
def insertLeft(self, newNode):
if self.leftChild == None:
self.leftChild = BinaryTree(newNode)
else:
t = BinaryTree(newNode)
t.leftChild = self.leftChild
self.leftChild = t
def insertRight(self, newNode):
if self.rightChild == None:
self.rightChild = BinaryTree(newNode)
else:
t = BinaryTree(newNode)
t = self.rightChild
self.rightChild = t
def getRightChild(self):
return self.rightChild
def getLeftChild(self):
return self.leftChild
def setRootVal(self, obj):
self.key = obj
def getRootVal(self):
return self.key
树的简单应用
问题:表达式解释。
存数据
解读:将一个表达式中的操作数和操作符都存入树中。例如:( 3+ ( 4 * 5 ) )。我们规定,叶节点存放操作数,内部节点存放操作符。
创建表达式解析树的过程:
1、创建空树,当前节点为根节点;
2、读入“(”,创建左子节点,当前节点下降;
3、读入“3”,当前节点设置为3,上升到父节点;
4、读入“+”,当前节点设置为+,创建右子节点,当前节点下降;
5、读入“(”,创建左子节点,当前节点下降;
6、读入“4”,当前节点设置为4,上升到父节点;
7、读入“*”,当前节点设置为 *,创建右子节点,当前节点下降;
8、读入“5”,当前节点设置为5,上升到父节点;
9、读入“)”,上升到父节点;
10、读入“)”,上升到父节点。
从上述我们可以总结出,总共有4种情况:
- 遇到“(”,创建左子节点,当前节点下降;
- 遇到“)”,上升到父节点;
- 遇到操作数,当前节点设置为该操作数,上升到父节点;
- 遇到操作符,当前节点设置为该运算符,创建右子节点,当前节点下降。
需要注意的是:在树的方法中,我们并没有“上升到父节点的方法”,在这里我们通过栈来实现。
实现代码如下:
def buildParseTree(fpexp):
fplist = fpexp.split()
pStack = Stack()
eTree = BinaryTree('')
pStack.push(eTree) # 入栈下降
currentTree = eTree
for i in fplist:
if i == '(':
currentTree.insertLeft('')
pStack.push(currentTree)
currentTree = currentTree.getLeftChild()
elif i not in ['+','-','*','/',')']:
currentTree.setRootVal(int(i))
parent = Stack.pop()
currentTree = parent
elif i in ['+','-','*','/']:
currentTree.setRootVal(i)
currentTree.insertRight('')
Stack.push(currentTree)
currentTree = currentTree.geRightChild()
elif i == ')':
currentTree = pStack.pop()
else:
raise ValueError
return eTree
取数据
上述 code 实现了将表达式存入树的过程,那么又该怎样从树中读取各个符号并求值呢?我们通过递归来实现。
在这之前,我们来介绍一个小技巧,从而更加方便的实现符号运算。
import operator
print(operator.add(1,2))
op = operator.add
print(op(1, 2))
测试结果:
3
3
实现求值的代码如下:
import operator
def evaluate(parseTree):
opers = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}
leftC = parseTree.getLeftChild()
rightC = parseTree.getRightChild()
if leftC and rightC:
fn = opers[parseTree.getRootVal()]
return fn(evaluate(leftC), evaluate(rightC))
else:
return parseTree.getRootVal() # 叶节点
树的遍历
树的遍历(Tree Traversals),分为三种:前序遍历、中序遍历和后序遍历。
- 前序遍历(preorder):根节点 --> 左子树 --> 右子树
- 中序遍历(inorder):左子树 --> 根节点 --> 右子树
- 后序遍历(postorder):左子树 – > 右子树 --> 根节点
从上面我们可以看出,树的遍历名字的区分,是根节点遍历的顺序。
树的遍历,我们通过递归实现。
# 前序遍历
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:
postorder(tree.getLeftChild())
postorder(tree.getRightChild())
print(tree.getRootVal())
关于树呢,还有二叉完全树、二叉查找树等内容,入门来说相对复杂,请有兴趣的同学自行学习。