二叉树迭代器算法

开发 后端 算法
二叉树(Binary Tree)的前序、中序和后续遍历是算法和数据结构中的基本问题,基于递归的二叉树遍历算法更是递归的经典应用。

二叉树(Binary Tree)的前序、中序和后续遍历是算法和数据结构中的基本问题,基于递归的二叉树遍历算法更是递归的经典应用。

假设二叉树结点定义如下:

  1. struct Node { 
  2.     int value; 
  3.     Node *left; 
  4.     Node *right; 

 

  1. void inorder_traverse(Node *node) { 
  2.     if (NULL != node->left) { 
  3.         inorder_traverse(node->left); 
  4.     } 
  5.     do_something(node); 
  6.     if (NULL != node->right) { 
  7.         inorder_traverse(node->right); 
  8.     } 
  9.  

前序和后序遍历算法类似。

但是,仅有遍历算法是不够的,在许多应用中,我们还需要对遍历本身进行抽象。假如有一个求和的函数sum,我们希望它能应用于链表,数组,二叉树等等不同的数据结构。这时,我们可以抽象出迭代器(Iterator)的概念,通过迭代器把算法和数据结构解耦了,使得通用算法能应用于不同类型的数据结构。我们可以把sum函数定义为:

  1. int sum(Iterator it) 

链表作为一种线性结构,它的迭代器实现非常简单和直观,而二叉树的迭代器实现则不那么容易,我们不能直接将递归遍历转换为迭代器。究其原因,这是因为二叉 树递归遍历过程是编译器在调用栈上自动进行的,程序员对这个过程缺乏足够的控制。既然如此,那么我们如果可以自己来控制整个调用栈的进栈和出栈不是就达到 控制的目的了吗?我们先来看看二叉树遍历的非递归算法:

  1. void inorder_traverse_nonrecursive(Node *node) { 
  2.     Stack stack; 
  3.     do { 
  4.         // node代表当前准备处理的子树,层层向下把左孩子压栈,对应递归算法的左子树递归 
  5.         while (NULL != node) { 
  6.             stack.push(node); 
  7.             node = node->left; 
  8.         } 
  9.         do { 
  10.             Node *top = stack.top(); 
  11.             stack.pop(); //弹出栈顶,对应递归算法的函数返回 
  12.             do_something(top); 
  13.             if (NULL != top->right) { 
  14.                 node = top->right; //将当前子树置为刚刚遍历过的结点的右孩子,对应递归算法的右子树递归 
  15.                 break
  16.             } 
  17.         } 
  18.         while (!stack.empty()); 
  19.     } 
  20.     while (!stack.empty()); 

通过基于栈的非递归算法我们获得了对于遍历过程的控制,下面我们考虑如何将其封装为迭代器呢? 这里关键在于理解遍历的过程是由栈的状态来表示的,所以显然迭代器内部应该包含一个栈结构,每次迭代的过程就是对栈的操作。假设迭代器的接口为:

  1. class Iterator { 
  2.     public
  3.         virtual Node* next() = 0; 
  4. }; 

下面是一个二叉树中序遍历迭代器的实现:

  1. class InorderIterator : public Iterator { 
  2.     public
  3.         InorderIterator(Node *node) { 
  4.             Node *current = node; 
  5.             while (NULL != current) { 
  6.                 mStack.push(current); 
  7.                 current = current->left; 
  8.             } 
  9.         } 
  10.         virtual Node* next() { 
  11.             if (mStack.empty()) { 
  12.                 return NULL; 
  13.             } 
  14.             Node *top = mStack.top(); 
  15.             mStack.pop(); 
  16.             if (NULL != top->right) { 
  17.                 Node *current = top->right; 
  18.                 while (NULL != current) { 
  19.                     mStack.push(current); 
  20.                     current = current->left; 
  21.                 } 
  22.             } 
  23.             return top; 
  24.          } 
  25.     private
  26.         std::stack<Node*> mStack; 
  27. }; 

下面我们再来考察一下这个迭代器实现的时间和空间复杂度。很显然,由于栈中最多需要保存所有的结点,所以其空间复杂度是O(n)的。那么时间复杂度 呢?一次next()调用也最多会进行n次栈操作,而整个遍历过程需要调用n次next(),那么是不是整个迭代器的时间复杂度就是O(n^2)呢?答案 是否定的!因为每个结点只会进栈和出栈一次,所以整个迭代过程的时间复杂度依然为O(n)。其实,这和递归遍历的时空复杂度完全一样。

除了上面显式利用栈控制代码执行顺序外,在支持yield语义的语言(C#, Python等)中,还有更为直接的做法。下面基于yield的二叉树中序遍历的Python实现:

  1. // Python 
  2. def inorder(t): 
  3.     if t: 
  4.         for x in inorder(t.left): 
  5.             yield x 
  6.         yield t.label 
  7.         for x in inorder(t.right): 
  8.             yield x 

yield与return区别的一种通俗解释是yield返回时系统会保留函数调用的状态,下次该函数被调用时会接着从上次的执行点继续执行,这是一种与 栈语义所完全不同的流程控制语义。我们知道Python的解释器是C写的,但是C并不支持yield语义,那么解释器是如何做到对yield的支持的呢? 有了上面把递归遍历变换为迭代遍历的经验,相信你已经猜到Python解释器一定是对yield代码进行了某种变换。如果你已经能够实现递归变非递归,不 妨尝试一下能否写一段编译程序将yield代码变换为非yield代码。

原文链接:http://coolshell.cn/articles/9886.html

责任编辑:陈四芳 来源: 酷壳网
相关推荐

2021-09-29 10:19:00

算法平衡二叉树

2020-04-27 07:05:58

二叉树左子树右子树

2020-09-23 18:25:40

算法二叉树多叉树

2021-09-15 07:56:32

二叉树层次遍历

2020-12-30 08:35:34

贪心算法监控

2021-09-28 06:28:51

二叉树公共祖先

2021-04-19 07:47:42

数据结构二叉树Tree

2021-04-20 08:37:14

数据结构二叉树

2021-03-17 08:19:22

二叉树LeetCode

2020-12-22 08:56:51

JavaScript数据结构前端

2009-08-11 13:29:57

C#二叉树遍历

2021-04-28 20:12:27

数据结构创建

2020-11-02 09:15:47

算法与数据结构

2022-10-26 23:58:02

二叉树数组算法

2021-05-06 17:46:30

二叉树数据结构

2023-05-08 15:57:16

二叉树数据结构

2021-03-22 08:23:29

LeetCode二叉树节点

2021-08-27 11:36:44

二叉树回溯节点

2021-07-16 08:57:31

迭代遍历二叉树

2018-03-15 08:31:57

二叉树存储结构
点赞
收藏

51CTO技术栈公众号