树和树算法(2)

1、树的属性

  • 树是分层的
  • 树的一个节点的所有子节点独立于另一个节点的所有子节点。
  • 每个叶节点是唯一的。
  • 可以将树的整个部分(子树)移动到树中的不同位置,而不影响层次结构的较低级别。

2、基本概念

  • 节点:树的基本组成部分,可以有一个名称,称为“键”。节点也可以有附加信息,称为“有效载荷”
  • :树的另一个基本组成部分。边连接两个节点以显示它们之间存在关系。
  • :树中唯一没有传入边的节点。
  • 路径:由边连接节点的有序列表。
  • 子节点:具有来自相同传入边的节点的集合称为该节点的子节点。
  • 父节点:具有和它相同传入边的所连接的节点。
  • 兄弟:同一个父节点的所有子节点称为兄弟节点。
  • 子树:子树是由父节点和该父节点的所有后代组成的一组节点和边。
  • 叶节点:是没有子节点的节点。
  • 层数: 节点n的层数为从根节点到该节点所经过的分支数目。根节点的层数为0.
  • 高度: 树中任意节点嘴的的层数。

2、使用列表构造树。

myTree = ['a', ['b', ['d',[],[]], ['e',[],[]] ], ['c', ['f',[],[]], []] ]
print(myTree)
print('left subtree = ', myTree[1])
print('root = ', myTree[0])
print('right subtree = ', myTree[2])
['a', ['b', ['d', [], []], ['e', [], []]], ['c', ['f', [], []], []]]
left subtree =  ['b', ['d', [], []], ['e', [], []]]
root =  a
right subtree =  ['c', ['f', [], []], []]
myTree[1][1]
['d', [], []]
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]
tree = binaryTree(0)
print(tree)
insertLeft(tree,1)
print(tree)
insertRight(tree, 2)
print(tree)
print(insertLeft(tree, 3))
[0, [], []]
[0, [1, [], []], []]
[0, [1, [], []], [2, [], []]]
[0, [3, [1, [], []], []], [2, [], []]]
  • 可见上面的代码在插入子树的时候均是插在根节点下,如果根节点下原来有子树,则将原来的子树移动到新插入的子树的下面。

3、节点表示

  • 第二种表示树的方法是使用节点和引用。
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
        
    def getRootVal(self):
        return self.key
    
    def preorder(self):
        '''前序遍历作为类的方法'''
        print(self.key)
        if self.leftChild:
            self.leftChild.preorder()
        if self.rightChild:
            self.rightChild.preorder()
r = BinaryTree('a')
print(r.getRootVal(), r.getLeftChild(), r.getRightChild())

r.insertLeft('b')
print(r.getRootVal(),r.getLeftChild().getRootVal(), r.getRightChild() )

r.insertRight('c')
print(r.getRootVal(),r.getLeftChild().getRootVal(), r.getRightChild().getRootVal())

r.getRightChild().setRootVal('hello')
print(r.getRightChild().getRootVal())
a None None
a b None
a b c
hello

6、使用树实现数学运算

  • 如果当前符号是 ‘(’ ,添加一个新节点作为当前节点的左子节点,并下降到左子节点。
  • 如果当前符号在列表 [’+’,’ - ‘,’/’,’*’] 中,请将当前节点的根值设置为由当前符号表示的运算符。 添加一个新节点作为当前节点的右子节点,并下降到右子节点。
  • 如果当前符号是数字,请将当前节点的根值设置为该数字并返回到父节点。
  • 如果当前令牌是 ‘)’ ,则转到当前节点的父节点
