主要记录:树和树的算法
内容来源:书籍、网络和个人总结
1. 理论
1.1 树的由来
1.2 树的三个属性
- 层次性,即树是按层级构建的,越笼统就越靠近顶部,越具体则越靠近底部。
- 一个节点的所有子节点与另一个节点的所有子节点无关。
- 叶子节点都是独一无二的。
1.3 术语
节点
节点是树的基础部分。它可以有自己的名字,我们称作“键”。节点也可以带有附加信息, 我们称作“有效载荷”。
边
边是树的另一个基础部分。两个节点通过一条边相连,表示它们之间存在关系。
路径
路径是由边连接的有序节点列表。
层数
节点 n 的层数是从根节点到 n 的唯一路径长度。(根节点的层数是0。)
高度
树的高度是其中节点层数的最大值。
其它:略
1.4 定义
版本一(节点和边)
树由节点及连接节点的边构成。树有以下属性:
- 有一个根节点;
- 除根节点外,其他每个节点都与其唯一的父节点相连;
- 从根节点到其他每个节点都有且仅有一条路径;
- 如果每个节点最多有两个子节点,我们就称这样的树为二叉树。
定义一示意图:
(由节点和边构成的树)
版本二(涉及递归)
一棵树要么为空,要么由一个根节点和零棵或多棵子树构成,子树本身也是一棵树。 每棵子树的根节点通过一条边连到父树的根节点。
定义二示意图:
(由递归定义的树)
2. 实现
实现树的关键在于选择一个好的内部存储技巧。Python 提供两种有意思的方式,第一种称作“列表之列表”,第二种称作“节点与引用”。
2.1 用列表实现树——“列表之列表”
在“列表之列表”的树 中,我们将根节点的值作为列表的第一个元素;第二个元素是代表左子树的列表;第三个元素是 代表右子树的列表。
图示

代码示例
myTree = ['a', #root
['b', #left subtree
['d', [], []],
['e', [], []] ],
['c', #right subtree
['f', [], []],
[] ]
]
优点
“列表之列表”表示法的优点:
- 子树的结构与树相同,是一种递归数据结构
- 很容易扩展到多叉树,仅需要增加列表元素即可(不局限于二叉树)
python 代码实现
通过定义一系列函数来辅助操作嵌套列表:
- BinaryTree创建仅有根节点的二叉树
- insertLeft/insertRight将新节点插入树中作
为其直接的左/右子节点 - 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) # 创建根节点为3的树
insertLeft(r, 4)
print(r)
insertLeft(r, 5)
print(r)
insertRight(r, 6)
print(r)
insertRight(r, 7)
print(r)
l = getLeftChild(r)
print(l)
setRootVal(l, 9)
print(r)
insertLeft(l, 11)
print(r)
print(getRightChild(getRightChild(r)))
2.2 用类和引用实现树
树的第二种表示法是利用节点与引用。我们将定义一个类,其中有根节点和左右子树的属性。
这种表示法遵循面向对象编程范式。
图解

Python 实现
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.leftChild == None:
self.leftChild = 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())
3. 应用
解析树(语法树)
树的遍历
对所有节点的访问称为“遍历”,共有 3 种遍历方式,分别 为前序遍历、中序遍历和后序遍历。
前序遍历(根左右)
在前序遍历中,先访问根节点,然后递归地前序遍历左子树,最后递归地前序遍历右子树。
# 前序
def preorder(tree):
if tree:
print(tree.getRootVal())
preorder(tree.getLeftChild())
preorder(tree.getRightChild())
中序遍历(左根右)
在中序遍历中,先递归地中序遍历左子树,然后访问根节点,最后递归地中序遍历右子树。
# 中序
def inorder(tree):
if tree:
preoder(tree.getLeftChild())
print(tree.getRootVal())
preoder(tree.getRightChild())
后序遍历 (左右根)
在后序遍历中,先递归地后序遍历左子树,然后递归地后序遍历右子树,最后访问根节点。
# 后序
def postorder(tree):
if tree:
postorder(tree.getLeftChild())
postorder(tree.getRightChild())
print(tree.getRootVal())
关于”前”中“后”的简单、本质的理解记忆方式:按照根节点的出现的先后顺序
4.二叉堆
二叉堆的来龙去脉
几个概念
优先列队
列队的重要变体,或说是特殊的“列队”,取出元素的顺序是依照元素的优先权(关键字)大小,而不是元素进入入列队的先后顺序。
平衡二叉树(AVL树)
平衡的二叉树是指,其根节点的左右子树含有数量大致相等 的节点。
完全二叉树
在完全二叉树中,除了最底层,其他每一层的节点都是满的。
二叉堆的由来
针对优先列队问题,考虑使用二叉树来存储结构的思考而来。
二叉堆能够将优先队列的入队和出队复杂度都保 持在O(log n)。
堆的两个特性
-
结构性:用数组表示的完全二叉树
注意两点:
除了最底层,其他每一层的节点都是满的;
对于在列表中处于位置 p 的节点来说,它的左子 节点正好处于位置 2p;同理,右子节点处于位置 2p+1 -
有序性:任意一节点的关键字是其子树所有节点的最大值(最小值)——最大堆和最小堆。
注意一点:对于堆中任意元素 x 及其
父元素 p,p 都不大于 x。
复杂度
可以采用二分搜索算法找到下一个元素的正确插 入位置,时间复杂度约为 O(log n) 。但是,为了在列表的中部插入元素,可能需要移动其他元素, 以为新元素腾出空间,这种操作的时间复杂度为 O(n) 。
因此,将 n 个元素插入堆中的操作为O(n log n) 。然而,如果从完整的列表开始,构建整个堆只需 O(n) 。
二叉堆操作的实现
5.二叉搜索树
——新的ADT Map实施方案
二叉搜索性
二叉搜索树依赖的一个性质:小于父节点的都在左子树中,大于父节点的键则都在右子树中。
代码实现
224



被折叠的 条评论
为什么被折叠?



