判断resultset是否遍历到最后一条记录_看完这篇,你会理解图和树的遍历

06d7476eb21ae7d62ce0ade7da7a586a.png

图和树的遍历是必会的算法之一,本文旨在帮助大家统一理解、灵活掌握图和树的遍历。

快速学习一个非常重要的策略就是,对繁杂的知识,进行统一划归,并进一步简化,去伪存真。因为除非你是智力超群的那一撮人,大部分人学习慢都是因为被杂乱的知识扰乱了精力,任何一种知识,只要你能抓最主要的矛盾,掌握最核心的概念,你就能快速学习。这也是在硅谷大热的“第一性原理”(据说 Elon Musk 是第一性原理的超级信徒)。

★ “每个系统中存在一个最基本的命题,它不能被违背或删除。” -- 亚里士多德 ”

树与图的联合理解

根据这一原理,我们暂时不需要分树和图,因为树其实是图的一种特殊情况。我们直接看图的知识点。

总所周知,图有两种遍历方式,深度优先遍历(DFS)和广度优先遍历(BFS),我们一个一个来看。

深度优先遍历

深度优先遍历可以用 Stack 来实现,具体实现基本上可以分这么几步:

  1. 如果栈不空,出栈得到当前节点
  2. 查看当前节点左右子树,如果不为空,入栈

可以说非常简单了,下面是一个简单实现:

class Solution(object):
    def dfs(self, root):
        stack = [(root, 0)]  # 记录当前节点的深度,当然你也可以记录任何你想记录的东西
        while stack:   # 如果栈不为空
            node, depth = stack.pop()  # 出栈

            if node is not None:
                stack.append((node.left, depth+1)) # 入栈
                stack.append((node.right, depth+1)) # 入栈

        return

整个代码非常简洁。需要注意的是,在 Python里面,一个简单实现 Stack 的方式就是直接用 List,入栈就是 list.append()(从栈尾入栈),出栈是 list.pop()(从栈尾出栈)。

这个时候,我们可能会回想到,对于树来说,前中后序遍历能不能统一进来?能,其实前中后序遍历都可以划归到深度优先遍历里面去,只是访问的顺序的问题。

前序遍历

上面的那个代码,不用改就是前序遍历的代码:

class Solution(object):
    def dfs(self, root):
        stack = [(root, 0)]  # 记录当前节点的深度,当然你也可以记录任何你想记录的东西
        while stack:   # 如果栈不为空
            node, depth = stack.pop()  # 出栈
            # 前序遍历的操作在这里
            if node is not None:
                stack.append((node.right, depth+1)) # 入栈
                stack.append((node.left, depth+1)) # 入栈
                # 注意这里的入栈顺序会直接影响到最后的结果
                # 一般来说,我们是让左节点最后入栈,这样出栈的时候,能优先出栈

        return

写成递归形式,大体长这样

def preorder(node):
  # 前序遍历的操作在这里
  preorder(node.left)
  preorder(node.right)

中序遍历

中序遍历的实现跟上面相比,稍微有点复杂,但是只要想清楚了,其实只是比上面前序遍历复杂了一点点。

首先,我们要知道为什么要使用栈来实现 DFS,因为在 DFS 里面,我们要一直往深度探索,这个没问题(入栈)。但是当我们遇到叶子节点要回溯的时候,一定是从上一个最新深度为起点接着探索(出栈)。而这些前中后序就是不同的出栈规则(操作总是紧跟出栈)。

在前序遍历中,每当我们到一个新节点的时候,就立即将其出栈,然后把他的孩子节点入栈。那么对于中序遍历,可以想象到,每当我们到达一个新节点的时候,我们要做一个判断:

  1. 如果他的左节点不是空的,那么我们就不能出栈,而是要继续把他的左节点放进来
  2. 如果他的左节点是空的,这个时候可以出栈了,但是记得出栈完把自己的右节点放进来。

非递归代码如下,

class Solution(object):
    def dfs(self, root):
        stack = []  # 记录当前节点的深度,当然你也可以记录任何你想记录的东西
        while (len(stack)>0) or (root is not None):   # 如果栈不为空
            if root is not None:
            	stack.append(root)
            	root = root.left
            else:  # 如果左节点是空
            	root = stack.pop()
              # 中序遍历的操作在这里
              root = root.right
        return

递归实现就很简单了:

