python深度优先算法——二叉树、小偷问题、二叉树中的最大路径和、最大的岛屿实现实例

 

本节涉及问题:

  1. 什么是深度优先遍历
  2. 什么是二叉树、二叉树的类型、二叉树的遍历
  3. 怎么抓住小偷:树的深度优先搜索问题
  4. 二叉树中的最大路径和:树的深度优先搜索问题
  5. 最大的岛屿:连通性问题

 

深度优先遍历算法:经典的图论算法,从某个节点v出发开始进行搜索,不断搜索直到该节点的所有边都被遍历完。当节点v的所有边都被遍历以后,深度优先遍历算法则需要回溯到v的前驱节点,来继续搜索这个前驱节点的其他边。

如果还存在尚未被遍历的节点,则深度优先遍历算法将会按照统一的规则从这些剩下的节点中选择一个节点再重复同样的遍历过程。这样的搜素过程从最开始的节点一直持续到最后一个节点的所有边都遍历完。

注意:深度优先遍历问题一定要按照规则尝试所有的可能才行。

 

二叉树是一种特殊的数据结构。常见的数据结构包含数组、链表、图、队列、散列表与树。二叉树属于树结构。二叉树中的每一个节点都有两个分支,称为“左子树”与“右子树”。二叉树中的每一层最多有2^n-1个节点。

 

二叉树的类型

  1. 空二叉树:有零个节点
  2. 满二叉树:每一个节点都有零或两个子节点,即没有只有一个子节点的节点
  3. 完美二叉树:每一层的节点都是满的(国内完美和满二叉树是一样的,这里按照国外定义)
  4. 完全二叉树:除了最后一层,每一层的节点都是满的,并且最后一层的节点全部从左排序。
  5. 平衡二叉树:每个节点的两个子树的深度相差不超过1

   

二叉树的遍历顺序

二叉树三种遍历顺序:DLR(先序)、LDR(中序)、LRD(后序)。遍历的意思是不重复地走遍二叉树的所有节点。LRD分别代表左子树、右子树和根。先、中、后代表的是根节点的位置。

 

深度优先遍历与广度优先遍历

先序、中序和后序遍历都属于深度优先遍历。在深度优先遍历中,从根节点出发直奔最远的节点。在广度优先遍历中,先访问离根节点最近的节点,按层递进。

一、二叉树的节点代码

class Node:          # 二叉树节点的定义
    def __init__(self,x):
        self.val = x           # 节点值
        self.left = None       # 左侧子节点
        self.right = None      # 右侧子节点

二、小偷问题

二叉树别墅小区的小偷

题目:一个绝顶狡猾的小偷瞄准一个高档小区。小区的别墅以二叉树的结构坐落。除了第一栋别墅,每一栋别墅都与另一栋“源头”别墅连接。一旦小偷闯入两栋相接的别墅,警铃就会触发。一旦小偷闯入两栋相接的别墅,警铃就会被触发。二叉树节点存储的各别墅的财产,狡猾的小偷已经计划好了路线,装备在不触发警报的前提下偷最多的钱。别墅保安打算在小偷必经之路上布置警卫,不动声色地抓住小偷。问题是:小偷会怎么选路线呢?

    解题思路:规律——每一个节点的偷值都是:左侧子节点的不偷值+右侧子节点的不偷值+节点的财富值;每一个节点的不偷值都是:左侧子节点的最大值+右侧子节点的最大值。即利用递归来深度优先遍历二叉树来解决。

 

# 二叉树别墅小区的小偷代码实现
class Node:          # 二叉树节点的定义
    def __init__(self,x):
        self.val = x           # 节点值
        self.left = None       # 左侧子节点
        self.right = None      # 右侧子节点
    
    def rob(self,root):
        a = self.helper(root)  # a是一个二维数组,为root的[偷值,不偷值]
        return max(a[0],a[1])  # 返回两个值中的最大值,此值为小偷最终获得的财富值
    
    def helper(self,root):
        '''
        参数:root节点
        返回值:一个二维数组,root节点的[偷值,不偷值]
        '''
        if root == None:       # 如果root节点为空,输出[0,0]
            return [0,0]
        left = self.helper(root.left) # left是一个二维数组,为root左侧子节点的[偷值,不偷值]
        right= self.helper(root.right)# right是一个二维数组,为root右侧子节点的[偷值,不偷值]
        robValue = root.val + left[1] + right[1]               #root的偷值
        skipValue= max(left[0],left[1])+max(right[0]+right[1]) #root的不偷值
        return [robValue,skipValue] # 输出小偷可获得的最大金额

 

# House Robber I:二叉树,相邻边不能被偷

