树的例子:
生物学的分类树、文件系统、网页
一个节点的所有子节点独立于另一个节点的子节点。
每个叶节点是唯一的。
树的概念
节点、边、根、路径、子节点、父节点、兄弟节点、子树
层数:节点 n 的层数为从根结点到该结点所经过的分支数目。 根节点的层数为零。
高度:树的高度等于树中任何节点的最大层数。
树的定义:
树由一组节点和一组连接节点的边组成。树具有以下属性:
树的一个节点被指定为根节点。
除了根节点之外,每个节点 n 通过一个其他节点 p 的边连接,其中 p 是 n 的父节点。
从根路径遍历到每个节点路径唯一。
如果树中的每个节点最多有两个子节点,我们说该树是一个二叉树。
定义二(递归定义):树是空的,或者由一个根节点和零个或多个子树组成,每个子树也是一棵树。每个子树的根节点通过边连接到父树的根节点。
在列表树的列表中,我们将根节点的值存储为列表的第一个元素。列表的第二个元素本身将是一个表示左子树的列表。列表的第三个元素将是表示右子树的另一个列表。
myTree = ['a', #root
['b', #left subtree
['d', [], []],
['e', [], []] ],
['c', #right subtree
['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)))
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.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
return self.key
def getRootVal(self):
return self.key
r = BinaryTree('a')
print(r.getRootVal()) # a
print(r.getLeftChild()) # None
r.insertLeft('b')
print(r.getLeftChild()) # <__main__.BinaryTree object at 0x00000259D3519DA0>
print(r.getLeftChild().getRootVal()) # b
r.insertRight('c')
print(r.getRightChild()) # <__main__.BinaryTree object at 0x00000259D3519D30>
print(r.getRightChild().getRootVal()) # c
r.getRightChild().setRootVal('hello')
print(r.getRightChild().getRootVal()) # hello
print(r.getRightChild()) # <__main__.BinaryTree object at 0x00000259D3519D30>
解析树常常用于真实世界的结构表示,例如句子或数学表达式。
在分析树中,叶节点将始终是操作数。
定义四条规则:
当我们想要下降到当前节点的子节点时,我们先将当前节点压入栈。当我们想要返回当前节点的父节点时,我们从栈中弹出该父节点。
from pythonds.basic.stack import Stack
from pythonds.trees.binaryTree import BinaryTree
import operator
# from stack import Stack
# from tree_node import BinaryTree
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 = 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("( ( 10 + 5 ) * 3 )")
pt.postorder() #defined and explained in the next section
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()] # mul
return fn(evaluate(leftC), evaluate(rightC))
else:
return parseTree.getRootVal()
result = evaluate(pt)
print(result)
二叉树是一种非线性结构,遍历(traversal)二叉树无法通过简单的循环实现。遍历二叉树就是要让树中所有节点被且仅被访问一次,即按一定规律排列成一个线性队列。二叉树包含3个部分,即根节点、左子树和右子树。根据3个部分的访问次序对二叉树的遍历进行分类,可以将遍历二叉树的方法分为3种类型,分别为先序遍历(preorder),中序遍历(inorder)和后序遍历(postorder)。(先序后序是针对根节点来说)
先序遍历
在先序遍历中,我们先访问根节点,然后递归使用先序遍历访问左子树,再递归使用先序遍历访问右子树。
外置方法:
def preorder(tree):
if tree:
print(tree.getRootVal())
preorder(tree.getLeftChild())
preorder(tree.getRightChild())
内置方法
def preorder(self):
print(self.key)
if self.leftChild:
self.leftChild.preorder()
if self.rightChild:
self.rightChild.preorder()
一般来说preorder作为一个外置方法比较好,原因是,我们很少是单纯地为了遍历而遍历,这个过程中总是要做点其他事情。
中序遍历
在中序遍历中,我们递归使用中序遍历访问左子树,然后访问根节点,最后再递归使用中序遍历访问右子树。
def inorder(tree):
if tree != None:
inorder(tree.getLeftChild())
print(tree.getRootVal())
inorder(tree.getRightChild())
当我们对一个解析树作中序遍历时,得到表达式的原来形式,没有任何括号。我们尝试修改中序遍历的算法使我们得到全括号表达式。只要做如下修改:在递归访问左子树之前输出左括号,然后在访问右子树之后输出右括号。
def printexp(tree):
sVal = ""
if tree:
sVal = '(' + printexp(tree.getLeftChild())
sVal = sVal + str(tree.getRootVal())
sVal = sVal + printexp(tree.getRightChild())+')'
return sVal
后序遍历
在后序遍历中,我们先递归使用后序遍历访问左子树和右子树,最后访问根节点。
def postorder(tree):
if tree != None:
postorder(tree.getLeftChild())
postorder(tree.getRightChild())
print(tree.getRootVal())
参考:Python数据结构
https://facert.gitbooks.io/python-data-structure-cn/