def buildParseTree(fpexp):
    fplist = fpexp.split()
    pStack = [] # 使用列表代替栈,书上使用的栈
    eTree = BinaryTree('')
    pStack.append(eTree)
    currentTree = eTree
    for i in fplist:
        if i == '(':
            currentTree.insertLeft('')
            pStack.append(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.append(currentTree)
            currentTree = currentTree.getRightChild()
        elif i == ')':
            currentTree = pStack.pop()
        else:
            raise ValueError
    return eTree
pt = buildParseTree("( ( 10 + 5 ) * 3 )")
pt.getRootVal()
'*'
pt.getLeftChild().getLeftChild().getRootVal()
10
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()
evaluate(pt)
45

7、树的遍历:

  • 前序遍历:根左右
  • 中序遍历:左右根
  • 后序遍历:左右根
def preorder(tree):
    '''作为外部方法'''
    if tree:
        print(tree.getRootVal())
        preorder(tree.getLeftChild())
        preorder(tree.getRightChild())
        
def postorder(tree):
    '''作为外部方法'''
    if tree:        
        preorder(tree.getLeftChild())
        preorder(tree.getRightChild())
        print(tree.getRootVal())
        
def inorder(tree):
    '''作为外部方法'''
    if tree:        
        preorder(tree.getLeftChild())
        print(tree.getRootVal())
        preorder(tree.getRightChild())
        
preorder(pt)
*
+
10
5
3
postorder(pt)
+
10
5
3
*
inorder(pt)
+
10
5
*
3
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()
postordereval(pt)
45
def printexp(tree):
    '''利用中序遍历将树结构的表达式恢复为完全括号表达式'''
    sVal = ""
    if tree:
        sVal = '(' + printexp(tree.getLeftChild())
        sVal = sVal + str(tree.getRootVal())
        sVal = sVal + printexp(tree.getRightChild()) + ")"
    return sVal
printexp(pt)
'(((10)+(5))*(3))'

8、二叉堆实现优先队列

  • 优先级队列是队列的一种变种,队列中的项的逻辑顺序由他们的优先级确定,优先级高的项排在队列的前面,因此将一个新的项插入到优先级队列时,新项有可能会移动到前面。二叉堆允许在O(logn)中排队和取出队列。
  • 二叉堆有两个有趣变体:最小堆(键值越小优先级越高,越排在前面); 最大堆(键值越大优先级越高)
  • 主要的操作
    • BinaryHeap()创建新的空二叉堆
    • insert(k) 向堆中插入一个新的项
    • findMin()返回具有最小键值的项
    • delMin()返回拥有最小键值的项,并将其从堆中删除。
    • isEmpty()判断二叉堆是否为空
    • size()返回堆中项的个数
    • buildHeap()从键列表创建一个新的堆。

8.1、实现最小二叉堆

  • 完整二叉树:是一个树中,其每层都有其所有的节点,除了树的最底层,从左到右填充。
  • 父节点的索引为p,则左右子节点的索引为2p和2p+1.
  • 堆的排序属性:在堆中,对具有父p的每个节点x, p中的键小于或等于x中的键。
  • 堆是一个完全二叉树,因此超过中间点的任何节点都是树的叶节点。
class BinHeap:
    def __init__(self):
        self.heapList = [0]
        self.currentSize = 0
    
    def insert(self, k ):
        self.heapList.append(k)
        self.currentSize  += 1
        self.percUp(self.currentSize)
        
    def percUp(self, i):
        '''当前节点的父节点可以通过当前节点的索引除以2来计算'''
        while i // 2 > 0:
            if self.heapList[i] < self.heapList[i//2]:
                temp = self.heapList[i//2]
                self.heapList[i // 2] = self.heapList[i]
                slef.heapList[i] = temp
            i = i // 2
    
    def percDown(self, i):
        while (i * 2) <= self.currentSize:
            mc = self.minChild(i) # mc 是索引为i的父节点的子节点的索引。
            if self.heapList[i] > self.heapList[mc]:
                temp = self.heapList[i]
                self.heapList[i] = self.heapList[mc]
                self.heapList[mc] = temp
            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 -= 1
        self.heapList.pop()
        self.percDown(1)
        return retval
    
    def buildHeap(self, alist):
        i = len(alist) // 2
        self.currentSize = len(alist)
        self.heapList = [0] + alist[:]
        while i > 0:
            self.percDown(i)
            i = i -1
            
    def isEmpty(self):
        return self.currentSize == 0
    def size(self):
        return self.currentSize
    def findMin(self):
        return self.heapList[1]
bh = BinHeap()
bh.buildHeap([9,5,6,2,3])
print(bh.findMin())
print(bh.delMin())
print(bh.findMin())
print(bh.delMin())
print(bh.delMin())
print(bh.isEmpty())
print(bh.size())
print(bh.delMin())
print(bh.delMin())
print(bh.isEmpty())
print(bh.size())
2
2
3
3
5
False
2
6
9
True
0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值