算法学习-二叉树基础

概念介绍

本博客在学习北京大学陈斌老师《数据结构与算法》MOOC课程中总结反思形成。

二叉树的鼎鼎大名人尽皆知,它整体的思维逻辑也符合人类整理归纳的习惯,因此在理解概念上的难度并不大。

树作为一种典型的“非线性结构”,具备两个显著的特征:

  • 分类体系是层次化的,比如文件系统、物种分类、HTML文档、域名体系等
  • 一个节点的子节点与另一个节点的子节点相互之间是隔离、独立的;
  • 每一个叶节点都具有唯一性,就比如“我爱学习算法”在不同语言中的表达,尽管都是“我爱学习算法”,但是因为属于不同的

语言体系,它仍然具备唯一性。

树作为一种简洁的表达方式,具备很多术语归纳如下:

image-20210927001739728

二叉树的实现

嵌套列表法

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]

链表实现法

class BinarryTree:
    def __init__(self,rootObj):
        self.key = rootObj
        self.leftChild = None
        self.rightChild = None

    def insertLeft(self,newNode):
        if self.leftChild == None:
            self.leftChild = BinarryTree(newNode)

        else:
            t = BinarryTree(newNode)
            t.leftChild = self.leftChild
            self.leftChild = t

    def insertRight(self,newNode):
        if self.rightChild == None:
            self.rightChild = BinarryTree(newNode)
        else:
            t = BinarryTree(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

应用举例

表达式解析树

项目需求:
从全括号表达式构建表达式解析树,利用表达式解析树对表达式求值,从表达式解析树恢复原表达式的字符串形式

梳理逻辑:
如果当前单词是“(”:为当前节点添加一个新节点作为其左子节点,当前节点下降为这个新节点
如果当前单词是操作符“+,-,*,/”:将当前节点的值设为此符号,为当前节点添加一个新节点作为其右子节点,当前节点下降为这个新节点
如果当前单词是操作数:将当前节点的值设为此数,当前节点上升到父节点
如果当前单词是“)”:则当前节点上升到父节点

要点:当前节点的跟踪
基本操作:

创建左右子树=> 调用insertLeft/Right
当前节点设置值=>调用setRootval
下降到左右子树=>调用getLeft/RightChild
用一个栈=>记录跟踪父节点
当前节点下降时,将下降前的节点push入栈;当前节点需要上升到父节点时,上升到pop出栈的节点

老师实现

from pythonds.basic.stack import Stack
import operator

class BinarryTree:
    def __init__(self, rootObj):
        self.key = rootObj
        self.leftChild = None
        self.rightChild = None

    def look(self):
        print(self.key, self.leftChild, self.rightChild)

    def insertLeft(self, newNode):
        if self.leftChild == None:
            self.leftChild = BinarryTree(newNode)

        else:
            t = BinarryTree(newNode)
            t.leftChild = self.leftChild
            self.leftChild = t

    def insertRight(self, newNode):
        if self.rightChild == None:
            self.rightChild = BinarryTree(newNode)
        else:
            t = BinarryTree(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 buildParseTree(fplist):
    pStack = Stack()
    eTree = BinarryTree('')
    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

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

自己实现

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]


def buildParseTree(fplist):
    pStack = Stack()
    eTree = BinaryTree('')
    print(eTree)
    pStack.push(eTree)
    currentTree = eTree

    for i in fplist:
        if i == '(':
            insertLeft(currentTree , '')
            pStack.push(currentTree )
            currentTree = getLeftChild(currentTree)
            print('current:',currentTree)

        elif i not in ['+', '-', '*', '/', ')']:
            setRootval(currentTree, int(i))
            parent = pStack.pop()
            currentTree = parent
            print('current:', currentTree)

        elif i in ['+', '-', '*', '/']:
            setRootval(currentTree, i)
            insertRight(currentTree, '')
            pStack.push(currentTree)
            currentTree = getRightChild(currentTree)
            print('current:', currentTree)
        elif i == ')':
            currentTree = pStack.pop()
        else:
            raise ValueError
        print(i,':',eTree)
    return eTree

调试过程

自己的实现方式主要采用了列表实现方法,方便打印出中间值去理解二叉树的变化过程,测试方案是(3+(4*5))。

打印结果如下:

['', [], []]
current: ['', [], []]
( : ['', ['', [], []], []]
current: ['', [3, [], []], []]
3 : ['', [3, [], []], []]
current: ['', [], []]
+ : ['+', [3, [], []], ['', [], []]]
current: ['', [], []]
( : ['+', [3, [], []], ['', ['', [], []], []]]
current: ['', [4, [], []], []]
4 : ['+', [3, [], []], ['', [4, [], []], []]]
current: ['', [], []]
* : ['+', [3, [], []], ['*', [4, [], []], ['', [], []]]]
current: ['*', [4, [], []], [5, [], []]]
5 : ['+', [3, [], []], ['*', [4, [], []], [5, [], []]]]
) : ['+', [3, [], []], ['*', [4, [], []], [5, [], []]]]
) : ['+', [3, [], []], ['*', [4, [], []], [5, [], []]]]

image-20210926233904166

心得体会

  • 整体实现过程中,二叉树的变化过程和理解有难度。

  • 个人认为应该需要重点关注“key”值,关注当前节点的变化去帮助理解。

  • 在代码书写过程中,可以手动 画出栈对于节点的管理帮助加深理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

儒雅的钓翁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值