【问题标题】:Changing root of tree and pruning改变树根和修剪
【发布时间】:2017-09-13 15:46:46
【问题描述】:

我有一个 n 叉树结构,就像答案 here 中解释的那样:

typedef struct node {
    int ID;
    struct node *child;   // points to (first) child
    struct node *next;    // points to next node at same level (sibling)
    struct node *parent;  // points to parent
} node;

节点是使用malloc 创建的。

我想编写一个函数,将树的根更改为另一个(指定)节点,并释放所有节点的内存 - 由于根的更改 - 不再是树的一部分(即如果一个节点不能通过父节点追踪到新的根节点,它应该设置为NULL,并且它的内存应该被释放)。

例如,如果这是我的树:

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

我想把root从1改成3,然后调用prune_tree函数后,root就是3,除3和6之外的所有节点的内存都被释放了。

我最接近解决我的问题涉及到这个功能:

void prune_tree(node **root, node *new_root) {
    if (*root == NULL || (*root)->parent == new_root)
        return;

    prune_tree(&((*root)->child), new_root);
    prune_tree(&((*root)->next), new_root);

    free(*root);
    *root = NULL;
}

调用这个函数后我设置了

root = new_root;

我来到这里主要是通过反复试验;事实上,我什至不确定为什么在我测试它的大部分时间它都有效。它还添加了可能不必要的步骤,即在调用函数后必须将根设置为新的根地址。我假设有一种方法可以修改函数中的根地址,或者返回新的根地址。

我认为我不需要担心内存使用,因为该函数正在释放内存,但更可取的是省时的函数。我不确定这是否意味着我应该避免递归......

【问题讨论】:

  • 对于返回新地址的方法,您可以使用调用递归prune_tree & 返回新根的包装函数。然后将调用包装函数而不是原来的prune_tree

标签: c recursion tree


【解决方案1】:

递归函数将是完美的。

您基本上想要做的(假设树中没有循环)是删除新根的所有兄弟姐妹,以及它们的孩子和父母以及根的所有父母。

算法可以是:

typedef struct node {
    int ID;
    struct node *child;
    struct node *sibling;
    struct node *parent;
} node;

void remove_node(struct node* node, struct node* root)
{
    if(node->parent != NULL)
        remove_node(node->parent, new_root)

    if(node->sibling != NULL)
        remove_node(node->sibling, new_root)

    if((node->child != NULL) && (node->child != root))
        remove_node(node->sibling, new_root)

    free(node)
}

void prune_tree(node **root, node *new_root) {
    *root = new_root
    remove_node(new_root->parent);
    remove_node(new_root->sibling);
}

假设:

  • new_root 在树中
  • 树中没有循环

【讨论】:

  • 这在逻辑上类似于OP的递归函数,一定是错误的,因为你最终会删除新根的节点。
  • 我已修复,您可以通过将节点设置为 NULL 来移除整棵树
  • 不幸的是,正如 OP 所指出的,这个更新的答案存在问题。到达new_root 后遍历停止。添加了一个新的正确答案。等待用户 @chqrlie 也批准更改以获得更好的代码版本。
【解决方案2】:

我想我找到了解决办法……

void prune_tree(node** root, node* new_root)
{
    bool del = true;

    if (*root == NULL)
        return;

    if (*root == new_root) {
        del = false;
    }

    if (del){
        prune_tree(&(*root)->child, new_root);
        prune_tree(&(*root)->next, new_root);
        free(*root);
        *root = NULL;
    }
    else{
        prune_tree(&(*root)->next, new_root);
    }
}

【讨论】:

  • 我对@chqrlie 的回答提出的更改在逻辑上与此等价,但以更优雅的方式并且没有代码冗余。由于那个无用的del 变量,你可以摆脱你在那里所做的那个丑陋的if else 事情。如果@chqrlie 批准我建议的更改,您将看到更好的代码。
【解决方案3】:

您的方法不起作用,因为您将释放新根本身。您只在 parent 是新根的节点上停止。

这是一个更正的版本:

void prune_tree(node **root, node *new_root) {
    if (*root == NULL)
        return;
    if (*root == new_root) {
        (*root)->parent = NULL;
        return;
    }
    prune_tree(&(*root)->child, new_root);
    prune_tree(&(*root)->next, new_root);
    free(*root);
    *root = NULL;
}

在使用地址调用此函数后,您仍需要将树的根更新为new_root

prune_tree(&root, new_root);
root = new_root;

【讨论】:

  • 谢谢。调用这个函数后,是root = new_root,还是我以后要设置成这样?
  • 您不需要释放不再在树中的旧树元素分配的内存吗?
  • @user178831 不,root != new_root。此功能完全按照您所做的工作,但仅保护新根的子树免于在合同中删除您所做的工作。所以你需要在包装函数中完成它:void wrapper_func(node **root, node *new_root){ prune_tree(root, new_root); *root = new_root; }
  • & 然后调用wrapper_func(来自main 或其他地方),只要你想设置新的根。
  • 我明白了。我还不是 100% 确定这一点,但从测试来看,这个函数似乎无法摆脱 new_root 的兄弟姐妹(nexts)或他们的孩子。
猜你喜欢
  • 2012-07-20
  • 2022-01-05
  • 2012-02-09
  • 2019-05-22
  • 2014-05-22
  • 1970-01-01
  • 2015-09-22
  • 2016-12-24
相关资源
最近更新 更多