我想要一个用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);
}
}
}
}
答案 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 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);
}
}
现在,无论何时进行递归调用,调用堆栈都将用来记住我们在代码中的位置以及我们在做什么。然后,我们从顶部重新开始,完成后,该信息将从堆栈中弹出,然后从上次中断的地方继续。
如果要使用自己的堆栈进行迭代,则必须自己完成相同的工作。您必须记住足够多才能继续上次离开的地方。
我们当然必须记住我们在哪个节点上工作,但是也有两个递归调用,因此我们可能必须返回两个可能的位置。当我们从递归调用返回时,则可以:
n->left
的通话,应该继续检查n->right
;或n->right
的通话,应该继续进行n
的最后访问我们可以在堆栈上存储一些额外的信息以区分这两种情况,但这对于此特定算法不是必需的。从上面的描述中,您可以看到我们可以根据返回的节点(n->left
或n->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);
}