树在面试中真的是一个出镜率异常高的数据结构了。而且和递归结合紧密,作为手撸代码的考察真是再合适不过。
所以就在这篇日记里不断更新树的一些概念和相应的题目吧~
我的梦想是!一文在手!天下我有!
树的手撸代码中常常涉及到的知识
- 树的深度
- 递归 / 非递归
- 树的高度
- 递归
- 非递归: 可以层序
- 平衡树
- 树的结点
- 结点数
- 求下一个结点:前中后序
- 数的遍历
- 前中后序遍历
- 递归
- 非递归:栈/队列辅助
- 层序遍历(队列辅助)
- 层序遍历一棵树
- 逐层打印一棵树
- 之字形打印一棵树等等
- 前序+中序 / 后序+中序重建一棵树
- 前中后序遍历
- 子树
- 树的镜像
- 二叉树
- 二叉平衡树
- 左右子树高度差不超过1
- 二叉搜索树
- 左子树永远小于等于右子树
- 树的路径
面试还可能问到:
- 红黑树
- B树
- B+树
先写一棵树
你一定要会写树的结点定义啊
# 没有指向老父亲的结点指针的树结点
class TreeNode():
def __init__(self,x):
self.val = x
self.left = None
self.right = None
# 有指向父亲的结点指针的树结点
class TreeNode2():
def __init__(self,x):
self.val = x
self.left = None
self.right = None
self.next = None # 父指针
然后呢?结点写出来了不会写出一棵树来测试自己的代码丢不丢人。。。
# Test:
# 10
# / \
# 5 12
# / \
# 4 7
value = [10,5,12,4,7]
node = []
# 初始化这些结点
for val in value:
node.append(TreeNode(val))
# 然后把结点连起来
node[0].left = node[1]
node[0].right = node[2]
node[1].left = node[3]
node[1].right = node[4]
# 或者这样
node_var = [10,9,8,7,6,5]
node = []
for item in node_var:
node.append(TreeNode(item))
i = 0
while 2*i+1<len(node):
node[i].left = node[2*i+1]
if 2*i+2<len(node):
node[i].right = node[2*i+2]
i+=1
然后是树的遍历!
这个不管是递归还是非递归,手撸的概率很高。之前阿里的暑期实习就要求我写一个非递归的中序遍历。
非递归可以写出各种版本。但是可以用一个统一的逻辑概括起来。便于记住。如下图所示。
当然还可以有其他逻辑。我就不贴出来了。
太多会乱。统一起来便于记忆!
详细的递归 / 非递归代码如下
class Tranverse():
# 递归先序
def preOrderIter(self,root):
if not root:
return []
res =[]
res.append(root.val)
left = self.preOrderIter(root.left)
right = self.preOrderIter(root.right)
res += left+right
return res
# 非递归先序
# 用一个栈辅助的话,先右孩子在左孩子入栈
# 用一个队列,那就pop(0),先左后右
def preOrderNoIter(self,root):
if not root:
return []
res =[]
stack =[root]
while stack:
# 元素出栈
root = stack.pop()
# 访问这个元素
res.append(root.val)
# 先右
if root.right:
stack.append(root.right)
# 再左
if root.left:
stack.append(root.left)
return res
# 递归中序
def inOrderIter(self,root):
if not root:
return []
res =[]
left = self.inOrderIter(root.left)
res += left
res.append(root.val)
right = self.inOrderIter(root.right)
res += right
return res
# 非递归中序
def inOrderNoIter(self,root):
if not root:
return []
res =[]
stack =[]
# 如果栈不空或者结点存在
while stack or root:
#结点存在
while root:
# 结点入栈并向左子树
stack.append(root)
root = root.left
# 左孩子空了跳出循环。如果栈不空
if stack:
# 元素出栈并访问
root = stack.pop()
res.append(root.val)
# 然后访问右子树
root = root.right
return res
# 递归后序
def postOrderIter(self,root):
if not root:
return []
res =[]
left = self.postOrderIter(root.left)
right = self.postOrderIter(root.right)
res += left+right
res.append(root.val)
return res
# 非递归后序
# 按照根-右-左的顺序存储到res,然后倒序输出
# 可以直接对照先序非递归来写
def postOrderNoIter1(self,root):
if not root:
return []
stack = [root]
res = []
while stack:
root = stack.pop()
# res 存储根
res.append(root.val)
# 先压栈左孩子,之后会后存入res
if root.left:
stack.append(root.left)
# 后压入右孩子,之后会先弹出先存入res
if root.right:
stack.append(root.right)
return res[::-1]
def postOrderNoIter2(self,root):
if not root:
return []
res = []
stack =[]
while stack or root:
while root:
stack.append(root)
res.append(root.val)
root = root.right
if stack:
root = stack.pop()
root = root.left
return res[::-1]
# 层序遍历
def layerOrder(self,root):
if not root:
return []
res =[]
queue =[root]
while len(queue)>0:
root = queue.pop(0)
res.append(root.val)
if root.left:
queue.append(root.left)
if root.right:
queue.append(root.right)
return res
然后数一下树的结点
其实数树的的结点数有很多办法。
比如可以用任意一种遍历,每次访问一个结点就计数。或者返回结点值列表的长度。
也可以这样直接递归数:
def treeNodeNums(root):
if not root:
return 0
nums = treeNodeNums(root.left)+treeNodeNums(root.right) + 1
# 当然这里也可以写:
# left = treeNodeNums(root.left)
# right = treeNodeNums(root.right)
# nums = left+right+1
return nums
再然后求一下树的深度
树的深度当然也可以用递归或者非递归方法求。
非递归的话可以借助一下层序遍历
递归的话就很类似于数结点数了:
def treeDepth(root):
if not root:
return 0
# 左子树深度
left = treeDepth(root.left)
# 右子树深度
right = treeDepth(root.right)
return max(left,right)+1