树
树结构相关术语
节点Node:组成树的基本部分
边Edge:边是组成树的另一个基本部分
- 每条边恰好连接两个节点,表示节点之间具有关联,边具有出入方向;
- 每个节点(除根节点)恰有一条来自另一节点的入边;
- 每个节点可以有多条连到其它节点的出边。
根Root:树中唯一一个没有入边的节点
路径Path:由边依次连接在一起的节点的有序列表
- 如:HTML->BODY->UL->LI,是一条路径
子节点Children:入边均来自于同一个节点的若干节点,称为这个节点的子节点
父节点Parent:一个节点是其所有出边所连接节点的父节点
兄弟节点Sibling:具有同一个父节点的节点之间称为兄弟节点
子树Subtree:一个节点和其所有子孙节点,以及相关边的集合
叶节点Leaf:没有子节点的节点称为叶节点
层级Level:从根节点开始到达一个节点的路径,所包含的边的数量,称为这个节点的层级。
高度:树中所有节点的最大层级称为树的高度
树的定义:
1.树由若干节点,以及两两连接节点的边组成,并有如下性质:
- 其中一个节点被设定为根 ;
- 每个节点n( 除根节点) ,都恰连接 一
条来自节点p 的边,p 是n 的父节点; - 每个节点从根开始的路径是 唯一 的 ,如果每个节点最多有两个子节点, 这样的树称为 “二叉树“
2.空集;或者由根节点及0 或多个子树构成(其中子树也是树),每个子树的根到根节点具有边相连。
树的嵌套列表实现
递归的嵌套列表实现二叉树,由具有3个元素的列表实现:
- 第1 个元素为根节点的值;
- 第2 个元素是左子树(所以也是一个列表);
- 第3 个元素是右子树(所以也是一个列表)。
[root,left,right]
# myTree = ['a', ['b',['d',[],[]],[e,[],[]]], ['c',['f',[],[]],[]]]
"""
根是root[0] ,左子树root[1] ,右子树root[2]
"""
def BinaryTree(r): # 创建仅有根节点的二叉树
return [r, [], []]
def insertLeft(root, newBranch):
t = root.pop(1) # 先把左子树pop出来
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)))
运行结果:
树的链表实现
每个节点保存根节点的数据项,以及指向左右子树的链接
'''
成员key 保存根节点数据项
成员left/rightChild 则保存指向左/ 右子树的
引用(同样是BinaryTree 对象)
'''
class BinaryTree:
def __init__(self, rootObject):
self.key = rootObject
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
def getRootVal(self):
return self.key
建立树:
r = BinaryTree('a')
r.insertLeft('b')
r.insertRight('c')
r.getLeftChild().insertRight('d')
树的应用:表达式解析
- 从全括号表达式构建表达式解析树
- 利用表达式解析树对表达式求值
- 从表达式解析树恢复原表达式的字符串形式
全括号表达式:(3+(4*5))分解为单词表[’(’, ‘3’, ‘+’, ‘(’, ‘4’, ‘*’, ‘5’,’)’, ‘)’]
创建表达式解析树过程:
- 创建 空树,当前节点为根节点
- 读入’(’ , 创建 了左子节点,当前节点 下降
读入’3’ ,当前节点 设置 为3 , 上升 到父节点
读入’+’ ,当前节点 设置 为+ , 创建 右子节点,当前节点
- 读入’(’ , 创建 左子节点,当前节点 下降
读入’4’ ,当前节点 设置 为4 , 上升 到父节点
读入’*’ ,当前节点 设置 为 * , 创建 右子节点,当前节点 下降
- 读入’5’ ,当前节点 设置 为5 , 上升 到父节点
读入’)’ , 上升 到父节点
读入’)’ ,再 上升
从左到右扫描全括号表达式的每个单词,依据规则建立解析树:
- 如果 当前单词 是"(" :为 当前节点 添加一个新节点作为其左子节点, 当前节点下降 为这个新节点
- 如果 当前单词 是 操作符"+,-,/,*" :将 当前节点的值设为此符号,为当前节点添加一个新节点作为其右子节点, 当前节点下降为这个新节点
- 如果 当前单词 是 操作数 :将当前节点的值设为此数, 当前节点上升 到父节点
- 如果 当前单词 是")" :则当前节点 上升到父节点
从全括号表达式构建表达式解析树:
from list.BinaryTree import BinaryTree
from list.Stack import Stack
'''
创建左右子树可调用insertLeft/Right
当前节点设置值,可以调用setRootVal
下降到左右子树可调用getLeft/RightChild
可以用一个栈来记录跟踪父节点
当前节点下降时,将下降前的节点push入栈
当前节点需要上升到父节点时,上升到pop出栈的节点即可!
'''
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
print(buildParseTree("( 3 + ( 4 * 5 ) )"))
利用表达式解析树对表达式求值:
思路:
求值函数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() r = buildParseTree("( 3 + ( 4 * 5 ) )") print(evaluate(r))
树的遍历
按照对节点访问次序的不同来区分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类中实现前序遍历:
'''
成员key 保存根节点数据项
成员left/rightChild 则保存指向左/ 右子树的
引用(同样是BinaryTree 对象)
'''
class BinaryTree:
def __init__(self, rootObject):
self.key = rootObject
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
def getRootVal(self):
return self.key
def preorder(self):
print(self.key)
if self.leftChild:
self.leftChild.preorder()
if self.rightChild:
self.rightChild.preorder()
采用后序遍历法重写表达式求值代码:
import operator
def postordereval(tree):
opers = {'+':operator.add,'-':operator.sub,"*":operator.mul,'/':operator.truediv}
res1 = None
res2 = None
if tree:
res1 = postordereval(tree.getLeftChild())
res2 = postordereval(tree.getRightChild())
if res1 and res2:
return opers[tree.getRootVal()](res1,res2)
else:
return tree.getRootVal()
r = buildParseTree("( 3 + ( 4 * 5 ) )")
print(postordereval(r))
采用中序遍历递归算法来生成全括号中缀表达式:
def printexp(tree):
sVal = ""
if tree:
sVal = '(' + printexp(tree.getLeftChild())
sVal = sVal +str(tree.getRootVal())
sVal = sVal + printexp(tree.getRightChild())+')'
return sVal
r = buildParseTree("( 3 + ( 4 * 5 ) )")
print(printexp(r)) # ((3)+((4)*(5)))
优先队列
优先队列的出队跟队列一样从队首出队
在优先队列内部,数据项的次序由“优先级”来确定:
- 高优先级 的数据项排在 队首 ,而低优先级的数据项则排在后面。
- 优先队列的 入队 操作比较 复杂 ,需要将数据项根据其优先级尽量挤到队列前方。
二叉堆
实现优先队列的经典方案是采用二叉堆数据结构
- 二叉堆能够将优先队列的 入队 和 出队 复杂度都保持在O(log n)
二叉堆逻辑结构上象二叉树,却是用非嵌套的列表来实现的!
最小key排在队首的称为“最小堆min heap”。反之,最大key 排在队首的是 “ 最大堆max heap
二叉堆Binary Heap实现优先队列:
class BinHeap:
def __init__(self):
self.heapList = [0] # 采用一个列表来保存堆数据
self.currentSize = 0
def percUp(self,i): # 节点上浮
while i//2 > 0:
if self.heapList[i] < self.heapList[i//2]:
tmp = self.heapList[i//2]
self.heapList[i//2] = self.heapList[i]
self.heapList[i] = tmp
i = i // 2
def insert(self,k):
self.heapList.append(k) # 添加到末尾
self.currentSize = self.currentSize + 1
self.percUp(self.currentSize)
def percDown(self,i): # 下沉
while(i * 2) <= self.currentSize: # 存在子节点
mc = self.minChild(i) # 找到最小的子节点
if self.heapList[i] > self.heapList[mc]: # 如果比子节点大,就交换位置
tmp = self.heapList[i]
self.heapList[i] = self.heapList[mc]
self.heapList[mc] = tmp
i = mc
def minChild(self,i):
if i * 2 + 1 > self.currentSize: # 如果只有一个子节点
return i * 2
else:
if self.heapList[i * 2] < self.heapList[i * 2 + 1]: # 否则,比较两个子节点的值
return i * 2
else:
return i * 2 + 1
def delMin(self): # 删除最小值即堆顶
retval = self.heapList[1]
self.heapList[1] = self.heapList[self.currentSize] # 将最后一个值即最大值放到堆顶
self.currentSize = self.currentSize - 1
self.heapList.pop() # 出栈最后一个值
self.percDown(1)
return retval
def buildHeap(self,alist): #“ 堆排序 ” 算法:O(nlog n)
i = len(alist) // 2 # 从最后节点的父节点开始因叶节点无需下沉
self.currentSize = len(alist)
self.heapList = [0] + alist[:]
print(len(self.heapList),i)
while(i > 0):
print(self.heapList,i)
self.percDown(i)
i = i-1
print(self.heapList,i)
b = BinHeap()
alist = [23,1,53,76,34,21,33,2,6,9,4]
b.buildHeap(alist)
运行结果:
insert操作图示:
delMin操作图示:
bulidHeap方法图示: