【问题标题】:What is the space complexity for an iterative preorder traversal in a Binary tree?二叉树中迭代前序遍历的空间复杂度是多少?
【发布时间】:2018-07-15 10:29:12
【问题描述】:

我一直想知道二叉树的迭代前序遍历(使用堆栈)的空间复杂度是多少。 我参考了编程面试的元素,他们说

空间复杂度为 O(h),其中 h 是树的高度,因为除了栈顶之外,栈中的节点对应于上节点的右子节点从根开始的路径。

以下是参考代码:

struct Node{
   int data;
   struct Node* left, right;
}
void printPreOrder(struct Node* root){
  if(!root)
   return ;
  stack<struct Node* > s;
  s.push(root);
  while(!s.empty()){
     struct Node *root_element = s.top();
     cout<<root_element->data<<" ";
     s.pop();
      if(root_element->right){
         s.push(root_element->right);
      }
      if(root_element->left){
         s.push(root_element->left);
      }
     }
     cout<<endl;
  }
  return ;
}

我的直觉

通过算法时,我观察到在任何情况下堆栈中的最大条目数可以是 max(num_of_leaves_in_left_subtree+1, num_of_trees_in_right_subtree)。 由此我们可以推断,对于高度为 h 的树,最大叶子数可以是 2^h。因此,左子树中的最大树数为 2^(h-1)。因此,堆栈中的最大条目数将为 2^(h-1)+1。因此,根据我的说法,上述算法的空间复杂度为 O(2^(log(n)))。

【问题讨论】:

    标签: algorithm data-structures binary-tree space space-complexity


    【解决方案1】:

    首先,您对preorder traversal 的迭代实现有一个错误——您应该先推右节点,然后推左节点,反之则不行。

    现在解释一下 - 在每次迭代中,您将更深一层,并在弹出一个节点(父节点)的同时向堆栈添加 2 个元素(如果存在,则为左右)。这意味着当您向下 1 级时最多添加 1 个新元素。到达最左边的节点并将其弹出后,您对堆栈中的顶部节点重复相同的过程 -> O(h)

    例如,

          1
         / \
       2     5
      / \   / \
     3   4 6   7
    

    步骤 0:1 被添加到堆栈中 -> O(1)

    第 1 步:删除 1,添加 2 和 5 -> O(2)

    第 2 步:删除 2,添加 3 和 4 -> O(3)

    第 3 步:删除 3 -> O(2)

    第 4 步:删除 4 个 -> O(1)

    第 5 步:删除 5,添加 6 和 7 -> O(2)

    第 6 步:删除 6 个 -> O(1)

    第 7 步:删除 7 个 -> O(0)

    如您所见,空间复杂度始终与树的高度成正比。

    在最坏的情况下(如果树看起来像一个列表),空间复杂度是 O(1) 对于你的实现(正如@Islam Muhammad 指出的那样),因为在 @ 的每次迭代987654325@循环,从堆栈中移除一项并添加一项(只有1child)。

    【讨论】:

      【解决方案2】:

      让我们通过顺序遍历算法来找出它。

      尝试观察以根节点为根的整棵树所需的最大堆栈大小将等于左子树所需的最大堆栈大小加1。

      但是怎么做?

      如果你仔细观察你会发现,当我们处理根节点时,我们会将右节点和左节点添加到堆栈中,然后向堆栈的顶部,即左节点前进。

      所以,如果递归地定义查找所需堆栈的最大大小的函数,它将如下:

      function maxStackSizeReq(struct Node* root){
       if(!root)
          return 0; 
       return maxStackSizeReq(node->left)+1;
      }
      

      现在解释一下 - 在每次迭代中,您将更深一层,并在弹出一个节点(父节点)的同时向堆栈添加 2 个元素(如果存在,则为左右)。这意味着当您向下 1 级时最多添加 1 个新元素。到达最左边的节点并将其弹出后,您对堆栈中的顶部节点重复相同的过程 -> O(h)。

      例如,让我们尝试找出以下树所需的最大堆栈大小。

           1
           / \
         2     5
        / \   / 
       3   4 6   
      

      第0步:调用maxStackSizeReq(root_1) -> 返回maxStackSizeReq(root_2)+1 这里我们的意思是,所需的最大堆栈大小将是左子树 +1 所需的堆栈大小。

        2  
       / \  
      3   4
      

      第 2 步:致电maxStackSizeReq(root_2) - &gt; return maxStackSizeReq(root_3)+1 这里我们的意思是,所需的最大堆栈大小将是左子树 +1 所需的堆栈大小。 3

      第三步:致电maxStackSizeReq(root_3) - &gt; return maxStackSizeReq(root_3-&gt;left which is NULL)+1 这里我们的意思是,所需的最大堆栈大小将是左子树所需的堆栈大小,即NULL + 1。 所以,这将返回 0

      所以,步骤 3 返回 1 -> 步骤 2 返回 2 -> 步骤 1 返回 3;

      因此,最终该函数将返回 3 作为处理以根节点为根的树的前序遍历所需的最大堆栈大小。

      时间复杂度O(h)如何? 好吧,如果您仔细遵循上述算法,您还会观察到我们仅以深度方式遍历树的左子树。因此,上述算法将具有 O(h) 的空间复杂度,因为正在进行 O(h) 递归调用。所以,最后,前序迭代栈实现的空间复杂度也是 O(h)。

      记住

      有时,您可能会听说前序或中序迭代解的空间复杂度为 O(n)。但是,请记住 O(h) 会是一个更好的答案,因为空间复杂度是 O(n) 仅对于倾斜的树,当 h 变为 n 时,这一点非常明显

      1
       \
        2
         \
          3
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-03-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-02-16
        • 1970-01-01
        • 2021-08-25
        • 1970-01-01
        相关资源
        最近更新 更多