C 理解深度优先遍历

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

在给定的BST表示形式下

/****************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)
  • 当先前访问的节点是当前节点的父节点时:
    •如果有一个左撇子,请访问下一个
    •否则,如果有合适的孩子,请访问下一个孩子
    •否则,调用函数并返回父级
  • 当先前访问的节点是当前节点的左子节点时:
    •如果有合适的孩子,请访问下一个孩子
    •否则,ca
    static 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");