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

数据结构和算法是计算机技术的基本功之一,北京大学的课程深入浅出,使用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]

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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值