美文网首页金融风险管理
数据结构与算法_二叉树与二叉堆的Python实现

数据结构与算法_二叉树与二叉堆的Python实现

作者: 柳誉鸣 | 来源:发表于2020-05-10 22:35 被阅读0次

    数据结构和算法是计算机技术的基本功之一,北京大学的课程深入浅出,使用Python作为载体简化了编程难度。最近浏览了59-67,主要内容是树结构的介绍,二叉树结构及其实现(含完全二叉树定义与实现问题),二叉堆结构及其实现。并且以语法解析树为例探讨了树结构的应用。

    非线性数据结构-树

    树在计算机科学的各个领域中被广泛应用-操作系统,图形学,数据库系统,计算机网络。生物分类树:层次化-越接近顶层越普遍,越接近底层越独特。一个节点的子节点与另一个节点的子节点相互之间是隔离、独立的,如不同对象共同的属性。HTML文档的嵌套标记就是树结构的应用,域名体系也是树结构的应用。

    树结构相关术语

    节点Node:使用键值作为节点的名称,Node还保存额外数据项
    边Edge:另一个基础部分,每条边恰好连接两个节点,表示节点之间具有关联,边具有出入方向
    每个节点(除根节点)恰有一条来自另一节点的入边
    每个节点可以有多条连到其他节点的出边
    根Root:
    路径Path:
    子节点和父节点:入边均来自于同一个节点的若干节点,成为这个节点的子节点。
    兄弟节点Sibling:同一个父节点的节点之间
    子树Subtree:一个节点和其所有子孙节点,以及相关边的集合
    层级:从根节点到某节点边的数量
    树中所有节点最大层级成为树的高度
    定义1:树由若干节点以及两两连接的边组成
    定义2:树是-空集;或者根节点及0或多个子树构成,每个子数的根到根节点具有边相连

    树的嵌套列表实现

    首先使用列表类型实现二叉树类型
    用递归的嵌套列表实现二叉树,由具有3个元素的列表实现:第1个元素为根节点的值,第2个元素是左子树,第三个元素是右子树-形如【root,left,right】
    嵌套列表法:
    myTree=['a',['b'],['c',['d'],['e']]]
    子树的结构与树相同,是一种递归数据结构
    很容易扩展成多叉树,只需要增加列表元素即可

    """
    定义一系列函数来辅助操作嵌套列表
    BinaryTree-创建仅有根节点的二叉树
    inserLeft-将新节点插入树中作为直接的左右节点
    get/setRootVal-取得返回根节点
    getLeft/RightChild-返回左右子树
    """

    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)
    

    节点链接法实现二叉树

    每个节点保存根节点的数据项,以及指向左右子树的链接
    定义一个binaryTree类-成员key保存根节点数据项,成员left/right保存指向左右子树的引用

    class BinaryTree:
        def __init__(self,rootObj):
            self.key=rootObj
            self.rightChild=None#BinaryTree对象的引用
            self.leftChild=None
        
        def insertLeft(self,newNode):
            if self.leftChild==None:
                self.leftChild=BinaryTree(newNode)
            else:
                t=BinaryTree(newNode)
                t.leftChild=sekf.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 getRootVal(self):
            return self.key
    
        def setRootVal(self,Obj):
            self.key=Obj
        
        def getLeftChild(self):
            return self.leftChild
        
        def getRightChild(self):
            return self.rightChild
    
    r=BinaryTree('a')
    r.insertLeft('b')
    r.getLeftChild().setRootVal('c')
    

    树结构的应用-语法树

    将树用于表示语言中句子,可以分析句子的各种语法成分,对句子的各种成分进行处理
    程序设计语言的编译-从语法树生成目标代码的编译过程
    表达式表示为树结构,操作数叶节点,操作符内部节点,越底层的表达谁计算优先级越高
    用全括号表达式构建表达式解析树
    利用表达式解析树对表达式求职
    从表达式解析树恢复原表达式的字符串形式

    首先,全括号表达式要分解为单词Token列表,其单词分为括号”()“,操作符”+-/*“和操作数”0-9“这几类
    创建流程,当前节点的移动-遇到左括号,创建左子节点,当前节点下降,右括号,上升到父节点
    操作符-当前节点值设为操作符,创建右子节点并移动当前节点至右节点
    """
    创建左右子树-insetLeft/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)#stack中的tree类值会随之改变
                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
    

    对此二叉树求值,使用递归算法,定义求值递归函数evaluate
    调用自身,左右子树求值,父节点操作符
    """
    Python operator库,将操作符引入为函数对象,增加代码可读性
    """

    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()
    

    对此二叉树遍历
    以递归的方式访问树。1 前序遍历 preorder:先访问根节点,再递归地前序访问左子树
    最后前序访问右子树
    2中序遍历,递归地访问左子树-根节点-右子树
    后续遍历-递归访问左子树-右子树-根节点 postorder

    def preorder(tree):
        if tree:
            print(tree.getRootVal())
            preorder(tree.getLeftChild())
            preorder(tree.getRightChild())
    

    也可以在BinaryTree类中实现前序遍历算法

    def preorder(self):
        print(self.key)
        if self.leftChild:
            self.leftChild.predorder()
        if self.rightChild:
            self.rightChild.preorder()
    

    中序遍历递归算法生成全括号中缀表达式

    def printexp(tree):
        sVal=""
        if tree:
            sVal='('+printexp(tree.getLeftChild())
            sVal=sVal+str(tree.getRootVal())
            sVal=sVal+printexp(tree.getRightChild())+')'
        return sVal
    

    队列的变体-优先队列Priority Queue

    VIP客户插队,操作系统中关键任务优先等
    优先队列的出队和队列一样从队首出队,但在优先队列内部,数据项的次序由优先级决定
    因此,优先队列的重点在于入队的时候确定正确的顺序,O(n)?
    使用二叉堆数据结构是一种经典方案-将入队出队都保持在O(log n)
    二叉堆逻辑上像二叉树,但却是用非嵌套的列表来实现的
    """
    定义二叉堆对象的操作
    BinaryHeap()空对象创建
    insert(k)新key插入堆中
    findMin()返回堆中最小项,最小项仍保留在堆中
    delMin()返回堆中最小项并删除之
    isEmpty()
    size()
    buildHeap(list)从一个key列表创建新堆
    为了使堆操作能保持在对数水平上,就必须采用二叉树结构,且二叉树最好是平衡的-左右子树拥有相同数量的节点
    定义【完全二叉树】:叶节点最多出现在最底层和次底层,而且最底层的叶节点都连续集中在最左边,每个内部节点都有两个子节点,最多可有1个节点例外
    使用非嵌套列表实现,节点下标p,左子节点2p,右子节点2p+1,父节点p//2[1开始的下标]。
    因此,任何一个节点,父节点的key值小于其key值,路径皆为已排序数列。这就是堆次序。
    """

    二叉堆操作的实现

    class BinHeap:
        def __init__(self):
            self.heapList=[0]
            self.currentSize=0
    
    #新key加入列表末尾,会影响路径上节点-上浮直到正确位置
        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)
        
    #delMin()将最后一个节点移动到根节点-再调整顺序,保持完全二叉树的性质-下沉方法
    #左还是右?找更小的。
        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
    
    #buildHeap(list)逐个insert的话O(nlogn),用下沉法可以O(n)
        def buildHeap(self,alist):
            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)
    

    相关文章

      网友评论

        本文标题:数据结构与算法_二叉树与二叉堆的Python实现

        本文链接:https://www.haomeiwen.com/subject/koxenhtx.html