深度优先遍历
从某个节点v出发进行搜索,直到该节点的所有边都遍历完。当该节点所有边被遍历完后,回溯到v的前驱节点,继续搜索这个前驱节点的其他边。
二叉树
二叉树属于树结构,每个节点有两个分支,成为“左子树”“右子树”,每一层最多 个节点。
二叉树遍历顺序
先(前)序遍历:DLR,中序遍历:LDR,后序遍历:LRD
LDR分别为左子树、右子树、根
上图 先序遍历:A-B-D-C-E-F
三种遍历方式都属于深度遍历,广度优先顺序遍历:A-B-C-D-E-F
抓小偷问题(Python构建并调用二叉树结构)
题目:一个绝顶狡猾的小偷瞄准一个高档小区。小区的别墅以二叉树的结构坐落。除了第一栋别墅,每一栋别墅都与另一栋“源头”别墅连接。一旦小偷闯入两栋相接的别墅,警铃就会触发。一旦小偷闯入两栋相接的别墅,警铃就会被触发。二叉树节点存储的各别墅的财产,狡猾的小偷已经计划好了路线,装备在不触发警报的前提下偷最多的钱。别墅保安打算在小偷必经之路上布置警卫,不动声色地抓住小偷。
问题是:小偷会怎么选路线呢
如,下图中,小偷分别选画√处受益最大
思路
如果我们把二叉树分层,小偷不能偷相邻两层的房子,偷了这一个节点,就不能偷连接的上下两层,每一个节点的取舍都关系到其他节点,所以小偷需要统筹全局。
规律——每一个节点的偷值都是:左侧子节点的不偷值+右侧子节点的不偷值+节点的财富值;每一个节点的不偷值都是:左侧子节点的最大值+右侧子节点的最大值
代码
class TreeNode(object):
def __init__(self, x, left=None, right=None):
self.value = x
self.right = right
self.left = left
def rob(self, root): # 该节点最大财富值
a = self.helper(root)
return max(a[0], a[1])
def helper(self, root): # 输出root节点的偷值与不偷值
if(root == None): # 当节点不存在(到底)
return [0, 0]
else:
left = self.helper(root.left) # 递归
right = self.helper(root.right)
robValue = root.value + left[1] + right[1] # 该节点偷值
skipValue = max(left[0], left[1]) + max(right[0], right[1]) # 不偷
return [robValue, skipValue]
A = TreeNode(3)
B = TreeNode(1)
C = TreeNode(2, None, A)
D = TreeNode(3, None, B)
E = TreeNode(3, C, D)
print(E.left.value)
print(E.rob(E))
二叉树中最大路径和
思路
最大的路径和有可能是哪些情况。
第一种是左子树的路径加上当前节点
第二种是右子树的路径加上当前节点
第三种是左右子树的路径加上当前节点(相当于一条横跨当前节点的路径)
第四种是只有自己的路径。
这四种情况只是用来计算以当前节点根的最大路径。
如果当前节点上面还有节点,那它的父节点是不能累加第三种情况的。所以我们要计算两个最大值,
一个是当前节点下的最大路径和,
一个是如果要连接父节点时最大的路径和
我们用前者更新全局最大量,用后者返回递归值就行了。
经过某一节点的最大路径和就一定在这四种情况中 max( case_1,case_2,case_3,case_4 )
ans = max(ans,left+right+root.val,left+root.val,right+root.val,root.val)
联想以前做的maxPathSum,如果前导路径,or 左右子树的最大和,是负数,那就直接当没有左右子树好了
也就是说left_max = max(0,getMax(root.left))
所以上面的max比较中, 可以不去比较4个这么多,直接max(ans,left+right+root.val)就好了,这样就已经
囊过上面的4种所有情况的比较了,此时便可以得到,如果以某个节点为最大路径和的一部分时的最大值
❗️❗️❗️:
要注意函数的返回值,函数返回的是代表当前节点作为头部节点的情况下,子树路径的最大和,譬如下面这个树
-10
/ \
9 20
/ \
15 7
20这个节点,在计算其为最大路径一部分的时候,当然会考虑20在路径中了,
也就是:左15 根20 右7
但是由于路径上是不能有回溯的,每个节点一定是遍历一次,即不可能有-10 20 15 20 7 这样的路径,如果
-10 要把20 15 7都囊括进来的话,是不对的。无法将路径拉成一条平的path,一条平的path遍历时一定是没有回溯
关系的。比如9,-10,20,7或者9,-10,20,15这样才是合法的路径
所以当前函数执行完时,应该返回max(left,right) + root.val,即以当前节点作为一段序列和的末尾,这个
节点的左子树或者右子树作为其前导or(后导)路径,并且只能选择一条,所以是max(left,right) + root.val
代码
class TreeNode(object):
def __init__(self, x, left=None, right=None):
self.value = x
self.right = right
self.left = left
class Solution(object):
def __init__(self):
self.result = float("-inf")
def maxPathSum(self, root:TreeNode):
if root == None:
return 0
self.getMax(root)
return self.result
def getMax(self, root:TreeNode):
if root == None:
return 0
left = max(0, self.getMax(root.left)) # 若正数则取,否则不取(0)
right = max(0, self.getMax(root.right))
self.result = max(self.result, left+right+root.value)
# 第二项为算上该节点的最大值,
return max(right, left) + root.value
# 返回的应该是该节点作为结尾点的最大值(不可回溯,所以要往上迭代)
# 此时result>=返回值,因为result是可以直接当结果的
# 而返回值需要进行下一步迭代,该节点不能为中间点,只能为端点
si = TreeNode(10)
wu = TreeNode(12)
liu = TreeNode(2)
qi = TreeNode(1)
er = TreeNode(2, si, wu)
san = TreeNode(-2, liu, qi)
yi = TreeNode(1, er, san)
a = Solution()
print(a.maxPathSum(yi))