class Solution(object):
    def rob(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        dic = {}
        dic_1 = {}
        def dfs(root,state):
            if root == None:
                return 0
            if root.left not in dic_1:
                dic_1[root.left] = dfs(root.left,0)
            if root.right not in dic_1:
                dic_1[root.right] = dfs(root.right,0)
            ans2 = dic_1[root.left]+dic_1[root.right]
            if state == 0: # can select or not:
                if root.left not in dic:
                    dic[root.left] = dfs(root.left,1)
                if root.right not in dic:
                    dic[root.right] = dfs(root.right,1)
                ans1 = root.val + dic[root.left] + dic[root.right] 
                return max(ans1,ans2)
            else: # selected 
                return ans2
        return dfs(root,0)

 

# House Robber II:小偷不能偷相邻的房子,求最大收益

class Solution(object):
    def rob(self, nums):
        size = len(nums)
        if size == 0: return 0
        if size <=2: return max(nums)
        Values = [nums[0],max(nums[0],nums[1])]
        for i in range(2,size):
            Values.append(max(Values[i-2]+nums[i],Values[i-1]))
        return max(Values)

 

# House Robber III:房子首尾相连,不能偷相邻的房子,求最大的收益

class Solution(object):
    def subrob(self,nums):
        Num_=len(nums)
        if Num_==1:
            return nums[0]
        Value=[nums[0],max(nums[0],nums[1])]
        for i in range(2,Num_):
            Value.append(max(Value[i-2]+nums[i],Value[i-1]))
        return max(Value)

    def rob(self,nums):
        if not len(nums):
            return 0
        if len(nums)==1:
            return nums[0]
        num1=nums[:len(nums)-1]
        num2=nums[1:len(nums)]
        max_=self.subrob(num1)
        return max(max_,self.subrob(num2))

 

三、二叉树中的最大路径和

解题思路

深度优先遍历的想法是首先遍历最底层的节点,然后逐步上移到根节点。每遍历一个节点,输出两个值以便之后的节点做决定,一个是停值,一个是不停值。工作即是找到一个适用于所有节点的停值与不停值的输出方法。root节点的不停值是以下三个值中的最大值:root、root值+左子节点的不停值、root+右子节点的不停值。root节点的停值是以下五个值中的最大值:左子节点的不停值、左子节点的停值、右子节点的不停值、右子节点的停值、root值+左右子节点的不停值。

# 实现代码:
class Solution:          # 二叉树节点的定义
    def __init__(self,x):
        self.val = x           # 节点值
        self.left = None       # 左侧子节点
        self.right = None      # 右侧子节点
        
    def maxPathSum(self,root):        #输出最大路径和的方法
        return max(self.helper(root)) #helper方法,传入根节点,输出返回两个值中的最大值
    
    def helper(self,root):            #helper方法,输出一个二维数组[不停值,停值]
        if root==None:                #如果节点为空,输出两个最小值
            return [float('-int'),float('-int')]
        leftY,leftN = self.helper(root.left)     #得到左子节点的不停值与停值
        rightY,rightN = self.helper(root.right)     #得到右子节点的不停值与停值
        yes = max(root.val+leftY,root.val,root.val+rightY)        #不停值
        no = max(leftY,leftN,rightY,rightN,root.val+leftY+rightY) #停值
        return [yes,no]              # 输出[不停值,停值]

四、最大的岛屿

    深度优先算法的本质就是“一条路走到黑”,直到不能走才换另一条路,有点“不撞南墙不回头”的意思。

# 实现代码:
class Solution:          # 二叉树节点的定义
    def __init__(self,x):
        self.val = x           # 节点值
        self.left = None       # 左侧子节点
        self.right = None      # 右侧子节点
        
    def maxAreaOfIsland(self,grid):
        self.maxArea = 0           #存储最大岛屿的面积
        row = len(grid)            #存储地图的行数
        col = len(grid[0])         #存储地图的列数
        for i in range(row):
            for j in range(col):   #开始从左到右、从上到下地搜索岛屿
                if grid[i][j] == 1:#如果发现了陆地
                    current = 1    # current存储目前岛屿的面积,grid为地图
                    self.dfs(i,j,current,grid) #测量岛屿面积,核心代码
        return self.maxArea        #最后返回最大岛屿的面积
    
    # 定义一个函数进行深度优先遍历算法,从四个方向进行递归调用,避免越界和重复访问
    def dfs(self,x,y,current,grid):
        # x,y 为点的横纵坐标
        # current存储目前岛屿的面积,grid为地图
        grid[x][y] = 2                               #第一步先标记此处为已访问
        if x>0 and grid[x-1][y] == 1:                #向上走前先考查是否越界并且是否为陆地
            current = self.dfs(x-1,y,current+1,grid) #递归调用函数,更新x左边和当前面积
        if x0 and grid[x][y-1] == 1:                #向左走前先考查是否 越界并且是否为陆地
            current = self.dfs(x,y-1,current+1,grid)
        if y

参考书籍:《你也能看得懂的Python算法书》/王硕等编著.—北京:电子工业出版社,2018.11

参考博客:https://blog.csdn.net/tinkle181129/article/details/79546671

 

你可能感兴趣的:(算法)