树相关算法及Python实现

Ⅰ目标

1.要理解树数据结构是什么,以及如何使用它。
2.查看树如何用于实现 map 数据结构。
3.使用列表实现树。
4.使用类和引用来实现树。
5.实现树作为递归数据结构。
6.使用堆实现优先级队列。

Ⅱ 树的相关概念

树在计算机科学的许多领域中使用,包括操作系统、图、数据库系统和计算机网络。
树数据结构与自然树有许多共同之处,它也具有根、分支、叶。而它们的区别在于树数据结构的根在顶部,其叶在底处。
节点
节点是树的基本组成部分。它可以有一个名称,我们称之为“键”(key)。节点还可能有其他信息。我们把这种附加信息称为“有效载荷”。虽然有效负载信息不是许多树算法的核心,但它在使用树的应用程序中通常是至关重要的。

边是树的另一个基本组成部分。一条边连接两个节点,表示它们之间存在关系。每个节点(根节点除外)都由来自另一个节点的一条传入边连接。每个节点可以有多条输出边。

树的根是树中唯一没有传入边的节点。
路径
路径是由边连接起来的有序节点列表。例如,Mammal → Carnivora → Felidae → Felis → Domestica是一条路径。
子节点
具有来自相同传入边的节点 c 的集合称为该节点的子节点。
父节点
所连接到的所有具有输出边的节点称为父节点。
兄弟节点
树中属于同一父节点的子节点称为兄弟节点。
子树
子树是由父节点和该父节点的所有后代组成的一组节点和边。
叶节点
叶节点是没有子节点的节点。
层数
节点 n n n 的层数为从根结点到该结点所经过的分支数目。根节点的层数为零。
高度
树的高度等于树中任何节点的最大层数。
下图举一个树的例子:
在这里插入图片描述

Ⅲ 树的定义

定义一
树由一组节点和一组连接节点的边组成。树具有以下属性:

  • 树的一个节点被指定为根节点。
  • 除了根节点之外,每个节点 n n n 通过另一节点 p p p 的边连接,其中 p p p n n n 的父节点。
  • 从根节点到每个节点都有唯一的路径。
  • 如果树中的每个节点最多有两个子节点,我们说该树是一个二叉树。

定义二
树要么是空的,要么由一个根和零个或多个子树组成,每个子树也是树。每个子树的根节点通过边连接到父树的根节点。

这个定义属于树的递归定义。下图说明了树的这种递归定义。使用树的递归定义,我们知道该树至少有四个节点,因为表示一个子树的每个三角形必须有一个根节点。 它可能有比这更多的节点。
在这里插入图片描述

Ⅳ 树的列表表示

我们可以使用以下函数来创建和操作二叉树:

  • BinaryTree() 创建二叉树的新实例。
  • get_left_child() 返回当前节点左子节点对应的二叉树。
  • get_right_child() 返回当前节点的右子节点对应的二叉树。
  • set_root_val(val) 在当前节点中存储val参数中的对象。
  • get_root_val() 返回存储在当前节点中的对象。
  • insert_left(val) 创建一个新的二叉树,并将其存储到当前节点的左子节点。
  • insert_right(val) 创建一个新的二叉树,并将其存储到当前节点的右子节点。

在列表树中,我们将根节点的值存储为列表的第一个元素。列表的第二个元素本身将是一个表示左子树的列表。列表的第三个元素将是表示右子树的另一个列表。看这样一个简单的树:
在这里插入图片描述
其python实现:

myTree = ['a',   # root
	['b', # left subtree
	['d',[],[]],
	['e',[],[]] ],
	['c', # right subtree
	['f',[],[]], [] ]
]

我们可以使用标准列表索引来访问列表的子树。树的根是 myTree[0] ,根的左子树是myTree[1] ,右子树是 myTree[2] 。一旦树被构建,我们可以访问根和左右子树。 该列表方法的一个非常好的属性是表示子树的列表的结构遵守树定义的结构; 结构本身是递归的。具有根值和两个空列表的子树是叶节点。列表方法的另一个很好的特性是它可以推广到一个有许多子树的树。在树超过二叉树的情况下,另一个子树是另一个列表。

myTree = ['a', ['b', ['d',[],[]], ['e',[],[]] ], ['c', ['f',[],[]], []] ]
print(myTree)
print('left subtree = ',myTree[1])
print('root = ',myTree[0])
print('right subtree = ',myTree[2])

通过提供一些函数来形式化树数据结构的定义,这些函数使我们可以轻松地将列表用作树。注意,我们不打算定义一个二叉树类。我们将编写的函数将帮助我们操作一个标准列表,就像处理一棵树一样。

def BinaryTree(r):
	return [r,[],[]]

BinaryTree 函数简单地构造一个具有根节点和两个子列表为空的列表。要将左子树添加到树的根,我们需要在根列表的第二个位置插入一个新的列表。我们必须小心。如果列表已经在第二个位置有东西,我们需要跟踪它,并沿着树向下把它作为我们添加的列表的左子节点。

def insertLeft(root,newBranch):
	t = root.pop(1)
	if len(t) > 1:
		root.insert(1,[newBranch,t,[]])
	else:
		root.insert(1,[newBranch,[],[]])
	return root

注意,要插入一个左子节点,我们首先获得与当前左子节点对应的(可能为空的)列表。然后我们添加新的左子树,添加旧的左子数作为新子节点的左子节点。这允许我们在任何位置将新节点拼接到树中。 insertRight 的代码与 insertLeft 类似:

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 BinaryTree:
	def __init__(self,rootObj):
		self.key = rootObj
		self.leftChild = None
		self.rightChild = None

