树
参考资料,以下内容来自优快云/知乎大佬们:
https://blog.youkuaiyun.com/wannuoge4766/article/details/83998377
https://zhuanlan.zhihu.com/p/90255760
... ...
定义
树
树,由n(n >= 1)个有限节点组成的具有层次关系的,具有倒立的树状结构的数据集合,根朝上,叶朝下。
特点:
1. 每个节点有零个或多个子节点;
2. 没有父节点的节点称为根节点;
3. 每一个非根节点有且只有一个父节点;
4. 除了根节点外,每个子节点可以分为多个不相交的子树;
术语:度,根节点,叶子节点,父节点,子节点,深度,高度……
二叉树
每个节点最多含有两个子树的树。
完满二叉树:
除叶子节点之外的每一个节点都有两个孩子节点。(除最后一层无任何子节点外,每一层上的所有结点都有两个子结点。节点数达到最大值,所有叶子结点必须在同一层上。)
完全二叉树:
除最后一层之外的其他每一层都被完全填充,并且所有节点都保持向左对齐。(若设二叉树的深度为h,除第 h 层外,其它各层 (1~(h-1)层) 的结点数都达到最大个数,第h层所有的结点都连续集中在最左边。)
完美二叉树:
除叶子节点之外的每一个节点都有两个孩子节点,每一层(包括最后一层)都被完全填充。
二叉树遍历方式
前序遍历:根节点->左子树->右子树(根左右)
中序遍历:左子树->根节点->右子树(左根右)
后序遍历:左子树->右子树->根节点(左右根)
前中后对应的根节点位置,即前序根节点在最前,中序根节点在中间,后序根节点在最后
层次遍历:从上至下逐层自左至右遍历(上图层次遍历的串行是:-+/a*efb-cd)
二叉查找树
Binary Search Tree,又称二叉搜索树、有序二叉树、排序二叉树。
1.若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值; 2.若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值; 3.任意节点的左、右子树也分别为二叉查找树; 4.没有键值相等的节点。
平衡二叉树
AVL树:当且仅当任何节点的两棵子树的高度差不大于1的二叉树,严格的平衡二叉树。适用于插入删除次数比较少,但查找多的情况。AVL树的左右子节点也是AVL树。
时间复杂度O(n)。
实现方式
Python树
本次只提供python的实现方式,C/C++可自行了解。
# !/usr/bin/env python
# coding: utf-8
from collections import deque
from typing import List
# 节点类
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
# 初始化树(参考大佬https://www.cnblogs.com/yezigege/p/13441377.html)
class BinaryTree:
def __init__(self):
self.root = None
def init_tree(self, values=None):
if not values:
return None
self.root = TreeNode(values[0])
queue = deque([self.root])
index = 1
while index < len(values):
node = queue.popleft()
if node:
node.left = TreeNode(values[index]) if values[index] else None
queue.append(node.left)
if index + 1 < len(values):
node.right = TreeNode(values[index + 1]) if values[index + 1] else None
queue.append(node.right)
index += 1
index += 1
Python树可视化
一些比较好玩的尝试,用到了matplotlib.pyplot、networkx和pygraphviz两种。
1. networkx
networkx详细说明请参考大佬文章:
https://blog.youkuaiyun.com/CUFEECR/article/details/103007712
https://blog.youkuaiyun.com/weixin_44301621/article/details/104837275
# !/usr/bin/env python
# coding: utf-8
import matplotlib.pyplot as plt
import networkx as nx
# 注意:整体代码有简化
def draw_tree(node):
pos = dict()
def create_graph_two(tree_graph, _node, x=0, y=0, layer=1):
pos[_node.val] = (x, y)
if _node.left:
tree_graph.add_edge(_node.val, _node.left.val)
l_x, l_y = x - 1 / 2 ** layer, y - 1
l_layer = layer + 1
create_graph_two(tree_graph, _node.left, x=l_x, y=l_y, layer=l_layer)
if _node.right:
tree_graph.add_edge(_node.val, _node.right.val)
r_x, r_y = x + 1 / 2 ** layer, y - 1
r_layer = layer + 1
create_graph_two(tree_graph, _node.right, x=r_x, y=r_y, layer=r_layer)
return tree_graph, pos
graph = nx.DiGraph()
graph, pos = create_graph_two(graph, node)
fig, ax = plt.subplots(figsize=(10, 10)) # 比例可以根据树的深度适当调节
nx.draw_networkx(graph, pos, ax=ax, node_size=1000, node_color='green', font_size=16)
plt.show()
2. pygraphviz
pygraphviz的安装可能会遇到问题,本次尝试(Win10 X64)是先安装了graphviz,再安装pygraphviz的whl,还重启了PyCharm才OK的,Linux有所不同。
下载graphviz:https://graphviz.org/download/
下载pygraphviz:https://www.lfd.uci.edu/~gohlke/pythonlibs/#pygraphviz
注意:pygraphviz需要与Python版本匹配。
pygraphviz详细说明请参考知乎大佬:
https://zhuanlan.zhihu.com/p/104636240
AGraph参数:
参数 | 说明 | 取值 |
---|---|---|
directed | 有向图 | False/True |
strict | 简单图 | False/True |
nodesep | 同级节点最小间距 | |
ranksep | 不同级节点最小间距 | |
rankdir | 绘图方向 | TB (从上到下), LR (从左到右), BT (从下到上), RL (从右到左) |
splines | 线条类型 | ortho (直角), polyline (折线), spline (曲线), line (线条), none (无) |
concentrate | 合并线条 (双向箭头) | False/True |
bgcolor | 背景颜色 | |
compound | 多子图时,不允许子图相互覆盖 | False/True |
normalize | 以第一个节点作为顶节点 | False/True |
示例代码:
# !/usr/bin/env python
# coding: utf-8
import pygraphviz as pgv
# pygraphviz 添加节点/边即可写入dot并导出和绘制,比较简单
# 又写了一个公用的方法,获取二叉树父子节点组合列表,遍历进行add_edge
# 注意:整体代码有简化
if __name__ == '__main__':
tree_pic = pgv.AGraph(directed=True, strict=True)
tree_pic.add_edge(1, 2)
tree_pic.add_edge(1, 3)
tree_pic.add_edge(2, 5)
tree_pic.add_edge(2, 4)
tree_pic.add_edge(5, 6)
tree_pic.add_edge(5, 7)
tree_pic.add_edge(3, 8)
tree_pic.add_edge(3, 9)
tree_pic.add_edge(8, 10)
tree_pic.add_edge(8, 11)
tree_pic.graph_attr['epsilon'] = '0.01'
print(tree_pic.string()) # print dot file to standard output
tree_pic.write('tree_pic.dot')
tree_pic.layout('dot') # layout with dot
tree_pic.draw('tree_pic.png')
LeetCode题目
94.二叉树的中序遍历
https://leetcode-cn.com/problems/binary-tree-inorder-traversal/
给定一个二叉树的根节点 root ,返回它的 中序 遍历。
解题思路
本地调试时候发现示例1给的列表"[1, None, 2, 3]",初始化树有点问题。普通的(2i+1, 2i+2)方法,对于元素个数小于(2*高度-1)的情况右子树会少一部分。思考和查询之后,参考大佬办法利用deque解决此问题。
Python3方法
# !/usr/bin/env python
# coding: utf-8
from collections import deque
from typing import List
# 节点类 + 初始化树
# 具体代码见上文
class Solution:
@staticmethod
def inorder_traversal(root: TreeNode) -> List[int]:
stack, res = [root], []
while stack:
i = stack.pop()
if isinstance(i, TreeNode):
stack.extend([i.right, i.val, i.left])
elif isinstance(i, int):
res.append(i)
return res
if __name__ == '__main__':
tree_data = [1, None, 2, 3]
# tree_data = [1, 2, 3, None, 4, 5]
rt = BinaryTree()
rt.init_tree(tree_data)
result = Solution()
print(result.inorder_traversal(rt.root))
95.不同的二叉搜索树 II
https://leetcode-cn.com/problems/unique-binary-search-trees-ii/
给你一个整数 n ,请你生成并返回所有由 n 个节点组成且节点值从 1 到 n 互不相同的不同 二叉搜索树 。可以按 任意顺序 返回答案。
解题思路
二叉搜索树关键性质:根节点值大于左子树所有节点值,小于右子树所有节点值。且左子树、右子树同为二叉搜索树。
回溯。
增加了一个打印的方法。
Python3方法
# !/usr/bin/env python
# coding: utf-8
from typing import List
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
@staticmethod
def generate_trees(n: int) -> List[TreeNode]:
dp = []
for i in range(n + 1):
dp.append([])
for j in range(n + 1):
if i == j:
dp[i].append([TreeNode(i)])
elif i < j:
dp[i].append([])
else:
dp[i].append([None])
dp[0][0] = None
for i in range(n - 1, 0, -1):
for j in range(i + 1, n + 1):
for r in range(i, j + 1):
left = r + 1 if r < j else r
for x in dp[i][r - 1]:
for y in dp[left][j]:
node = TreeNode(r)
node.left = x
node.right = y
if r == j:
node.right = None
dp[i][j].append(node)
return dp[1][n]
@staticmethod
def generate_trees_2(n: int) -> List[TreeNode]:
def get_trees(start, end):
if start > end:
return [None, ]
all_trees = []
for i in range(start, end + 1): # 枚举可行根节点
# 获得所有可行的左子树集合
left_trees = get_trees(start, i - 1)
# 获得所有可行的右子树集合
right_trees = get_trees(i + 1, end)
# 从左子树集合中选出一棵左子树,从右子树集合中选出一棵右子树,拼接到根节点上
for l in left_trees:
for r in right_trees:
curr_tree = TreeNode(i)
curr_tree.left = l
curr_tree.right = r
all_trees.append(curr_tree)
return all_trees
return get_trees(1, n) if n else []
@staticmethod
def print_trees(trees):
tree_list = list()
for tree in trees:
if not tree:
return []
res = [tree]
nums = []
while res:
node = res.pop(0)
nums.append(node.val)
if node.left:
res.append(node.left)
if node.right:
res.append(node.right)
tree_list.append(nums)
print(tree_list)
if __name__ == '__main__':
num = 3
result = Solution()
# dp
result.print_trees(result.generate_trees(num))
# 回溯
result.print_trees(result.generate_trees_2(num))
98.验证二叉搜索树
https://leetcode-cn.com/problems/validate-binary-search-tree/
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。
解题思路
根据二叉搜索树的定义判断。
Python3方法
# !/usr/bin/env python
# coding: utf-8
from collections import deque
INT_MAX = 2 ** 31
INT_MIN = -(2 ** 31) - 1
# 节点类 + 初始化树
# 具体代码见上文
class Solution:
def __init__(self):
self.is_ok = True
def is_valid_bst(self, root: TreeNode) -> bool:
self.validate(root, INT_MIN, INT_MAX)
return self.is_ok
def validate(self, node, low, high):
if self.is_ok:
if node.val >= high or node.val <= low:
self.is_ok = False
if node.left:
self.validate(node.left, low, node.val)
if node.right:
self.validate(node.right, node.val, high)
if __name__ == '__main__':
# tree_data = [2, 1, 3]
tree_data = [5, 1, 4, None, None, 3, 6]
rt = BinaryTree()
rt.init_tree(tree_data)
res = Solution()
print(res.is_valid_bst(rt.root))
总结
十二月的算法训练,树。因为工作和学习一直在用Python,写C++手生了都。本月也更多时间沉迷于更好玩的可视化,所以直接略去了三道题的C++方法解答。还有就是,做题过程中发现C++在初始化树时候(个人)不太好实现,94题给的示例1列表,Python方法转树结构一开始都没搞定。
三道题做完之后多想了点,于是就有了树的可视化这一小节内容。工作中遇到有同事在用DiGraph方法,正好就借此学习学习。本来可以独立成篇,不过先就这样吧,毕竟主题是算法。
OJ算法训练,第一阶段就此结束。