树的构成要素:
节点(Node) 边(Edge) 根节点(Root) 路径(Path) 子节点集(Children) 父节点(Parent) 兄弟节点(Sibling)
子树 (Subtree) 叶节点(Leaf Node) 层数(Level) 高度(height)
定义一:树有以下特征:
- 有一个节点是根节点
- 除了根节点外的每一个节点n,都通过一条边与另一个节点p相连,p是n的父节点
- 可以沿着唯一的路径从根节点到达每个节点
- 如果这个树每个节点都至多有两个子节点,我们称它为二叉树
定义二:
每个树或者为空或者包含一个根节点和零个或多个子树,其中每个子树也符合这样的定义
通过嵌套列表来实现树
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]
第二种实现树的方式 使用节点和引用 利用对象的方式
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.leftChild = self.leftChild
self.leftChild = 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
在插入时我们必须考虑两种情况,第一种情况:当前没有现有左子节点,当没有左子节点时,简单的将新节点添加到树中即可
有左子节点时 我们插入节点时 需要把原有的左子节点进行降级
解析树
比如(7+3)*(5-2):如果当前读入字符是‘(’添加一个新的节点作为当前节点的子节点,当前节点下降
如果当前读入的字符在列表 ['+','-','/','*'] 中,将当前节点的根值设置为当前读入的字符。添加一个新的节点(node)作为当前节点的右子节点,当前节点下降。
如果当前读入的字符是一个数字,将当前节点的根值设置为该数字,当前节点变为它的父节点
如果当前读入的字符是 ')' ,当前节点变为其父节点(parent)。
写一个解析树
from pythonds.basic.stack import Stack
from pythonds.trees.binaryTree import BinaryTree
def buildParseTree(parse):
fplist = parse.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 = 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 = buildParseTree("( ( 1 + 2 ) * 3 )")
pt.postorder()
使用堆栈保持对父节点的跟踪,当我们要下降到当前节点的子节点时,我们先将当前节点压入栈中,而当我们想要返回当前节点的父节点时,我们就从堆栈中弹出该父节点
在解析树中,叶节点总是操作数 我们只需检查一个操作符是否是叶节点,递归调用使我们有效的向叶节点移动
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()
print(evaluate(pt))
树的遍历
- 前序遍历 :在前序遍历中,我们先访问根节点,然后递归地前序遍历访问左子树,再递归地前序遍历访问右子树
- 中序遍历:先访问左子树 再根节点 最后右子树
- 后序遍历:先访问左子树和右子树 ,最后访问根节点
从头到尾阅读这本书 前序遍历正好符合这种规则
前序遍历
def preordder(tree):
if tree:
print(tree.getRootVal())
preordder(tree.getLeftChild())
preordder(tree.getRightChild())
后序遍历
def postorder(tree):
if tree:
postorder(tree.getLeftChild())
postorder(tree.getRightChild())
print(tree.getRootVal())
中序遍历
def inorder(tree):
if tree:
inorder(tree.getLeftChild())
print(tree.getRootVal())
inorder(tree.getRightChild())
用遍历来计算解析树
def evaluate(parseTree):
opers = {'+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv}
if parseTree:
leftc = postorder(parseTree.getLeftChild())
rightc = postorder(parseTree.getRightChild())
if leftc and rightc:
return opers[parseTree.getRootVal()](leftc,rightc)
else:
return parseTree.getRootVal()