创建BinaryTree 类的六个实例方法。

# 添加左子树
def insertLeft(self,newNode):
	if self.leftChild == None:
		self.leftChild = BinaryTree(newNode)
	else:
		t = BinaryTree(newNode)
		t.leftChild = self.leftChild
		self.leftChild = t

必须考虑两种插入情况。 第一种情况是没有左孩子节点。当没有左孩子节点时,只需向树中添加一个节点。 第二种情况是有左孩子节点。在第二种情况下,我们插入一个节点并将现有的子节点放到树中的下一个层。insertRight 的代码必须考虑一组同样的情况:

# 添加右子树
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

现在有了创建和操作二叉树的所有部分,使用它们来检查结构。我们使用节点 a作为根的简单树,并将节点 b 和 c 添加为子节点。完整代码如下:

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

r = BinaryTree('a')
print(r.getRootVal)
print(r.getLeftChild())
r.insertLeft('b')
print(r.getLeftChild())	
print(r.getLeftChild().getRootVal())
r.insertRight('c')
print(r.getRightChild())
print(r.getRightChild().getRootVal())
r.getRightChild().setRootVal('hello')
print(r.getRightChild().getRootVal())

Ⅵ 分析树

分析树可以用于表示诸如句子或数学表达式的真实世界构造。举两个例子:

例子1

在这里插入图片描述
上图展示了一个简单句子的层次结构。 将句子表示为树结构允许我们通过使用子树来处理句子的各个部分。

例子2

在这里插入图片描述
我们还可以表示诸如 ((7 + 3)*(5-2)) 数学表达式作为分析树。树的层次结构有助于我们了解整个表达式的求值顺序。在我们计算顶层乘法之前,我们必须计算子树中的加法和减法。作为左子树的加法结果为10。减法,即右子树,计算结果为3。使用树的层次结构,我们可以简单地用一个节点替换整个子树,一旦我们计算了表达式中这些子树。替换过程给出下面的简化树:
在这里插入图片描述

如何构建分析树?

构建分析树的第一步是将表达式字符串拆分成符号列表。有四种不同的符号要考虑:左括号,右括号,运算符和操作数。我们知道,每当我们读一个左括号,就意味着开始一个新的表达式,因此我们应该创建一个新的树来对应于该表达式。 相反,每当我们读一个右括号,就意味着完成了一个表达式。 另外,操作数是叶节点,也是它们的操作符的子节点。 最后,每个操作符都有一个左和右孩子。使用上面的信息,我们可以定义四个规则如下:

  • 如果当前符号是 ‘(’ ,添加一个新节点作为当前节点的左子节点,并下降到左子节点。
  • 如果当前符号在列表 [‘+’,’ - ‘,’/‘,’*'] 中,请将当前节点的根值设置为由当前符号
    表示的运算符。 添加一个新节点作为当前节点的右子节点,并下降到右子节点。
    *如果当前符号是数字,请将当前节点的根值设置为该数字并返回到父节点。
  • 如果当前符号是 ‘)’ ,则转到当前节点的父节点。

具体例子:(3+(4 * 5))
把这个表达式解析成下面的字符标记列表
[‘(’,‘3’,‘+’,‘(’,‘4’,‘*’,‘5’,‘)’,‘)’]。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上图展示了当每个新符号被处理时分析树的结构和内容。详细看一下这个过程:

a. 创建一个空树。
b. 读取 ( 作为第一个标记。按规则1,创建一个新节点作为根的左子节点。使当前节点到这个新子节点。
c. 读取 3 作为下一个符号。按照规则3,将当前节点的根值设置为3,使当前节点返回到父节点。
d. 读取 + 作为下一个符号。根据规则2,将当前节点的根值设置为+,并添加一个新节点作为右子节点。新的右子节点成为当前节点。
e. 读取 ( 作为下一个符号,按规则1,创建一个新节点作为当前节点的左子节点,新的左子节点成为当前节点。
f. 读取 4 作为下一个符号。根据规则3,将当前节点的值设置为 4。使当前节点返回到父节点。
g. 读取 作为下一个符号。根据规则2,将当前节点的根值设置为 ,并创建一个新的右子节点。新的右子节点成为当前节点。
h. 读取 5 作为下一个符号。根据规则3,将当前节点的根值设置为5。使当前节点返回到父节点。
i. 读取 ) 作为下一个符号。根据规则4,使当前节点返回到父节点。
j. 读取 ) 作为下一个符号。根据规则4,使当前节点返回到父节点 + 。没有+ 的父节点,所以我们完成创建。

从上面的例子,很明显,我们需要跟踪当前节点以及当前节点的父节点。树接口为我们提供了一种通过 getLeftChild 和 getRightChild 方法获取节点的子节点的方法,但是我们如何跟踪父节点呢?当我们遍历树时,保持跟踪父对象的简单解决方案是使用栈。每当我们想下降到当前节点的子节点,我们首先将当前节点入到栈上。当我们想要返回到当前节点的父节点时,我们将父节点从栈中弹出。使用上述规则,以及 Stack 和 BinaryTree 操作,我们现在可以编写一个Python 函数来创建一个分析树。

from pythonds.basic.stack import Stack
from pythonds.trees.binaryTree import BinaryTree

def buildParseTree(fpexp):
	fplist = fpexp.split()
	pStack = Stack()
	eTree = BinaryTree('')
	pStack.push(eTree)
	currentTree = eTree
	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

弓早早o_O

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

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

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

打赏作者

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

抵扣说明:

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

余额充值