文章目录
树
树是一种非线性数据结构。
在树中,每个节点都含有自己的数值,以及与之相连的子节点,连接节点的线叫做相连线(edge)。如下图所示,A是根节点(root),也是B和C的父节点(parent node),也就是说B、C都是A的子节点(child node)。同理,B是D和E的父节点,以此类推。要注意H、I、J、F、G都是尾节点(leaf node),因为它们位于树的最底部,没有任何子节点。
树的特点:
- 每个节点都只有有限个子节点或无子节点;
- 没有父节点的节点称为根节点;
- 每一个非根节点有且只有一个父节点;
- 除了根节点外,每个子节点可以分为多个不相交的子树;
- 树里面没有环路(cycle)
相关术语
- 节点Node:组成树的基本部分;每个节点具有名称,或键值,节点还可以保存额外数据项,数据项根据不同的应用而变;
- 边Edge:每条边恰好连接两个节点,表示节点之间具有关联,边具有出入方向;
- 根Root:树中唯一一个没有入边的节点;
- 路径Path:由边依次连接在一起的节点的有序列表;
树的种类:
- 二叉树:每个节点最多含有两个子树的树称为二叉树;
- 完全二叉树:对于一棵二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树;
- 满二叉树:所有叶节点都在最底层的完全二叉树;
- 平衡二叉树(AVL树):当且仅当任何节点的两棵子树的高度差不大于1的二叉树;
- 排序二叉树(二叉查找树(英语:Binary Search Tree)):也称二叉搜索树、有序二叉树;
- B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个子树。
嵌套列表法实现
Python List来实现二叉树数据结构,[root, left, right]。
嵌套列表法优点:
子树的结构与树相同,是一种递归数据结构;
很容易扩展到多叉树,仅需要增加列表元素即可;
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)
print("r = BinaryTree(3): ",r)
insertLeft(r,4)
print("insertLeft(r,4): ",r)
insertLeft(r,5)
print("insertLeft(r,5): ",r)
insertRight(r,6)
print("insertRight(r,6): ",r)
insertRight(r,7)
print("insertRight(r,7): ",r)
l = getLeftChild(r)
print("l = getLeftChild(r): ",l)
setRootVal(l,9)
print("setRootVal(l,9): ",r)
insertLeft(l,11)
print("insertLeft(l,11): ",r)
print("getRightChild(getRightChild(r)): ",getRightChild(getRightChild(r)))
输出结果:
r = BinaryTree(3): [3, [], []]
insertLeft(r,4): [3, [4, [], []], []]
insertLeft(r,5): [3, [5, [4, [], []], []], []]
insertRight(r,6): [3, [5, [4, [], []], []], [6, [], []]]
insertRight(r,7): [3, [5, [4, [], []], []], [7, [], [6, [], []]]]
l = getLeftChild(r): [5, [4, [], []], []]
setRootVal(l,9): [3, [9, [4, [], []], []], [7, [], [6, [], []]]]
insertLeft(l,11): [3, [9, [11, [4, [], []], []], []], [7, [], [6, [], []]]]
getRightChild(getRightChild(r)): [6, [], []]
节点连接法
每个节点保存根节点的数据项,以及指向左右子树的链接。
class BinaryTree:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
def insertLeft(self,newNode):
if self.left == None:
self.left = BinaryTree(newNode)
else:
t = BinaryTree(newNode)
t.left = self.left
self.left = t
def insertRight(self,newNode):
if self.right == None:
self.right = BinaryTree(newNode)
else:
t = BinaryTree(newNode)
t.right = self.right
self.right = t
def getRightChild(self):
return self.right
def getLeftChild(self):
return self.left
def setRootVal(self,val):
self.val = val
def getRootVal(self):
return self.val
r = BinaryTree('a')
r.insertLeft('b')
r.insertRight('c')
r.getRightChild().setRootVal('hello')
r.getLeftChild().insertRight('d')
树的应用
表达式解析树
规则:从左到右扫描全括号表达式的每个单词,依据规则建立解析数。
- 如果当前单词是“( ”:为当前节点添加一个新节点作为其左子节点,当前节点下降为这个新节点;
- 如果当前单词是操作符“+,-,*,/ ”:将当前节点的值设为此符号,为当前节点添加一个新节点作为其右子节点,当前节点下降为这个新节点;
- 如果当前单词是操作数:将当前节点的值设为此数,当前节点上升到父节点;
- 如果当前单词是“ )”:则当钱节点上升到父节点;
建立表达式解析树:
- 创建左右子树可以调用insertLeft/Right;
- 当前节点设置值,可以调用setRootVal;
- 下降到左右子树可调用getLeft/RightChild;
- 上升到父节点,我们需要用一个栈来记录跟踪父节点;
当前节点下降时,将下降前的节点push入栈,当前节点需要上升到父节点时,上升到pop出栈的节点即可。
def buildParseTree(fpexp):
fplist = fpexp.split()
pStack = Stack()
eTree = BinaryTree('')
pStack.push(eTree)
curTree = eTree
for i in fplist:
if i == "(":
curTree.insertLeft('')
pStack.push(curTree)
curTree = curTree.getLeftChild()
elif i not in ['+','-','*','/',')']:
curTree.setRootVal(int(i))
parent = pStack.pop()
curTree = parent
elif i in ['+','-','*','/']:
curTree.setRootVal(i)
curTree.insertRight('')
pStack.push(curTree)
curTree = curTree.getRightChild()
elif i == ')':
curTree = pStack.pop()
else:
raise ValueError
return eTree
buildParseTree("( 2 + ( 3 * 5 ) )")
利用表达式解析树求值
- 基本结束条件: 叶节点是最简单的子树,没有左右子节点,其根节点的数据项即为子表达式树的值;
- 缩小规模: 将表达式树分为左子树、右子树,即为缩小规模;
- 调用自身: 分别调用evaluate计算左子树和右子树的值,然后将左右子树的值依根节点的操作符进行计算,从而得到表达式的值;
import operator
op = operator.add
n = op(1,2)
print(n)
输出:2
import operator
def evaluate(parseTree):
ops = {'+':operator.add,'-':operator.sub,'*':operator.mul,'/':operator.truediv}
leftC = parseTree.getLeftChild()
rightC = parseTree.getRightChild()
if leftC and rightC:
fn = ops[parseTree.getRootVal()]
return fn(evaluate(leftC),evaluate(rightC))
else:
return parseTree.getRootVal()
树的遍历
前序遍历 Preorder Traversal
在前序遍历中,先访问节点自己,然后访问左子树,最后再访问右子树,对于每个节点迭代此操作:
def pre_order(tree):
if tree:
print(tree.getRootVal())
pre_order(tree.getLeftChild())
pre_order(tree.getRightChild())
中序遍历 Inorder Traversal
在中序遍历中,先访问左子树上的节点,再访问自己,最后再访问右子树上的节点:
def in_order(tree):
if tree != None:
in_order(tree.getLeftChild())
print(tree.getRootVal())
in_order(tree.getRightChild())
后序遍历 Postorder Traversal
在后序遍历中,先访问左右子树,最后再访问自己:
def post_order(tree):
if tree != None:
post_order(tree.getLeftChild())
post_order(tree.getRightChild())
print(tree.getRootVal())
利用后序遍历重写表达式求值:
def post_order_eval(tree):
ops = {'+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv}
res1= res2 = None
if tree:
res1 = post_order_eval(tree.getLeftChild())
res2 = post_order_eval(tree.getRightChild())
if res1 and res2:
return ops[tree.getRootVal()](res1,res2)
else:
return tree.getRootVal()
print(post_order_eval(parseTree))