关于树的考点:
1.树的遍历实现
2.宽度优选遍历(按层打印)
3.二叉搜索树,堆(最大堆,最小堆),红黑树
与链表相比,树中的指针操作更多也更复杂,因此与树相关的问题通常会比链表的要难。
当遇到树的问题时,比如遍历,可以使用递归和循环的方式去遍历,但是采用递归的代码会比较简洁,在没有特殊要求的条件下,我们一般采用递归的方式。
Tips:
- 与二叉树相关的代码有大量的指针操作,在每次使用指针的时候,我们都要问自己这个指针有没有可能是None,如果是None应该怎么处理。
- 当数值类型为double的时候,判断两个节点的值是不是相等时,不能直接写p1.val==p2.val,这是因为计算机内表示小数时都有误差。判断两个小数是否相等,只能判断它们之差的绝对值是不是在一个很小的范围内,如果两个数相差很小(如0.0000001),就可以认为他们相等。
- 如果面试题要求处理一棵二叉树的遍历序列,则可以先找到二叉树的根节点,再基于根节点把整棵树的遍历序列拆分成左子树对应的子序列和右子树对应的子序列,接下来再递归地处理这两个子序列。
常见的问题有:
1)二叉树的遍历:先序遍历,中序遍历,后序遍历。扩展:寻找二叉搜索树中第K小的数值。
2)二叉树按照层次遍历,不分层打印/分层打印。
3)二叉树与双向链表
4)二叉树的深度
5)给定一颗二叉树,对每一个结点加上一个next指针指向其右边的元素。
6)已知树的前序遍历和中序遍历,构造出树结构。
7)给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
8)请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
9)堆:
堆其实就是利用完全二叉树的结构来维护的一维数组
按照堆的特点可以把堆分为大顶堆和小顶堆
大顶堆:每个结点的值都大于或等于其左右孩子结点的值
小顶堆:每个结点的值都小于或等于其左右孩子结点的值
堆的存储形式是数组,我们用简单的公式来描述一下堆的定义就是:(读者可以对照上图的数组来理解下面两个公式)
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
解法:
1)这里以二叉树的中序遍历为例:
#递归的方法
Class Solution(object):
def inorderTraversal(self,root):
res=[] #定义一个数组存放已经遍历的树节点
self.help(root,res)
return res
def help(self,root,res):
if not root:
return None
self.help(root.left,res)
res.append(root.val)
self.help(root.right,res)
#循环的方法
Class Solution(object):
def inorderTraversal(self,root):
#res=stack=[] 注意不能这么写,这么写的话对res和stack的操作会同时作用在两个变量上
res,stack=[],[]
while stack or root:
while root:
stack.append(root)
root=root.left
root=stack.pop()
res.append(root.val)
root=root.right
return res
扩展:求二叉搜索树中第K小的节点。
二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
解法:实际上就是二叉树中序遍历的第k个结点:
class Solution:
# 返回对应节点TreeNode
def KthNode(self, pRoot, k):
# write code here
if not pRoot:
return
res=[]
self.help(pRoot,res)
if k<=0 or k>len(res):
return
return res[k-1]
def help(self,root,res): #输入参数:root:根结点 res:已经遍历过的结点序列
if not root:
return res
self.help(root.left,res)
res.append(root)
self.help(root.right,res)
这种解法需要占用o(n)大小的内存空间,怎么解放这种空间使用呢?
#递归的方法,因为不需要存储所有值,而只需要确定调用函数help的次数,因此help中不需要res参数
Class Solution(object):
def kthSmallest(self,root,k):
self.k=k
self.res=0
return self.help(root)
def help(self,root):
if root:
self.help(root.left)
self.k-=1
if self.k==0:
self.res=root.val
self.help(root.right)
return self.res
#循环的方法
Class Solution(object):
def kthSmallest(self,root,k):
stack=[]
res=None
while stack or root:
while root:
stack.append(root)
root=root.left
root=stack.pop()
k-=1
if k==0:
res=root.val
root=root.right
return res
2)解法:按照层次遍历采用循环的方法,用一个队列来存储每一层数值依次处理。
#按照层次打印
class Solution(object):
def levelOrder(self, root):
if not root:
return []
queue=[root] #待打印的结点
res,subres=[],[] #res总的list,subres每一层list
ToBePrinted=1 #提示这一层是否已经打印完
NextLevel=0 #下一层的结点数
while queue:
p=queue.pop(0)
ToBePrinted-=1
subres.append(p.val)
if p.left:
NextLevel+=1
queue.append(p.left)
if p.right:
NextLevel+=1
queue.append(p.right)
if ToBePrinted==0:
res.append(subres)
subres=[]
ToBePrinted=NextLevel
NextLevel=0
return res
扩展:要Z字形打印二叉树怎么做?
主要改变是:1.将队列改为栈来实现,这样就导致上一层还没pop完,下一层的数据又进入队列,因此应该使用两个栈queue,queue2,第一个存放上一层的节点,第二个存放下一层进入的节点,当上一层pop完之后queue=queue2;2.在层数为奇偶时增加树节点的方式不同,在奇数层先增加左节点,偶数层先增加右节点(因为使用栈实现,可能顺序反过来).
class Solution(object):
def zigzagLevelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
"""
if not root:
return []
queue=[root]
queue2=[]
res,subres=[],[]
flg=1
while queue:
p=queue.pop()
subres.append(p.val)
if flg:
if p.left:
queue2.append(p.left)
if p.right:
queue2.append(p.right)
else:
if p.right:
queue2.append(p.right)
if p.left:
queue2.append(p.left)
if not queue:
res.append(subres)
subres=[]
flg=1-flg
queue=queue2
queue2=[]
return res
3)解法:使用递归完成,实际上是一个中序遍历,当遍历到根节点时,先连接根结点与左节点,然后连接根节点与右节点。对于根节点的左右子树的操作与主要操作一致,使用递归。
class Solution:
def Convert(self, pRootOfTree):
if not pRootOfTree:
return None
if not pRootOfTree.left and not pRootOfTree.right:
return pRootOfTree
self.Convert(pRootOfTree.left) ##递归处理左子树
Left=pRootOfTree.left
if Left: ##连接根节点与左子树
while Left.right:
Left=Left.right
pRootOfTree.left,Left.right=Left,pRootOfTree
self.Convert(pRootOfTree.right) ##递归处理右子树
Right=pRootOfTree.right
if Right: ##连接根节点与右子树
while Right.left:
Right=Right.left
pRootOfTree.right,Right.left=Right,pRootOfTree
while pRootOfTree.left: ##这种写法每次递归都有一个返回值,浪费了资源
pRootOfTree=pRootOfTree.left
return pRootOfTree
5)解题思路是利用上一层构建的next指针来实现下一层的指向,让每一个结点的左节点指向右节点是容易实现的,难的在于如何使前一个结点的右节点指向第二个结点的左节点,此时应该使用next。
code中有两个循环,第一层是level的循环直至最后一层,第二层循环是利用root.next实现对level中每一个结点遍历。
class Solution:
def connect(self, root):
while root and root.left:
Next=root.left
while root:
root.left.next=root.right
root.right.next=root.next and root.next.left
root=root.next
root=Next
6)解法:
思路:结合以下三个事实采用递归的方式重建二叉树:
- 前序遍历的第一个遍历点总是根结点,接下来是左子树,然后是右子树;
- 中序遍历总是先遍历左子树,然后是根结点,最后是右子树。
- 该函数返回的结点为根结点。
返回根结点,让下一次的返回值作为上一层结构的左右节点。注意在最后何时返回为None.
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def buildTree(self, preorder, inorder):
"""
:type preorder: List[int]
:type inorder: List[int]
:rtype: TreeNode
"""
if len(preorder)==0:
return None
proot=TreeNode(preorder[0])
j=inorder.index(preorder[0])
proot.left=self.buildTree(preorder[1:j+1],inorder[0:j])
proot.right=self.buildTree(preorder[j+1:],inorder[j+1:])
return proot
7)
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
# -*- coding:utf-8 -*-
# class TreeLinkNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
# self.next = None
'''
若该结点有右子结点,则下一个结点是右子结点不断递归的左子结点;
如果该结点没有右子结点,有三种情况:
1.该结点是根结点,直接返回None;
2.它是父结点的左结点,则下一个结点是它的父结点;
3.它是父结点的右结点,则沿着父结点向上找,直到找到一个结点的父结点的左结点为该结点为止,此时该结点的父结点为下一个结点;
'''
class Solution:
def GetNext(self, pNode):
# write code here
if not pNode:
return
if pNode.right:
p=pNode.right
while p.left:
p=p.left
return p
while pNode.next:
if pNode.next.left==pNode:
return pNode.next
pNode=pNode.next
return
8)请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
# -*- coding:utf-8 -*-
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
'''
二叉树对称时的定义:左结点=右结点,且左结点和右结点的子结点也是镜面关系。
同样是使用递归的方法来解决问题,我们需要不停的比较两个呈镜面关系的两个结点的左结点和右结点
'''
class Solution:
def isSymmetrical(self, pRoot):
# write code here
def issame(left,right):
if not left and not right: #终止条件
return True
if left and right and left.val==right.val: #继续迭代的条件:上一层的两个结点为镜像结点
return issame(left.right,right.left) and issame(left.left,right.right) # 判断镜像结点的左结点和右结点是否相等
return
if not pRoot:
return True
left=pRoot.left
right=pRoot.right
return issame(left,right)