数据结构与算法python北大版笔记 - 树

树结构相关术语

节点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操作图示:

图示1
图示2
图示3

delMin操作图示:

图示1
图示2

bulidHeap方法图示:

图示

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值