文章目录
树的Python实现
树的嵌套列表实现
- 用Python List来实现二叉树数据结构。
- 递归的嵌套列表实现二叉树,由具有3个元素的列表实现:[root, left, right]
- 第1个元素是根节点的值(是一个数据项)
- 第2个元素是左子树(是一个列表)
- 第3个元素是右子树(是一个列表)
- 嵌套列表法的优点:
- 子树的结构与树相同,是一种递归数据结构
- 易扩展到多叉树,仅需要增加列表元素即可
myTree = [
'a', #根节点 myTree[0]
['b',#左子树 myTree[1]
['d',[],[]],
['e',[],[]]],
['c',#右子树 myTree[2]
['f',[],[]],
[]]
]
通过定义一系列函数来符注操作嵌套列表:
#创建仅有根节点的二叉树
def BinaryTree(r):
return [r,[],[]]
#将新节点插入树中作为其直接的左子节点(如果已有左子节点,则把原左子节点作为新插入节点的左子节点,新插入节点做直接的左子节点)
def insertLeft(root,newBranch):
t = root.pop(1)
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]
#应用
r = BinaryTree(3)
insertLeft(r, 4)
insertLeft(r, 5)
insertRight(r, 6)
insertRight(r, 7)
l = getLeftChild(r)
print(l)
setRootVal(l, 9)
print(r)
insertLeft(l, 11)
print(r)
print(getRightChild(getRightChild(r)))
[5, [4, [], []], []]
[3, [9, [4, [], []], []], [7, [], [6, [], []]]]
[3, [9, [11, [4, [], []], []], []], [7, [], [6, [], []]]]
[6, [], []]
树的链表实现
- 使用节点链接法实现树
- 每个节点保存根节点的数据项,以及指向左右子树的链接
- 通过left和right属性引用其他BinaryTree类实现
class BinaryTree:
def __init__(self, rootObj):
self.key = rootObj #成员key保存根节点数据项
self.leftChild = None #成员leftChid/rightChild则保存指向左/右子树的引用(同样是BinaryTree对象)
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.rightChild = 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
r = BinaryTree('a')
r.insertLeft('b')
r.insertRight('c')
r.getRightChild().setRootVal('hello')
r.getLeftChild().insertRight('d')
树的应用:表达式解析
树用于表示语言中的句子,可以分析句子的各种语法成分,对句子的各种成分进行处理
- 程序设计语言的编译:词法、语法检查;从语法树生成目标代码
- 自然语言处理:机器翻译、语义理解
将表达式表示为树结构:叶节点保存操作数,内部节点保存操作符
- 表达式树层次决定计算的优先级,越底层的表达式,优先级越高。
- 树中每个子树都表示一个子表达式,将子树替换为子表达式值的节点,即可实现求值。
表达式解析:从全括号表达式构建表达式解析树,利用表达式解析树对表达式求值,从表达式解析树恢复原表达式的字符串形式
- 全括号表达式要分解为单词Token列表。
- 其单词分为括号‘()’、操作符‘±*/’和操作数‘0-9’这几类。左括号是表达式的开始,右括号则是表达式的结束。
- 例子(3+(45)):[’(’,‘3’,’+’,’(’,‘4’,’’,‘5’,’)’,’)’
- 创建表达式解析式:从左到右扫描全括号表达式的每个单词,依据规则建立解析树。
- 当前单词是左括号,则为当前节点添加一个新节点作为其左子节点,当前节点下降为这个新节点;如果当前单词是操作符±*/,将当前节点的值设为此符号,为当前节点添加一个新节点作为其右子节点,当前节点下降为这个新节点;如果当前单词是操作数,将当前节点的值设为此数,当前节点上升到父节点;如果当前节点是右括号,则当前节点上升到父节点。
- 创建空树,当前节点为根节点
- 读入’(’,创建了左子节点,当前节点下降
- 读入’3’,当前节点设置为3,上升到父节点
- 读入’+’,当前节点设置为+,创建右子节点,当前节点下降
- 读入’(’,创建左子节点,当前节点下降
- 读入’4’,当前节点设置为4,上升到父节点
- 读入’’,当前节点设置为,创建右子节点,当前节点下降
- 读入’5’,当前节点设置为5,上升到父节点
- 读入’)’,上升到父节点
- 读入’)’,再上升到父节点
建立表达式解析树思路
- 创建左右子树:insertLeft/Right()
- 当前节点设置值:setRootVal()
- 下降到左右子树:setLeft/RightChild()
- 上升到父节点:用一个栈来记录跟踪父节点
- 当前节点下降时,将下降前的节点push入栈
- 当前节点需要上升到父节点时,上升到pop出栈的节点即可
def buildPaeseTree(fpexp):
fplist = fpexp.split()#分割为token列表
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 = pStack.pop() #出栈上升
currentTree = parent
#操作符
elif i in ['+', '-', '*', '/']:
currentTree.setRootVal(i)
currentTree.insertRight('')
pStack.push(currentTree) #入栈下降
currentTree = currentTree.getRightChild()
#表达式结束
elif i == ')':
currentTree = pStack.pop() #出栈上升
else:
raise ValueError
return eTree
pt = buildPaeseTree("((10 + 5) * 3)")
增强程序可读性operator模块,为我们提供了很多常用操作符的函数。当我们在字典中查找一个操作符时,相应的函数功能会被取回。因为这个取回的变量是一个函数,可以按通常调用的方式来调用它们,如函数名(变量1,变量2)。
- 例如,查找符’+’(2,2)就等价于operator.add(2,2)
import operator
print(operator.add)
print(operator.add(1,2))
op = operator.add
n = op(1,2)
print(n)
<built-in function add>
3
3
利用表达式解析树求值思路:
- 由于二叉树是一个递归数据结构,可以用递归算法来处理
- 求值递归函数evaluate():由前述对子表达式的描述,可从树的底层子树开始,逐步向上层求值,最终得到整个表达式的值
- 基本结束条件:叶节点是最简单的子树,没有左右子节点,其根节点的数据项即为子表达式树的值
- 缩小规模:将表达式树分为左子树、右子树,即为缩小规模
- 调用自身:分别调用evaluate()计算左子树和右子树的值,然后将左右子树的值依根节点的操作符进行计算,从而得到表达式的值
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() #基本结束条件
树的遍历
- 遍历Traversal:对一个数据集中的所有数据项进行访问的操作
- 根据对节点访问次序的不同来区分3种遍历
- 前序遍历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())
- 使三种遍历方法成为二叉树类BinaryTree中的内置方法
- 内置的方法在递归调用方法之前必须检查左右子节点是否存在。
- 但是一般来说,在使用遍历方法,外置函数更好一些。因为很少需要单纯遍历整个树。多数情况下,只是想利用基本的遍历方法来实现其他的事情。
class BinaryTree:
def preorder(self):
print(self.key)
if self.leftChild:
self.leftChild.preorder()
if self.rightChild:
self.rightChild.preorder()
def inorder(self):
if self.leftChild:
self.leftChild.inorder()
print(self.key)
if self.rightChild:
self.rightChild.inorder()
def postorder(self):
if self.leftChild:
self.leftChild.postorder()
if self.rightChild:
self.rightChild.postorder()
print(self.key)
- 后序遍历的一种一般应用:解析树求值
def postordereval(tree):
opers = {
'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}
res1 = None
res2 = None