树的知识
什么是树
树是一种基本的非线性数据结构,在操作系统、图形学、数据库系统、计算机网络等领域被广泛使用。
跟自然界中的树一样,数据结构树也分为:根、枝、叶三个部分,一般数据结构的图示会把根放在上方,叶放在下方,就像一颗倒置的树,如下示例:
![image-20210423175442117](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210423175442117.png)
相关术语
节点(node)
节点是组成树的基本部分,每个节点具有不同的名称或“键值”属性。
除此之外,节点还可以保存额外数据项,数据项根据不同的应用而变化。
![image-20210423183240968](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210423183240968.png)
边(edge)
边是组成树的另一个基本部分,每条边恰好连接两个节点,表示节点之间具有关联。
除此之外,边还具有出入方向:
-
每个节点(除根节点)恰好有一条来自另一节点的入边。
-
每个节点可以有多条连到其它节点的出边。
![image-20210423183637054](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210423183637054.png)
根(root)
根是树中唯一一个没有入边的节点,即最顶端的节点。
![image-20210423183100473](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210423183100473.png)
路径(path)
路径是由边依次连接在一起的节点组成的有序列表,如:HTML->BODY->UL->LI就是一条路径。
![image-20210423183853742](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210423183853742.png)
子节点(children)
入边均来自于同一个节点的若干节点,称为这个节点的子节点。
![image-20210423182947004](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210423182947004.png)
父节点(parent)
一个节点是其所有出边所连接节点的父节点。
![image-20210423182906981](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210423182906981.png)
兄弟节点(sibling)
具有同一个父节点的节点之间称为兄弟节点.
![image-20210423182758607](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210423182758607.png)
子树(subtree)
子树是一个节点和其所有子孙节点,以及相关边的集合。
一个树中有多个子树,每个子树是独立的一颗树。
![image-20210423181607109](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210423181607109.png)
叶子节点(leaf)
没有子节点的节点称为叶节点。
![image-20210423182646416](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210423182646416.png)
层级(level)
根节点开始到达一个节点的路径,所包含的边的数量,称为这个节点的层级。
Root从0开始计数,也可以从1开始计数:
![image-20210423182551238](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210423182551238.png)
高度(height)
树中所有节点的最大层级称为树的高度。
如下图树的高度为2:
![image-20210423182307745](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210423182307745.png)
度(degree)
一个枝的度代表它有几条出边,而一颗树的度则取决于树中节点的最大度。
如,一颗树中最大的度为2,这可树可被称为二叉树,除此之外还有三叉、四叉树等结构。
如下图,这是一颗三叉树:
![image-20210425141745353](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210425141745353.png)
认识二叉树
普通二叉树
普通的二叉树没什么要求,树的最大度为2即可。
完全二叉树
在完全二叉树中,枝节点必须全部是满的,叶子节点可以不满。
但是新的节点添加时必须从左至右依次添加,不能先添加右边后添加左边。
平衡二叉树
树的左右子树高度差不超过1的树被称为平衡二叉树,空树也是平衡二叉树的一种。
满二叉树
满二叉树的意思就是无论是枝节点还是叶子节点,必须全部都是满的。
二叉树表示
列表表示法
在Python中,我们可以使用多维的列表来表示一颗不确定度的多叉树。
这样的做法有一个好处,每一个列表都是一颗子树,索引值1是左子树,索引值2是右子树。
同时,如果要增加某个节点的度,则向其添加一个空列表即可。
如上述这幅图的二叉树,则可以向下面这样进行表示:
- 第1个元素为根节点的值
- 第2个元素是左子树(所以也是一个列表)
- 第3个元素是右子树(所以也是一个列表)
[root, [left], [right]]
-----------------------
0 1 2
如下所示:
myTree = \
[
"a",
["b",
["d", [], []],
["e", [], []]
],
["c",
["f"]
]
]
拿到第二层的叶子节点f:
print(myTree[2][1])
节点表示法
使用节点表示法,能够更加清晰的管理树,这比列表表示法通常更加省力。
如下图所示:
以下是代码实现上图中二叉树的表现:
class BinaryTree:
def __init__(self, root):
self.key = root
self.leftChild = None
self.rightChild = None
self.height = 0
def insertLeft(self, newNode):
tree = BinaryTree(newNode)
if not self.leftChild:
self.leftChild = tree
else:
# 如果插入位置已有节点,则整体向下挪
# 新的子节点与旧的子节点链接,旧的父节点与新的子节点链接
tree.leftChild = self.leftChild
self.leftChild = tree
self.height += 1
def insertRight(self, newNode):
tree = BinaryTree(newNode)
if not self.rightChild:
self.rightChild = tree
else:
tree.rightChild = self.rightChild
self.rightChild = tree
self.height += 1
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
if __name__ == '__main__':
binaryTree = BinaryTree("a")
binaryTree.insertLeft("b")
binaryTree.insertRight("c")
binaryTree.leftChild.insertLeft("d")
binaryTree.leftChild.insertRight("e")
binaryTree.rightChild.insertLeft("f")
二叉树遍历
将一颗二叉树依次排开,如下所示:
- 根节点在中间
- 左节点在左边
- 右节点在右边
![image-20210425155317927](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210425155317927.png)
那么就会有不同的4种遍历规则。
- 前序遍历(preorder)
- 中序遍历(inorder)
- 后序遍历(postorder)
- 层级遍历(levelorder)
我们可以在BinaryTree中新增几个实例方法,用来书写不同的遍历代码:
class BinaryTree:
...
def preOrder(self):
pass
def inOrder(self):
pass
def postOrder(self):
pass
def levelOrder(self):
pass
前序遍历(pre order)
前序遍历规则如下:
- 首先访问根节点
- 前序访问左子树
- 前序访问右子树
一句话总结:中左右
遍历顺序:
![image-20210425155546095](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210425155546095.png)
代码实现:
def preOrder(self):
def inner(tree):
if tree:
print(tree.getRootVal())
inner(tree.getLeftChild())
inner(tree.getRightChild())
inner(self)
中序遍历(in order)
中序遍历规则如下:
- 中序访问左子树
- 然后访问根节点
- 中序访问右子树
一句话总结:左中右
遍历顺序:
![image-20210425155745841](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210425155745841.png)
代码实现:
def inOrder(self):
def inner(tree):
if tree:
inner(tree.getLeftChild())
print(tree.getRootVal())
inner(tree.getRightChild())
inner(self)
后序遍历(post order)
后序遍历规则如下:
- 后序访问左子树
- 后序访问右子树
- 最后访问根节点
一句话总结:左右中
遍历顺序:
![image-20210425155914157](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210425155914157.png)
代码实现:
def postOrder(self):
def inner(tree):
if tree:
inner(tree.getLeftChild())
inner(tree.getRightChild())
print(tree.getRootVal())
inner(self)
层级遍历(level order)
层级遍历没什么好说的,按层访问:
- 第0层、根
- 第1层、左、右
- …第n层、左、右
遍历顺序:
![image-20210425160204961](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210425160204961.png)
代码实现,需要借助一个双端队列或者列表:
def levelOrder(self):
def inner(tree):
treeLst = []
treeLst.append(tree)
while len(treeLst) > 0:
node = treeLst.pop(0)
print(node.getRootVal())
if node.leftChild:
treeLst.append(node.getLeftChild())
if node.rightChild:
treeLst.append(node.getRightChild())
inner(self)
根据遍历画出树
如果给出你2个遍历结果,并且指明这棵树是二叉树,如何画出这棵树的结构?
- 前序遍历顺序是 A B C D E F G
- 中序遍历顺序是 C B D A F E G
首先,前序遍历是中左右,中序遍历是左中右,按照下面的结构开始进行结构划分:
前序: [A] [B C D] [E F G]
中序: [C B D] [A] [F E G]
划分完成之后就可以出图了:
![image-20210425162554727](https://images-1302522496.cos.ap-nanjing.myqcloud.com/img/image-20210425162554727.png)