C 理解深度优先遍历
在给定的BST表示形式下C 理解深度优先遍历,c,data-structures,binary-search-tree,tree-traversal,C,Data Structures,Binary Search Tree,Tree Traversal,在给定的BST表示形式下 /****************tree.h ***********************/ typedef void* (*ProcessItem)(void *, void *); 作为学习的一部分,下面是为三次DFS遍历编写的详细算法(用C注释)和相应代码 预序遍历 后序遍历 顺序遍历 其中,BSTNode.c中定义了visitFunc,并且processItem来自用户 static void visitFunc(ProcessItem pro
/****************tree.h ***********************/
typedef void* (*ProcessItem)(void *, void *);
作为学习的一部分,下面是为三次DFS遍历编写的详细算法(用C注释)和相应代码
预序遍历
后序遍历
顺序遍历
其中,
BSTNode.c
中定义了visitFunc
,并且processItem
来自用户
static void visitFunc(ProcessItem processItem, Node *node){
if(node->visit == TRUE){
return;
}
node->visit=true;
processItem(node->key, node->value);
return;
}
背景: 很明显,上面的DFS算法是在线可用的。但是,据我所知,这些算法中所需的细节水平缺失。我认为,上述算法(在C注释中)已经涵盖了这些细节的缺失 重点介绍了所提到的算法(在C注释中)和使用
parent
指针而不是显式堆栈的相应遍历代码
我的问题:
1) 您是否看到所有三次遍历的4步算法和代码中存在逻辑缺陷
2) 在上述代码中,在访问节点之前是否需要进行条件检查
注意:遍历算法初学者
1) 您是否看到所有三次遍历的4步算法和代码中存在逻辑缺陷
我检查了预顺序遍历。看起来不错
然而,正如M Ohem所提到的,visit字段最初可能被设置为false,但在任何遍历之后,它将被设置为true。因此,您将无法再次遍历该树
树遍历通常是使用递归函数完成的,或者如果您想使用迭代函数,则需要使用push-and-pop-to-stack
2) 在上述代码中,在访问节点之前是否需要进行条件检查
是的,在再次访问某个节点之前,必须检查该节点是否已被访问,否则遍历将出错
编辑-关于这一点的进一步解释。
让我们假设我们有一棵树
a
b c
d e
f g
条件是,没有检查预顺序遍历
每次达到第一个if条件,然后继续,首先访问a,然后访问b,然后访问d,然后访问f。在root=f时,您将达到下一个if条件,该条件为false,您将达到最终条件root=root->parent
现在,root=d。由于条件检查不存在,因此再次访问d,这是错误的,然后转到第二个if条件并访问f。再次root=root->parent
和root=d,然后再次打印d
因此,您可以看到遍历是错误的
1) 您是否看到所有三次遍历的4步算法和代码中存在逻辑缺陷
我检查了预顺序遍历。看起来不错
然而,正如M Ohem所提到的,visit字段最初可能被设置为false,但在任何遍历之后,它将被设置为true。因此,您将无法再次遍历该树
树遍历通常是使用递归函数完成的,或者如果您想使用迭代函数,则需要使用push-and-pop-to-stack
2) 在上述代码中,在访问节点之前是否需要进行条件检查
是的,在再次访问某个节点之前,必须检查该节点是否已被访问,否则遍历将出错
编辑-关于这一点的进一步解释。
让我们假设我们有一棵树
a
b c
d e
f g
条件是,没有检查预顺序遍历
每次达到第一个if条件,然后继续,首先访问a,然后访问b,然后访问d,然后访问f。在root=f时,您将达到下一个if条件,该条件为false,您将达到最终条件root=root->parent
现在,root=d。由于条件检查不存在,因此再次访问d,这是错误的,然后转到第二个if条件并访问f。再次root=root->parent
和root=d,然后再次打印d
因此,您看到遍历是错误的。显然,您需要一个非递归且不使用堆栈的二叉树遍历函数。(你在问题中没有明确表示,但你的评论表明。) 让我们来看一个后序遍历。(这个答案可以很容易地扩展到其他深度优先遍历,但PostOrders是最简单的,所以我们只看一下这个。) 递归函数非常简单:
typedef struct Node Node;
struct Node {
int key;
Node *left;
Node *right;
Node *parent;
};
void postorder_rec(const Node *nd, void (*func)(const Node *nd))
{
if (nd) {
postorder_rec(nd->left, func);
postorder_rec(nd->right, func);
func(nd);
};
}
此函数是轻量级的,并且具有良好的递归深度。你说你不会在生产代码中使用递归版本,但是在这样的代码中,你的目标是保持树的平衡,这样你就可以有100万个节点,树(因此递归)深度为20
您已建议向节点添加已访问
标志。这种方法不合适,因为通用将改变树的状态。必须在遍历之前重置此状态,但要执行此操作,必须遍历树。哎哟
你的建议根据树根的状态来对待标志“访问”或“未访问”可能是一种解决方法,但是如果出于某种原因,你想停止中间的遍历,或者在遍历之间添加新的节点,那么它将失败。这种方法不可靠。此外,您始终需要为每个节点携带额外的标志
不过,有一个解决方案:您的树节点有一个指向其父节点的链接。您可以保留到以前访问过的节点的链接,然后根据该链接决定下一步要去哪里:
- 从null的前一个节点开始
- 永远不要访问任何空的子节点。否则,您将无法区分null子节点和根节点的父节点(也是null)
- 当先前访问的节点是当前节点的父节点时:
•如果有一个左撇子,请访问下一个
•否则,如果有合适的孩子,请访问下一个孩子
•否则,调用函数并返回父级 - 当先前访问的节点是当前节点的左子节点时:
•如果有合适的孩子,请访问下一个孩子
•否则,castatic void visitFunc(ProcessItem processItem, Node *node){ if(node->visit == TRUE){ return; } node->visit=true; processItem(node->key, node->value); return; }
a b c d e f g
typedef struct Node Node; struct Node { int key; Node *left; Node *right; Node *parent; }; void postorder_rec(const Node *nd, void (*func)(const Node *nd)) { if (nd) { postorder_rec(nd->left, func); postorder_rec(nd->right, func); func(nd); }; }
void postorder_iter(const Node *nd, void (*func)(const Node *nd)) { const Node *prev = NULL; while (nd) { const Node *curr = nd; if (prev == nd->parent && nd->left) { nd = nd->left; } else if (prev != nd->right && nd->right) { nd = nd->right; } else { func(nd); nd = nd->parent; } prev = curr; } }
typedef struct PostIter PostIter; struct PostIter { const Node *nd; const Node *prev; int count; }; const Node *postorder_next(PostIter *iter) { if (iter->nd == NULL) return NULL; if (iter->count) { (iter->prev) = iter->nd; iter->nd = iter->nd->parent; } while (iter->nd) { const Node *prev = iter->prev; const Node *nd = iter->nd; if (prev == nd->parent && nd->left) { iter->nd = nd->left; } else if (prev != nd->right && nd->right) { iter->nd = nd->right; } else { iter->count++; return nd; } iter->prev = nd; } return NULL; }
PostIter iter = {head}; while (postorder_next(&iter)) { printf("%d -> ", iter.nd->key); } puts("nil");