准备过互联网公司的服务端岗位面试的人,对于二叉树的三种遍历方式想必是如数家珍。假设以类BinaryTree
定义一棵二叉树html
class BinaryTree: def __init__(self, left, right, value): self.left = left self.right = right self.value = value
实现一个前序遍历的算法即是信手拈来的事情node
def preorder_traversal(tree, func): """前序遍历二叉树的每一个节点。""" if tree is None: return func(tree.value) preorder_traversal(tree.left, func) preorder_traversal(tree.right, func)
随着行业曲率的增大,要求写出不使用递归的版本也没什么过度的python
def iterative_preorder_traversal(tree, func): nodes = [tree] while len(nodes) > 0: node = nodes.pop() func(node) if node.left is not None: nodes.append(node.right) if node.left is not None: nodes.append(node.left)
一直以来,我以为这种用一个显式的栈来代替递归过程当中隐式的栈的作法就是镜花水月。但最近却找到了它的一个用武之地——用于实现iterator
。git
iterator
是个啥?这年头,iterator
已经不是什么新鲜事物了,许多语言中都有支持,维基百科上有一份清单列出了比较知名的语言的iterator
特性。按照Python官方的术语表中的定义,iterator
表示一个数据流,反复调用其__next__
方法能够一个接一个地返回流中的下一项数据。将内置函数iter
做用于list
、str
、tuple
类型的对象,能够得到相应的迭代器github
$ cat get_iter.py # -*- coding: utf8 -*- if __name__ == '__main__': values = [ [1, 2, 3], 'Hello, world!', (True, None), ] for v in values: print('type of iter({}) is {}'.format(v, type(iter(v)))) $ python get_iter.py type of iter([1, 2, 3]) is <class 'list_iterator'> type of iter(Hello, world!) is <class 'str_iterator'> type of iter((True, None)) is <class 'tuple_iterator'>
iterator
一个iterator
对象必需要实现__iter__
和__next__
方法:面试
__iter__
只须要返回iterator
对象自身便可;__next__
负责返回下一个元素。仔细观察一下前文中的iterative_preorder_traversal
函数能够看出:算法
nodes = [tree]
属于初始化逻辑;len(nodes) > 0
用于判断是应当抛出StopIteration
,仍是应当继续返回下一个值(nodes.pop()
);nodes
,好让它能够在下一次调用__next__
的时候有值能够返回的。到这里,iterator
的具体实现代码已经呼之欲出了shell
class BinaryTreePreorderIterator: def __init__(self, root): nodes = [] if root is not None: nodes.append(root) self.nodes = nodes def __iter__(self): return self def __next__(self): if len(self.nodes) == 0: raise StopIteration node = self.nodes.pop() if node.right is not None: self.nodes.append(node.right) if node.left is not None: self.nodes.append(node.left) return node.value
构造一棵这样的满二叉树app
用BinaryTreePreorderIterator
能够正确地打印出每个节点的值函数
if __name__ == '__main__': tree = BinaryTree( BinaryTree( BinaryTree(None, None, 1), BinaryTree(None, None, 3), 2, ), BinaryTree( BinaryTree(None, None, 5), BinaryTree(None, None, 7), 6, ), 4, ) for n in BinaryTreePreorderIterator(tree): print('{}\t'.format(n), end='') # 打印内容为:4 2 1 3 6 5 7
iterator
的优点显然,iterator
比起preorder_traversal
更为灵活——很容易在for-in
循环内添加各类各样的控制逻辑:用continue
跳过一些值,或者用break
提早结束遍历过程。这些在函数preorder_traversal
中作起来会比较别扭。
聪明的你应该已经发现了,大可没必要将preorder_traversal
拆解到一个构造方法和一个__next__
方法中。用generator
写起来明明更加直观
def preorder_generator(tree): """返回一个可以之前序遍历的次序遍历二叉树节点的generator。""" nodes = [tree] while len(nodes) > 0: node = nodes.pop() yield node.value if node.left is not None: nodes.append(node.right) if node.left is not None: nodes.append(node.left)
可是,不少语言并不支持generator
。与之相比,iterator
要亲民得多,更容易移植。例如,即便是Common Lisp这种一贫如洗的语言,也能够实现和Python的iterator
以及for
相似的效果
(in-package #:cl-user) (defpackage #:com.liutos.binary-tree (:use #:cl)) (in-package #:com.liutos.binary-tree) (defclass preorder-iterator () ((nodes :initform nil) (tree :initarg :tree)) (:documentation "前序遍历二叉树的迭代器")) (defmethod initialize-instance :after ((instance preorder-iterator) &key) (with-slots (nodes tree) instance (when tree (push tree nodes)))) (defgeneric next (iterator) (:documentation "返回迭代器的下一个值。")) (define-condition stop-iteration (error) () (:documentation "Python中StopIteration异常的等价物。")) (defmethod next ((iterator preorder-iterator)) (with-slots (nodes) iterator (when (null nodes) (error 'stop-iteration)) (let ((node (pop nodes))) ;; 一个节点的结构为:(值 左子树 右子树) (when (third node) (push (third node) nodes)) (when (second node) (push (second node) nodes)) (first node)))) (defmacro for-in (var iterator &body forms) "将iterator中的值逐个绑定到变量var上,并执行forms中的表达式。" (let ((iter (gensym))) `(let ((,iter ,iterator)) (handler-case (loop (let ((,var (next ,iter))) ,@forms)) (stop-iteration (c) (declare (ignorable c))))))) (defparameter *tree* '(4 (2 (1 nil nil) (3 nil nil)) (6 (5 nil nil) (7 nil nil)))) (defun test-preorder-iterator () "测试前序遍历迭代器。" (for-in n (make-instance 'preorder-iterator :tree *tree*) (format t "~D~C" n #\Tab)))
中序遍历和后序遍历也能够写成迭代器,证实略。