当我们使用数据结构来管理数据时,一个重要的用例是检查我们管理的所有数据。在本文中,我们将实现遍历二叉树的函数。
本文是用 Python 构建森林系列的第二篇。在本文中,我们不会构建树。相反,我们将实现遍历二叉树的函数。当我们使用数据结构来管理数据时,一个重要的用例是检查我们管理的所有数据。这就是为什么树遍历是必不可少的。
介绍
遍历是一种系统地检查树的节点以准确访问每个节点一次的方法。遍历二叉树的方法有很多种:中序、前序、后序和层序。名称来自根与其子树的相对位置。树遍历也是图遍历的一种形式。有序、前序和后序遍历是深度优先遍历的类型,而级别序遍历是广度优先遍历的类型。本文将以递归和迭代的方式介绍这些遍历函数的实现。
项目设置
与Build the Binary Search Tree相同,实现假定 Python 3.9 或更高版本。此外,我们在项目中添加了两个模块:用于遍历函数的 traversal.py 和用于单元测试的test_traversal.py 。添加这两个文件后,我们的项目布局变成如下:
<span style="color:#000000"><span style="background-color:#fbedbb">Forest-python
├── forest
│ ├── __init__.py
│ ├── binary_trees
│ │ ├── __init__.py
│ │ ├── binary_search_tree.py
│ │ └── traversal.py
│ └── tree_exceptions.py
└── tests
├── __init__.py
├── conftest.py
├── test_binary_search_tree.py
└── test_traversal.py</span></span>
(完整的代码可在forest-python获得。)
功能接口
我们在这里实现的遍历方法尽量通用。理论上,我们可以将遍历方法应用于任何二叉树,只要它们的节点具有左右字段,这是真的。但是,对于不同类型的二叉树,它们的节点可能有额外的信息。例如,线程二叉树节点具有告诉我们该节点是叶还是线程的信息。在这种情况下,当我们遍历一个线程二叉树时,我们需要检查额外的信息以确定我们是否到达叶子。换句话说,我们不能只检查节点的左侧或右侧字段是否为空。
我们不是为每种树类型的不同条件添加逻辑,而是定义支持的树类型,因此客户端(即人类或 linter)将知道是否支持树类型。我们支持的类型定义如下所示:
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">from</span> typing <span style="color:#0000ff">import</span> Union
<span style="color:#0000ff">from</span> forest.binary_trees <span style="color:#0000ff">import</span> binary_search_tree
SupportedNode = Union[<span style="color:#0000ff">None</span>, binary_search_tree.Node]
SupportedTree = Union[binary_search_tree.BinarySearchTree]</span></span>
目前,我们支持类型None
和Node
节点,我们只支持BinarySearchTree
树的类型;当我们实现其他类型的二叉树时,我们会添加更多。
通过类型定义,我们可以利用类型注释来定义我们的遍历函数(以按顺序遍历为例)。
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">from</span> typing <span style="color:#0000ff">import</span> Any, Iterator, Optional
Pairs = Iterator[<span style="color:#339999">tuple</span>[Any, Any]]
<span style="color:#800080">"""</span><span style="color:#800080">Iterator of Key-Value pairs, yield by traversal functions. For type checking"""</span>
<span style="color:#0000ff">def</span> inorder_traverse(tree: SupportedTree, recursive: <span style="color:#339999">bool</span> = <span style="color:#0000ff">True</span>) -> Pairs:
…</span></span>
请注意,我们还Pairs
为返回定义了一个自定义类型。尽管我们需要编写更多的代码,正如Python 之禅所暗示的那样,“显式优于隐式”。这样,客户端可以清楚地看到要输入的正确参数类型以及函数将返回的类型,并且类型检查工具(如mypy)可以防止类型不匹配问题。此外,对于深度优先遍历,我们将以递归和非递归方式实现它们,这就是为什么我们有第二个参数recursive
.
为什么不@overload?
Python 是一种动态类型语言,函数重载在 Python 中没有太大意义。但是我们可以利用类型注释来防止类型不匹配问题。
从 Python 3.5 开始,PEP-484引入了装饰器。根据官方文档,“装饰器允许描述支持多种不同参数类型组合的函数和方法”。听起来不错。但是,它只是为了类型检查器的好处。在运行时,客户端代码仍然可以将任何参数传递给函数。该文档还说:“一个重载示例,它给出了比使用联合或类型变量表达的更精确的类型。” 因此,我们使用Union定义类型。与定义几个-decorated 定义相比,使用的代码也更少。@overload
@overload
SupportedTree
SupportedTree
@overload
为什么返回类型Pairs
是迭代器?
这个想法是将遍历函数实现为生成器。这样做的一个主要好处是,当生成器迭代器返回时,它会从中断处继续。当树很大时,这种方法可以节省大量内存。遍历函数的客户端代码可以一次处理一项。此外,客户端代码将更简单,更易于阅读。
有序遍历
中序遍历通过以下方法访问二叉树,每个节点都可以表示为子树的根。
遍历左子树 访问根 遍历右子树下图演示了中序遍历的思想。黄色部分是根 23 的左子树,绿色部分是根 23 的右子树。而节点旁边的小数字(即1、2、3)表示遍历顺序。
如果二叉树也是二叉搜索树,二叉搜索树属性允许我们通过中序遍历产生排序的顺序键值,如上图所示。
可以使用递归或辅助堆栈来完成二叉树遍历。如上一节所述,我们以递归和非递归方式实现它们。
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">def</span> inorder_traverse(tree: SupportedTree, recursive: <span style="color:#339999">bool</span> = <span style="color:#0000ff">True</span>) -> Pairs:
<span style="color:#0000ff">if</span> recursive:
<span style="color:#0000ff">return</span> _inorder_traverse(node=tree.root)
<span style="color:#0000ff">return</span> _inorder_traverse_non_recursive(root=tree.root)</span></span>
因为我们想隐藏实现细节,所以我们将实际的实现函数 ( _inorder_traverse
and _inorder_traverse_non_recursive
) 命名为前导下划线,这意味着它们是内部函数。
递归实现
由于中序遍历定义,递归遍历是遍历树的最自然方式,可以如下实现:
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">def</span> _inorder_traverse(node: SupportedNode) -> Pairs:
<span style="color:#0000ff">if</span> node:
<span style="color:#0000ff">yield</span> <span style="color:#0000ff">from</span> _inorder_traverse(node.left)
<span style="color:#0000ff">yield</span> (node.key, node.data)
<span style="color:#0000ff">yield</span> <span style="color:#0000ff">from</span> _inorder_traverse(node.right)</span></span>
请注意,我们使用yield from来_inorder_traverse
递归调用。那是因为_inorder_traverse
是发电机;为了允许生成器将其部分操作委托给另一个生成器,我们需要使用yield from
.
非递归实现
我们首先访问左子树,然后是根,以及关于中序遍历的右子树。因此,在访问左子树之前,我们先推右子树,然后再推根。推送顺序是因为堆栈是先进后出的数据结构。访问完左子树后,我们出栈访问根,然后再次出栈访问右子树。重复这些步骤,直到堆栈为空。
为了实现一个中序遍历函数,我们需要一个栈来保存我们稍后要访问的子树的根。在进行非递归遍历时,关键是: 1. 我们什么时候将一个节点压入堆栈并从堆栈中拉出一个节点,以及 2. 在我们遍历时我们什么时候产生(即访问)该节点。
当我们访问的节点有一个右孩子时,我们将其右孩子推入堆栈,然后将该节点推入堆栈。 当一个节点从堆栈中弹出时,如果该节点没有右孩子或其右孩子与顶部节点相同,则我们生成该节点。我们使用 Python 的内置列表作为栈来实现非递归的中序遍历,因为内置列表具有先进后出的能力:list.append(x)
在列表末尾添加一个项目并list.pop()
移除并返回列表中的最后一项。
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">def</span> _inorder_traverse_non_recursive(root: SupportedNode) -> Pairs:
<span style="color:#0000ff">if</span> root <span style="color:#0000ff">is</span> None:
<span style="color:#0000ff">raise</span> StopIteration
stack = []
<span style="color:#0000ff">if</span> root.right:
stack.append(root.right)
stack.append(root)
current = root.left
<span style="color:#0000ff">while</span> True:
<span style="color:#0000ff">if</span> current:
<span style="color:#0000ff">if</span> current.right:
stack.append(current.right)
stack.append(current)
current = current.left
<span style="color:#0000ff">continue</span>
stack.append(current)
current = current.left
<span style="color:#0000ff">else:</span> <span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> current is None</em></span>
<span style="color:#0000ff">if</span> <span style="color:#339999">len</span>(stack) > 0:
current = stack.pop()
<span style="color:#0000ff">if</span> current.right <span style="color:#0000ff">is</span> None:
<span style="color:#0000ff">yield</span> (current.key, current.data)
current = <span style="color:#0000ff">None</span>
<span style="color:#0000ff">continue</span>
<span style="color:#0000ff">else:</span> <span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> current.right is not None</em></span>
<span style="color:#0000ff">if</span> <span style="color:#339999">len</span>(stack) > 0:
<span style="color:#0000ff">if</span> current.right == stack[-<span style="color:#000080">1</span>]:
<span style="color:#0000ff">yield</span> (current.key, current.data)
current = stack.pop() <span style="color:#0000ff">if</span> <span style="color:#339999">len</span>(stack) > <span style="color:#000080">0</span> <span style="color:#0000ff">else</span> <span style="color:#0000ff">None</span>
<span style="color:#0000ff">continue</span>
<span style="color:#0000ff">else:</span> <span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> current.right != stack[-1]:</em></span>
<span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> This case means there are more nodes on the right</em></span>
<span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> Keep the current and go back to add them.</em></span>
<span style="color:#0000ff">continue</span>
<span style="color:#0000ff">else:</span> <span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> stack is empty</em></span>
<span style="color:#0000ff">break</span></span></span>
逆序遍历
当遍历一种二叉搜索树时,中序遍历产生按升序排序的输出。如果以逆序遍历二叉树,则排序结果为降序。为了以相反的顺序遍历,我们首先访问右子树,最后访问左子树。
遍历右子树 访问根 遍历左子树逆序遍历也可以通过递归和非递归的方式实现。并且实现与中序遍历是对称的。
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">def</span> reverse_inorder_traverse(tree: SupportedTree, recursive: <span style="color:#339999">bool</span> = <span style="color:#0000ff">True</span>) -> Pairs:
<span style="color:#0000ff">if</span> recursive:
<span style="color:#0000ff">return</span> _reverse_inorder_traverse(node=tree.root)
<span style="color:#0000ff">return</span> _reverse_inorder_traverse_non_recursive(root=tree.root)</span></span>
递归逆序遍历
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">def</span> _reverse_inorder_traverse(node: SupportedNode) -> Pairs:
<span style="color:#0000ff">if</span> node:
<span style="color:#0000ff">yield</span> <span style="color:#0000ff">from</span> _reverse_inorder_traverse(node.right)
<span style="color:#0000ff">yield</span> (node.key, node.data)
<span style="color:#0000ff">yield</span> <span style="color:#0000ff">from</span> _reverse_inorder_traverse(node.left)</span></span>
非递归逆序遍历
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">def</span> _reverse_inorder_traverse_non_recursive(root: SupportedNode) -> Pairs:
<span style="color:#0000ff">if</span> root <span style="color:#0000ff">is</span> None:
<span style="color:#0000ff">raise</span> StopIteration
stack = []
<span style="color:#0000ff">if</span> root.left:
stack.append(root.left)
stack.append(root)
current = root.right
<span style="color:#0000ff">while</span> True:
<span style="color:#0000ff">if</span> current:
<span style="color:#0000ff">if</span> current.left:
stack.append(current.left)
stack.append(current)
current = current.right
<span style="color:#0000ff">continue</span>
stack.append(current)
current = current.right
<span style="color:#0000ff">else:</span> <span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> current is None</em></span>
<span style="color:#0000ff">if</span> <span style="color:#339999">len</span>(stack) > 0:
current = stack.pop()
<span style="color:#0000ff">if</span> current.left <span style="color:#0000ff">is</span> None:
<span style="color:#0000ff">yield</span> (current.key, current.data)
current = <span style="color:#0000ff">None</span>
<span style="color:#0000ff">continue</span>
<span style="color:#0000ff">else:</span> <span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> current.right is not None</em></span>
<span style="color:#0000ff">if</span> <span style="color:#339999">len</span>(stack) > 0:
<span style="color:#0000ff">if</span> current.left == stack[-<span style="color:#000080">1</span>]:
<span style="color:#0000ff">yield</span> (current.key, current.data)
current = stack.pop() <span style="color:#0000ff">if</span> <span style="color:#339999">len</span>(stack) > <span style="color:#000080">0</span> <span style="color:#0000ff">else</span> <span style="color:#0000ff">None</span>
<span style="color:#0000ff">continue</span>
<span style="color:#0000ff">else:</span> <span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> current.right != stack[-1]:</em></span>
<span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> This case means there are more nodes on the right</em></span>
<span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> Keep the current and go back to add them.</em></span>
<span style="color:#0000ff">continue</span>
<span style="color:#0000ff">else:</span> <span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> stack is empty</em></span>
<span style="color:#0000ff">break</span></span></span>
预购遍历
前序遍历通过以下方法访问二叉树:
访问根。 遍历左子树。 遍历右子树。下图展示了前序遍历的思路。黄色部分是根 23 的左子树,绿色部分是根 23 的右子树。与中序遍历相同,节点旁边的小数字(即1、2、3)表示遍历顺序。
我们还以递归和非递归的方式实现了前序遍历。
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">def</span> preorder_traverse(tree: SupportedTree, recursive: <span style="color:#339999">bool</span> = <span style="color:#0000ff">True</span>) -> Pairs:
<span style="color:#0000ff">if</span> recursive:
<span style="color:#0000ff">return</span> _preorder_traverse(node=tree.root)
<span style="color:#0000ff">return</span> _preorder_traverse_non_recursive(root=tree.root)</span></span>
递归前序遍历
递归实现很简单,按照遍历顺序即可。
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">def</span> _preorder_traverse(node: SupportedNode) -> Pairs:
<span style="color:#0000ff">if</span> node:
<span style="color:#0000ff">yield</span> (node.key, node.data)
<span style="color:#0000ff">yield</span> <span style="color:#0000ff">from</span> _preorder_traverse(node.left)
<span style="color:#0000ff">yield</span> <span style="color:#0000ff">from</span> _preorder_traverse(node.right)</span></span>
非递归前序遍历
关于非递归的实现,下图演示了非递归的前序遍历。
非递归的前序遍历比非递归的中序遍历更简单。由于我们首先访问根目录,因此过程可以看作是步骤:
当我们访问一个节点时,我们将其右孩子推入堆栈(如果有的话),然后将其左孩子推入堆栈(如果有的话)。 当一个节点从堆栈中弹出时,我们生成该节点。
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">def</span> _preorder_traverse_non_recursive(root: SupportedNode) -> Pairs:
<span style="color:#0000ff">if</span> root <span style="color:#0000ff">is</span> None:
<span style="color:#0000ff">raise</span> StopIteration
stack = [root]
<span style="color:#0000ff">while</span> <span style="color:#339999">len</span>(stack) > 0:
temp = stack.pop()
<span style="color:#0000ff">yield</span> (temp.key, temp.data)
<span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> Because stack is FILO, insert right child before left child.</em></span>
<span style="color:#0000ff">if</span> temp.right:
stack.append(temp.right)
<span style="color:#0000ff">if</span> temp.left:
stack.append(temp.left)</span></span>
后序遍历
后序遍历通过以下方法访问二叉树:
遍历左子树。 遍历右子树。 访问根。下图展示了后序遍历的思想。与前面的遍历类似,节点旁边的小数字(即1、2、3)表示遍历顺序。
再次,我们以递归和非递归方法构建后序遍历。
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">def</span> postorder_traverse(tree: SupportedTree, recursive: <span style="color:#339999">bool</span> = <span style="color:#0000ff">True</span>) -> Pairs:
<span style="color:#0000ff">if</span> recursive:
<span style="color:#0000ff">return</span> _postorder_traverse(node=tree.root)
<span style="color:#0000ff">return</span> _postorder_traverse_non_recursive(root=tree.root)</span></span>
递归后序遍历
类似于中序和前序遍历,递归实现很简单。
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">def</span> _postorder_traverse(node: SupportedNode) -> Pairs:
<span style="color:#0000ff">if</span> node:
<span style="color:#0000ff">yield</span> <span style="color:#0000ff">from</span> _postorder_traverse(node.left)
<span style="color:#0000ff">yield</span> <span style="color:#0000ff">from</span> _postorder_traverse(node.right)
<span style="color:#0000ff">yield</span> (node.key, node.data)</span></span>
非递归后序遍历
但是,后序遍历的非递归实现有点复杂。下图以非递归方式演示了后序遍历。
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">def</span> _postorder_traverse_non_recursive(root: SupportedNode) -> Pairs:
<span style="color:#0000ff">if</span> root <span style="color:#0000ff">is</span> None:
<span style="color:#0000ff">raise</span> StopIteration
stack = []
<span style="color:#0000ff">if</span> root.right:
stack.append(root.right)
stack.append(root)
current = root.left
<span style="color:#0000ff">while</span> True:
<span style="color:#0000ff">if</span> current:
<span style="color:#0000ff">if</span> current.right:
stack.append(current.right)
stack.append(current)
current = current.left
<span style="color:#0000ff">continue</span>
<span style="color:#0000ff">else:</span> <span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> current.right is None</em></span>
<span style="color:#0000ff">if</span> current.left:
stack.append(current)
<span style="color:#0000ff">else:</span>
<span style="color:#0000ff">yield</span> (current.key, current.data)
current = current.left
<span style="color:#0000ff">else:</span> <span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> current is None</em></span>
<span style="color:#0000ff">if</span> <span style="color:#339999">len</span>(stack) > 0:
current = stack.pop()
<span style="color:#0000ff">if</span> current.right <span style="color:#0000ff">is</span> None:
<span style="color:#0000ff">yield</span> (current.key, current.data)
current = <span style="color:#0000ff">None</span>
<span style="color:#0000ff">else:</span> <span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> current.right is not None</em></span>
<span style="color:#0000ff">if</span> <span style="color:#339999">len</span>(stack) > 0:
<span style="color:#0000ff">if</span> current.right != stack[-<span style="color:#000080">1</span>]:
<span style="color:#0000ff">yield</span> (current.key, current.data)
current = <span style="color:#0000ff">None</span>
<span style="color:#0000ff">else:</span> <span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> current.right == stack[-1]</em></span>
temp = stack.pop()
stack.append(current)
current = temp
<span style="color:#0000ff">else:</span> <span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> stack is empty</em></span>
<span style="color:#0000ff">yield</span> (current.key, current.data)
<span style="color:#0000ff">break</span>
<span style="color:#0000ff">else:</span> <span style="color:#008000"><em>#</em></span><span style="color:#008000"><em> stack is empty</em></span>
<span style="color:#0000ff">break</span></span></span>
层序遍历
与之前的深度优先遍历不同,层序遍历是广度优先的。在这种情况下,我们在访问较低级别之前访问级别上的每个节点。这个想法如下图所示:
我们不使用栈,而是使用队列来实现层序遍历功能。队列是一种先进先出的数据结构。对于每个节点,首先访问该节点,然后将其子节点放入队列中。从队列中取出一个节点,首先访问该节点,然后将该节点的子节点入队。重复直到队列为空。
我们还使用 Python 的内置列表作为队列来实现级别排序功能,因为内置列表也具有先进先出的能力:list.append(x)
在列表末尾添加一项并list.pop(0)
删除并返回列表中的第一项。
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">def</span> levelorder_traverse(tree: SupportedTree) -> Pairs:
queue = [tree.root]
<span style="color:#0000ff">while</span> <span style="color:#339999">len</span>(queue) > 0:
temp = queue.pop(<span style="color:#000080">0</span>)
<span style="color:#0000ff">if</span> temp:
<span style="color:#0000ff">yield</span> (temp.key, temp.data)
<span style="color:#0000ff">if</span> temp.left:
queue.append(temp.left)
<span style="color:#0000ff">if</span> temp.right:
queue.append(temp.right)</span></span>
测试
与往常一样,我们应该尽可能多地对我们的代码进行单元测试。在这里,我们使用我们在构建二叉搜索树中创建的conftest.pybasic_tree
中的函数来测试我们的遍历函数。在test_traversal.py中,我们可以像下面这样进行单元测试:我们检查遍历输出是否与预期相同(以测试为例)。postorder_traverse
<span style="color:#000000"><span style="background-color:#fbedbb"><span style="color:#0000ff">def</span> test_binary_search_tree_traversal(basic_tree)<span style="color:#808080">:
"""</span><span style="color:#808080">Test binary search tree traversal."""</span>
tree = binary_search_tree.BinarySearchTree()
<span style="color:#0000ff">for</span> key, data <span style="color:#0000ff">in</span> basic_tree:
tree.insert(key=key, data=data)
assert [item <span style="color:#0000ff">for</span> item <span style="color:#0000ff">in</span> traversal.postorder_traverse(tree)] == [
(<span style="color:#000080">1</span>, <span style="color:#800080">"</span><span style="color:#800080">1"</span>), (<span style="color:#000080">7</span>, <span style="color:#800080">"</span><span style="color:#800080">7"</span>), (<span style="color:#000080">15</span>, <span style="color:#800080">"</span><span style="color:#800080">15"</span>), (<span style="color:#000080">22</span>, <span style="color:#800080">"</span><span style="color:#800080">22"</span>), (<span style="color:#000080">20</span>, <span style="color:#800080">"</span><span style="color:#800080">20"</span>), (<span style="color:#000080">11</span>, <span style="color:#800080">"</span><span style="color:#800080">11"</span>),
(<span style="color:#000080">4</span>, <span style="color:#800080">"</span><span style="color:#800080">4"</span>), (<span style="color:#000080">24</span>, <span style="color:#800080">"</span><span style="color:#800080">24"</span>), (<span style="color:#000080">34</span>, <span style="color:#800080">"</span><span style="color:#800080">34"</span>), (<span style="color:#000080">30</span>, <span style="color:#800080">"</span><span style="color:#800080">30"</span>), (<span style="color:#000080">23</span>, <span style="color:#800080">"</span><span style="color:#800080">23"</span>)
]</span></span>
(检查test_traversal.py以获得完整的单元测试。)
分析
深度优先遍历:有序、逆序、前序和后序
运行时按顺序、按顺序保留、前序和后序遍历
在图算法中,深度优先搜索的运行时间为 O(V+E),其中 V 是顶点数,E 是边数。(运行时间为O(V+E)的原因,请参考算法介绍章节22.3)。由于中序、逆序、前序和后序遍历是深度优先遍历的类型,我们可以将这些遍历映射到深度优先搜索。因此,这些遍历的运行时间为 O(V+E)。二叉树中的一个顶点是一个节点。此外,对于二叉树,如果树有 n 个节点,则它必须有 n-1 条边。因此,我们可以将运行时间改写为 O(n+(n-1))=O(2n-1)=O(n),其中 n 是节点数。因此,它们的运行时间变为 O(n)。
关于空间复杂度,我们需要根据我们的方法来分析它:递归还是非递归。
递归方法中的空间复杂度
空间使用是我们进行递归方法时的调用堆栈,即函数调用自身的深度。我们从根到叶层遍历二叉树,所以树的高度决定了函数调用自身的深度。因此,如果一棵二叉树有n个节点,我们知道随机构建树时的平均情况是O(lg n);当树是线性链接时,最坏的情况是 O(n)。
非递归方法中的空间复杂度
如果我们使用非递归方法,空间使用量就是堆栈的大小。与递归方法相同,堆栈大小由树的高度决定。因此,对于一棵有n个节点的二叉树,我们知道随机构建树时的平均情况是O(lg n);当树是线性链接时,最坏的情况是 O(n)。
广度优先遍历:级别顺序
广度优先搜索算法的时间复杂度为 O(V+E),其中 V 是顶点数,E 是边数(详情请参阅算法介绍第 22.2 章)。因此,层序遍历的运行时间也是O(n),其中n为节点数。原因与上面讨论的有序和其余深度优先类型遍历相同。
更多精品课程