二叉树特点是每个结点最多只能有两棵子树,且有左右之分。
二叉树的特殊类型:
满二叉树:
如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树;
完全二叉树:
深度为k,有n个结点的二叉树当且仅当其每一个结点都与深度为k的满二叉树中编号从1到n的结点一一对应时,称为完全二叉树。
二叉搜索树:
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。二叉搜索树作为一种经典的数据结构,它既有链表的快速插入与删除操作的特点,又有数组快速查找的优势;所以应用十分广泛。
python中二叉树节点定义如下,含有值val,左节点left,右节点right:
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
二叉树主要是通过遍历实现的,一般需要用到递归或者迭代,对于递归,需要注意其需要递归之处及终止条件。
重点在于递归原理是,主树的深度为其左右子树中最大值加一,因此根据此原理构建处maxDepth函数。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root: return 0
else: return max(self.maxDepth(root.right),self.maxDepth(root.left))+1
解法一:常规方法,先计算出左右子树的深度再进行比较。
不过这样会导致很多节点的重复计算
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
def depth(root:TreeNode): # 计算深度
if not root: return 0
return max(depth(root.right),depth(root.left))+1
# 判断,因为要判断左右子树本身是否为平衡树
if not root: return True
if abs(depth(root.left)-depth(root.right)) <=1 and self.isBalanced(root.left) and self.isBalanced(root.right): return True
return False
解法二:
参考解法
优化的地方在于,事先判断左子树和右子树是否为平衡树,如果不是则直接退出(返回-1),达到了剪枝的目的。
# 方法二
def find(root:TreeNode):
if not root: return 0
left = find(root.left) # -1
if left == -1:return -1
right = find(root.right) # -1
if right == -1:return -1
return max(left,right)+1 if abs(left-right)<=1 else -1
return find(root) != -1
重点在于迭代的停止条件,
若两边节点为空,则返回True,若有一个为空,或者均不为空,但值不同,则返回False;
迭代部分是对左子树的左节点和右子树的右节点,以及左子树的右节点和右子树的左节点进行比较。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
def duicheng(R,L):
# 两边同时为空
if not L and not R: return True
# 若有一个为空,或者都不为空,但值不同
if not L or not R or (L.val != R.val): return False
return duicheng(R.left,L.right) and duicheng(L.left,R.right)
return duicheng(root.right,root.left) if root else True
重点在于交换一个节点的左右子节点。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def mirrorTree(self, root: TreeNode) -> TreeNode:
if not root: return None
# temp = (root.left)
root.left,root.right = self.mirrorTree(root.right),self.mirrorTree(root.left)
return root
这个在于判断条件比较复杂,
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
if not root or root==q or root==p:return root
left = self.lowestCommonAncestor(root.left,p,q)
right = self.lowestCommonAncestor(root.right,p,q)
if not left and not right:return None # 均为空
if left and not right: return left # 左有,
if right and not left: return right # 右有
return root
上面一题是针对的普通的二叉树,这题是搜索二叉树,搜索二叉树的特点就是左节点小于根节点小于右节点(因此中序遍历是从小到大的遍历),可以因此简化判断条件。。
方法一:通用的递归方法
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if root.val>p.val and root.val>q.val: # 说明在左子树
return self.lowestCommonAncestor(root.left,p,q)
elif root.val<p.val and root.val<q.val: # 说明在右子树
return self.lowestCommonAncestor(root.right,p,q)
return root
方法二:迭代方法
先保证p、q的大小确定,则可以简化判断条件
if p.val>q.val: p,q=q,p # p
while root:
if root.val<p.val: # 说明在右子树
root = root.right
if root.val>q.val: # 说明在左子树
root = root.left
else: # 说明在两侧,即root为公共节点
return root
二叉搜索树的特点是,节点值大小按左,中,右排列,因此,中序遍历的话,则可以得到顺序的节点值,因此,本题适合用中序遍历做。
class Solution:
def kthLargest(self, root: TreeNode, k: int) -> int:
ls = []
def medium(root):
if not root: return None
if root.right: medium(root.right)
if root.val: ls.append(root.val)
if root.left: medium(root.left)
# return root
medium(root)
return ls[k]
改进:
class Solution:
def kthLargest(self, root: TreeNode, k: int) -> int:
self.m = k
def medium(root):
if not root: return None
medium(root.right)
if self.m==0:
return None
self.m -= 1
if self.m==0: self.res = root.val
medium(root.left)
medium(root)
return self.res
明显的,考察的是二叉树的层序遍历。。
class Solution:
def levelOrder(self, root: TreeNode) -> List[int]:
if not root: return []
queue = [root]
self.val = []
while queue:
vertex = queue.pop(0) # 首节点出列
self.val.append(vertex.val)
if vertex.left: queue.append(vertex.left)
if vertex.right: queue.append(vertex.right)
return self.val
这个是在上一题的基础上,加上一个条件:按每一层打印。因此在层序遍历的基础上,分开每一层。
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root: return []
queue = [root]
self.ls = []
while queue:
tmp = []
for _ in range(len(queue)): # 每一层有多少节点就循环多少次
vertex = queue.pop(0) # 正好循环完所有节点都存在tmp里面
tmp.append(vertex.val)
if vertex.left:
queue.append(vertex.left)
if vertex.right:
queue.append(vertex.right)
self.ls.append(tmp) # 保存每一层的输出
return self.ls
本题也是在上一题的基础上,隔一层换一次输出方向,因此需要控制每一层节点放进队列的前后顺序,因此采用collections模块的deque函数,优点是方便对队列两端的操作(其实上面两题也可以用它)。
import collections
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root: return []
queue = collections.deque() # 创建队列
queue.append(root) # append和list的append一样,从后面添加
self.ls = []
while queue:
tmp = collections.deque()
for i in range(len(queue)):
vertex = queue.popleft() # 从前面弹出(即首节点)
if len(self.ls)%2: # 如果为偶数层,从队列尾部添加
tmp.append(vertex.val)
else: # 奇数层,从队列前面添加,非常方便(这样就倒序了)
tmp.appendleft(vertex.val)
if vertex.right:
queue.append(vertex.right)
if vertex.left:
queue.append(vertex.left)
self.ls.append(list(tmp)) # 注意这里需要转化一下。。。
return self.ls
根据一位大佬的代码总算弄清楚了原理。其思路是,前序遍历中的子树序列仍为前序遍历结果(显而易见,因为都是递归套娃而来),中序遍历的子树序列仍为中序遍历。因此,根据根节点的位置,划分出左子树序列和右子树序列,然后以左子树前序序列和中序序列为函数的输入,返回根节点的左子树,同理返回右子树,再将这两棵子树加到根节点的左右,程序最终返回完整的root。
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if not preorder or not inorder:
return None
if len(preorder)!=len(inorder):
return None
root_node = TreeNode(preorder[0])
root_pos = inorder.index(root_node.val)
pre_left_node = preorder[1:root_pos+1]
pre_right_node = preorder[root_pos+1:]
in_left_node = inorder[:root_pos]
in_right_node = inorder[root_pos+1:]
left = self.buildTree(pre_left_node, in_left_node)
right = self.buildTree(pre_right_node, in_right_node)
# 接上root
root_node.left = (left) # 因为返回值已经是node了
root_node.right = (right) # 这个也是
return root_node
二叉搜索树的特点是左节点<根节点<右节点,后序遍历的顺序是左右根,因此需要先将序列分成根节点、左子树、右子树。
方法一:通过遍历找出左子树和右子树的分界点mid,然后由于左子树由于前面步骤,确保了左子树必定小于根节点,只需要判断右子树是否都大于根节点了。
class Solution:
def verifyPostorder(self, postorder: List[int]) -> bool:
def search(left,root):
if left>=root: return True
tmp = left
while postorder[tmp]<postorder[root]: tmp+=1
mid = tmp
while postorder[root]<postorder[mid]: mid+=1
return mid==root and search(0,mid-1) and search(mid,root)
return search(0,len(postorder)-1)
方法二:思路和方法一一致,但实现更为清晰,更容易想到。
class Solution:
def verifyPostorder(self, postorder: List[int]) -> bool:
if not postorder: return True
root = postorder[-1]
length = len(postorder)
posi = 0
for i in range(length):
if postorder[i]>=root:
posi = i
break
left = postorder[0:posi]
right = postorder[posi:-1]
for val in right:
if val<root:
return False
return self.verifyPostorder(left) and self.verifyPostorder(right)
参考来源
本题原理是匹配树节点,利用前序遍历的过程中,搜索匹配B的根节点,主要是终止条件比较复杂,不好分析。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
def recur(A,B):
if not B: return True
if not A or A.val != B.val: return False
return recur(A.left,B.left) and recur(A.right,B.right)
return bool(A and B) and (self.isSubStructure(A.left,B) or self.isSubStructure(A.right,B) or recur(A,B) )
根据二叉搜索树的特点,明显需要进行中序遍历使其有序,通常的想法是,先进行中序遍历,将节点按顺序存入列表,再进行一个循环,改变节点的左右节点的指向,需要注意对头节点和尾节点的处理。。
"""
# Definition for a Node.
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
"""
class Solution:
def treeToDoublyList(self, root: 'Node') -> 'Node':
# 中序遍历
if not root: return None
if not root.left and not root.right:
root.left = root
root.right = root
return root
ls = []
def recur(root):
if root.left:
recur(root.left)
ls.append(root)
if root.right:
recur(root.right)
recur(root)
# 节点处理
ls[0].left = ls[-1]
ls[0].right = ls[1]
ls[-1].left = ls[-2]
ls[-1].right = ls[0]
for i in range(1,len(ls)-1):
ls[i].left = ls[i-1]
ls[i].right = ls[i+1]
return ls[0]
第二种方法是直接在中序遍历的时候直接对节点进行处理。。
class Solution:
def treeToDoublyList(self, root: 'Node') -> 'Node':
def dfs(cur):
if not cur: return
dfs(cur.left) # 递归左子树
if self.pre: # 修改节点引用
self.pre.right, cur.left = cur, self.pre
else: # 记录头节点
self.head = cur
self.pre = cur # 保存 cur
dfs(cur.right) # 递归右子树
if not root: return
self.pre = None
dfs(root)
self.head.left, self.pre.right = self.pre, self.head
return self.head
根据给出的示例,可以看出这个主要框架是层序遍历,因此需要采用BFS法,需要注意的是,叶子节点也需要输出其左右节点(null)。
参考解法
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
import collections
class Codec:
def serialize(self, root):
"""Encodes a tree to a single string.
:type root: TreeNode
:rtype: str
"""
if not root: return "[]"
queue = collections.deque()
queue.append(root)
res = []
while queue:
vertex = queue.popleft()
if vertex:
res.append(str(vertex.val)) # 因为要求是字符串类型
queue.append(vertex.left)
queue.append(vertex.right)
else:
res.append("null")
return "["+",".join(res)+"]"
def deserialize(self, data):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""
if data=="[]": return None
res,i = data[1:-1].split(','),1
root = TreeNode(int(res[0]))
queue = collections.deque()
queue.append(root)
while queue:
vertex = queue.popleft()
if res[i] != "null":
vertex.left = TreeNode(int(res[i]))
queue.append(vertex.left)
i += 1
if res[i] != "null":
vertex.right = TreeNode(int(res[i]))
queue.append(vertex.right)
i += 1
return root
剑指offer的15道二叉树题这样总算是刷完了,这样记录下来,短期内还是印象还算是比较深刻的,也算是从一开始一团雾水到逐渐清晰的过程,尽管大部分的题目还是得看大佬们(主要是Krahets)的解析,但比开始之前好多了。。。
def recur(root):
if not root: return
dfs(root.left) # 左
print(root.val) # 根
dfs(root.right) # 右
# 左根右,中序遍历