基于堆栈的Euler-Tree遍历问题

时间:2018-11-20 21:01:03

标签: algorithm data-structures tree binary-tree tree-traversal

我想要一个用Euler遍历(https://greenido.wordpress.com/2013/12/16/big-query-and-google-spreadsheet-intergration/)遍历二叉树的函数。当然,通过递归可以轻松实现-我知道它是如何工作的。但是现在我想使用堆栈而不是递归来实现该算法的迭代版本。我的想法是将要遍历的方向也存储在堆栈中。我的代码无法正常工作,我无法以某种方式解决这个问题。您能给我一些有关如何解决此问题的提示吗?到目前为止,这是我的代码:

#define LEFT (struct Node*) 0xBADF00D
#define RIGHT (struct Node*) 0xDEADBEEF

struct Node { 
    int data; 
    struct Node* parent; 
    struct Node* left; 
    struct Node* right; 
}; 

void eulerTree(struct Node* root) 
{ 
    stack<struct Node*> s;

    s.push(root);
    s.push(RIGHT);
    s.push(root);
    s.push(LEFT);

    while(!s.empty()) {
        struct Node* direction = s.top(); s.pop();
        struct Node* node = s.top(); s.pop();

        visit(node);

        if(direction == LEFT) {
            if(node->left) {
                s.push(node->left);
                s.push(RIGHT);

                s.push(node->left);
                s.push(LEFT);
            }
        } 

        if(direction == RIGHT) {
            if(node->right) {
                s.push(node->right);
                s.push(RIGHT);

                s.push(node->right);
                s.push(LEFT);
            }
        }
    }
}

2 个答案:

答案 0 :(得分:1)

以一个简单的二叉树开始:

           1
        2     3

对此,Euler遍历为:1 2 1 3 1

您会在此处看到模式: root, root->left, root, root->right, root

因此,您的堆叠顺序应为:

root
root->left
root
root->right
root

但是,如果您的根是一片叶子怎么办?然后什么都不做,只打印值。

此外,一旦向左推动节点,请确保将其根节点设置为0,以免永远永远压下去。

话虽如此,cpp中的代码将是:

编辑:

我之前发布的代码存在错误。正确的代码如下:

void eulerTree(struct Node* root) 
{ 
    stack<struct Node*> s;

    s.push(root);


    while(!s.empty()) {

        struct Node* node = s.pop();

        visit(node);

        if(node->right) {
          s.push(node);
          s.push(node->right);
        }

        if(node->left) {
          s.push(node);
          s.push(node->left);
        }
        node->left = 0;
        node->right = 0;
    }
}

不破坏树:

但是,是的,即使代码很简单,也会破坏不需要的树。为了解决这个问题,我将在欧拉树遍历中对树的叶子使用两个属性。

  1. 如果叶子是父级的左子级,而该父级的右子级为

    (或)

    如果叶子是对的孩子

    -在打印完此叶子之后,将父节点一直打印到根。

  2. 如果叶子是左子元素,而右子元素不为空

    -在打印此叶子之后,仅打印其直接父项。

为说明起见,请看下面的树。

1 2 3 4 5 6 7

如果叶子是5,则在打印后,将所有父项打印到1

如果叶子是4,则在打印后,仅打印其直接父项2

为简化实现,我将在当前堆栈之外使用父堆栈。

void eulerTree(struct Node* root) {
  stack<struct Node*> s;
  s.push(root);
  struct Node* original = root;
  stack<struct Node*> p;

  while(!s.empty()) {
    struct Node* node = s.top();
    s.pop();

    visit(node);

    if ( !node->right && !node->left && !p.empty() ) {
      struct Node* pNode = p.top();
      if ( pNode->left == node && !pNode->right  || pNode->right == node ) {
        while ( !p.empty() ) {
          visit(p.top());
          p.pop();
        }
        p.push(original);
      } else {
        visit(pNode);
      }
    }

    if(node->left || node->right) {
      p.push(node);
    }

    if(node->right) {
      s.push(node->right);
    }

    if(node->left) {
      s.push(node->left);
    }
  }
}

答案 1 :(得分:0)

递归实现可能看起来像这样:

void euler(Node *n) {
    visit(n);
    if (n->left) {
        euler(n->left);
        visit(n);
    }
    if (n->right) {
        euler(n->right);
        visit(n);
    }
}

现在,无论何时进行递归调用,调用堆栈都将用来记住我们在代码中的位置以及我们在做什么。然后,我们从顶部重新开始,完成后,该信息将从堆栈中弹出,然后从上次中断的地方继续。

如果要使用自己的堆栈进行迭代,则必须自己完成相同的工作。您必须记住足够多才能继续上次离开的地方。

我们当然必须记住我们在哪个节点上工作,但是也有两个递归调用,因此我们可能必须返回两个可能的位置。当我们从递归调用返回时,则可以:

  1. 我们刚刚完成了n->left的通话,应该继续检查n->right;或
  2. 我们刚刚完成了n->right的通话,应该继续进行n的最后访问

我们可以在堆栈上存储一些额外的信息以区分这两种情况,但这对于此特定算法不是必需的。从上面的描述中,您可以看到我们可以根据返回的节点(n->leftn->right)来区分这些情况。

因此,只需将等待的节点存储在堆栈中,我们可以编写如下的迭代版本:

int state=0; // 0 => initial visit, 1 => just did left, 2 => just did right
Node *n = root;
while (n) {
    visit(n);

    if (n->left && state<1) {
        stack.push(n);
        n=n->left;
        state=0;
        continue;
    }

    if (n->right && state<2) {
        stack.push(n);
        n=n->right;
        state=0;
        continue;
    }

    if (stack.empty())
        break; // done

    Node *child=n;
    n = stack.pop();
    state = (child == n->left ? 1 : 2);
}