def preorder(node):
  preorder(node.left)
  # 中序遍历的操作在这里
  preorder(node.right)

后序遍历

后序遍历还要更复杂一些,因为后序遍历的定义就是,只有当一个节点的左右节点都访问过了,才能访问这个节点。

思路是: 先将当前节点的所有左侧子结点压入栈,现在要保证在访问当前节点的右子结点之后才能访问当前节点。所以每次从栈中拿出节点时,都需要判断该节点的右子树是否存在或右子树是否被访问过,这里使用了一个 preNode 来记录刚被访问过的节点,这样就可以实现只有当前节点的右子结点访问完成,才能访问当前节点。

class Solution(object):
    def dfs(self, root):
        stack = []  # 记录当前节点及其深度,当然你也可以记录任何你想记录的东西
        node = root # 当前节点
        preNode = None # 上一个被访问的节点
        while (len(stack)>0) or (root is not None):   # 如果栈不为空
            while node is not None:
            	stack.append(root)
            	root = root.left
            if len(stack) > 0:
            	tmp = stack[-1].right  # 这个地方不能直接出栈,因为栈顶位置不一定能能方法,还需要判断其右节点被已经被访问过了
            	if (tmp is None) or (tmp==preNode):
              	node = stack.pop()
              	# 中序遍历的操作在这里
              	preNode = node
                node=None
              else:
              	node = tmp  # 处理右节点
        return

递归实现就很简单了:

def preorder(node):
  preorder(node.left)
  preorder(node.right)
  # 后序遍历的操作在这里

广度优先遍历

广度优先遍历和深度优先相比,只需要把 Stack 换成 Queue。

from collection import deque
class Solution(object):
    def dfs(self, root):
        queue = deque([(root, 0)])  # 记录当前节点的深度,当然你也可以记录任何你想记录的东西
        while queue:   # 如果队列不为空
            node, depth = queue.popleft()  # 出队列

            if node is not None:
                queue.append((node.left, depth+1)) # 入队列
                queue.append((node.right, depth+1)) # 入队列

        return

特点总结

深度优先

以中序遍历为例:某个节点被访问时,其左子树一定已经全部被访问,其右子树一定没有被访问。(“深度”对应“子树”的概念。)

广度优先(层次遍历):

从根节点开始,访问顺序一定是按照深度递增的。

实战习题

这两题都是 FaceBook 高频题目。

习题 1

5dbd1f6d9116ee365c0d852915bb34bd.png

对树进行搜索(可以广度优先,也可以前序深度优先),在搜索的时候,记录节点和其对应的宽度。例如,根节点的宽度是 0,然后对其左节点,入队列,并将其宽度置为 -1,右节点也入队列,宽度置为 +1。同理遍历整个树即可。

class Solution:
    def verticalOrder(self, root: TreeNode) -> List[List[int]]:
        if root is None:
            return []
        from collections import deque, defaultdict

        res_dict = defaultdict(list)

        queue = deque([(root, 0)])

        while queue:
            node, vert = queue.popleft()

            res_dict[vert].append(node.val)

            # if node is not None:
            if node.left is not None:
                queue.append([node.left, vert-1])
            if node.right is not None:
                queue.append([node.right, vert+1])


        return [res_dict[i] for i in sorted(res_dict.keys())]

习题 2

817176a89393ca4696aa875831f24a6c.png

上面的题是树中每个宽度(水平方向)上的问题,这一题就是每个深度(竖直方向)上的问题。我们只需要找到每个深度上面最右边的那个节点即可。

DFS 或者 BFS 都可以,但是 BFS 实现起来简单一些,因为只需要记录每个深度上最后一个节点即可。

from collections import deque

class Solution(object):
    def rightSideView(self, root):
        rightmost_value_at_depth = dict() # depth -> node.val
        max_depth = -1

        queue = deque([(root, 0)])
        while queue:
            node, depth = queue.popleft()

            if node is not None:
                # maintain knowledge of the number of levels in the tree.
                max_depth = max(max_depth, depth)

                # overwrite rightmost value at current depth. the correct value
                # will never be overwritten, as it is always visited last.
                rightmost_value_at_depth[depth] = node.val

                queue.append((node.left, depth+1))
                queue.append((node.right, depth+1))

        return [rightmost_value_at_depth[depth] for depth in range(max_depth+1)]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值