【问题标题】:Complete Binary Tree swapping child and parent bug完成二叉树交换子和父错误
【发布时间】:2020-07-22 10:34:47
【问题描述】:

我正在为学校做一个项目,但我遇到了一个奇怪的错误。 我正在实现一个完整的二叉树,但在与他的父节点交换节点时遇到了麻烦。

在测试过程中,我发现 1 个案例无法正常工作。

这个:

除这种情况外,所有其他交换都可以正常工作

我的树的结构是这样的

typedef struct TreeNode {
    void * data;
    struct TreeNode * left;
    struct TreeNode * right;
    struct TreeNode * parent;
} TNode;

typedef struct CompleteBinaryTree {
    TNode * root;
    TNode * last;
    int numelm;
} CBTree;

CBTree * newCBTree(void)
{
    CBTree * ret = malloc(sizeof(CBTree));

    if( ret )
    {
        ret->root = NULL;
        ret->last = NULL;

        ret->numelm = 0;
    }
}

TNode * newTNode( void * data )
{
    TNode *ret = malloc(sizeof(TNode));
    if( ret )
    {
        ret->data = data;
        ret->parent = ret->left = ret->right = NULL;
    }

    return ret;
}

这是我的交换功能:

void CBTreeSwap(CBTree* tree, TNode* parent, TNode* child)
{
  assert(parent != NULL && child != NULL && (child == parent->left || child == parent->right));

  if (child == tree->last)
    tree->last = parent;

  if(child == parent->left)
  {        
    if(child->left != NULL)
      child->left->parent = parent;

    parent->left = child->left;
    child->left = parent;

    if (child->right != NULL)
      child->right->parent = parent;

    if (parent->right != NULL)
      parent->right->parent = child;

    TNode * tmp = child->right;
    child->right = parent->right;
    parent->right = tmp;

    if (parent != tree->root)
    {
      parent->parent->left = child;
      child->parent = parent->parent;
      parent->parent = child;
    }
    else
    {
      child->parent = NULL;
      tree->root = child;
      parent->parent = child;
    }
  }
  else
  {
    if(child->right != NULL)
      child->right->parent = parent;

    parent->right = child->right;
    child->right = parent;

    if(child->left != NULL)
      child->left->parent = parent;

    if(parent->left != NULL)
      parent->left->parent = child;

    TNode * tmp = child->left;
    child->left = parent->left;
    parent->left = tmp;

    if(parent != tree->root)
    {
      parent->parent->right = child;
      child->parent = parent->parent;
      parent->parent = child;
    }
    else
    {
      child->parent = NULL;
      tree->root = child;
      parent->parent = child;
    }
  }
}

要在使用中的树中插入:

void CBTreeInsert(CBTree* tree, void* data)
{
  TNode * tmp = newTNode(data);
  TNode * curr = tree->last;

  if(tree->root == NULL)
  { //empty
    tree->root = tmp;
  }
  else if(tree->last == tree->root)
  { //one node
    tree->last->left = tmp;
    tmp->parent = tree->root;
  }
  else if(tree->last->parent->right == NULL)
  { //general
    tree->last->parent->right = tmp;
    tmp->parent = tree->last->parent;
  }
  else if (tree->last == tree->last->parent->right)
  { //degenarated
    curr = tree->last->parent ;

    while (1)
    {
      if (curr == tree->root)
        break ;

      if (curr == curr->parent->left)
      {
        curr = curr->parent->right ;
        assert(curr != NULL) ;
        break ;
      }
      curr = curr->parent ;
   }

   while (curr->left != NULL) 
   {
      assert(curr->right != NULL) ;
      curr = curr->left ;
   }
    assert(curr->right == NULL) ;
    tmp->parent = curr ;
    curr->left  = tree->last = tmp;
  }
  else 
  {
    fprintf(stderr,"Error\n");
  }
  tree->last = tmp;
  tree->numelm++;
}

所以我像这样构建我的测试:

void main(){
  int * i[15], j;

  CBTree * tree  = newCBTree(); //create tree

  for(j=0; j<15; j++)
  {
    i[j] = malloc(sizeof(int));
    *(i[j]) = j+1;

    CBTreeInsert(tree, (int*) i[j]);
  }

    //All these work
    CBTreeSwap(T, T->root->left, T->root->left->left);
    CBTreeSwap(T, T->root, T->root->left);
    CBTreeSwap(T, T->root->right, T->root->right->right);
    CBTreeSwap(T, T->root, T->root->right);
    CBTreeSwap(T, T->last->parent, T->last);

    //This one is broken
    CBTreeSwap(T, T->root->left, T->root->left->right);
}

当我运行那个损坏的测试并尝试查看我的树时,我得到了我树的所有分支,然后是一个段错误。

这只是一个更大项目的一部分,如果您需要更多我的代码,请不要犹豫

谢谢!

【问题讨论】:

标签: c pointers segmentation-fault binary-tree swap


【解决方案1】:

您的问题不仅会在交换T-&gt;leftT-&gt;left-&gt;right 时(破坏所有T-&gt;left 左侧子树),而且还会在交换T-&gt;rightT-&gt;right-&gt;left 时产生树损坏。

问题出在CBTreeSwap() 函数中。

它的实现实际上很棘手。这并不容易理解,所以我将提出我的实现。我只能说问题的根源可能是在某个地方,在交换过程中,您分配了parent/child的某些字段,而没有注意它们已经被更改的事实!


修复

请在下面找到CBTreeSwap() 的更正版本。区分 child 是 parent-&gt;leftparent-&gt;right 的情况是正确的,但在这两种情况下,许多其他操作都很常见。代码已注释。

void CBTreeSwap(CBTree* tree, TNode* parent, TNode* child)
{
    assert(parent != NULL && child != NULL && (child == parent->left || child == parent->right));

    if (child == tree->last)
        tree->last = parent;

    /* Save child's childs */
    TNode *tmpL = child->left, *tmpR = child->right;

    /* Link child (new parent!) to parent's parent */
    if(parent != tree->root)
    {
        TNode *parpar = parent->parent;
        child->parent = parpar;

        /* Is parent left or right child of his parent? */
        if( parent->parent->left == parent)
          parpar->left = child;
        else
          parpar->right = child;
    }
    else
    {
        child->parent = NULL;
        tree->root = child;
    }

    /* In order to actually swap nodes we need to know if child is at parent's left or right*/       
    if(child == parent->left)
    {        
        /* Link parent's other child to child */
        parent->right->parent  = child;
        child->right  = parent->right;

        /* Link former parent to child's right (making it its new right child) */
        child->left = parent;
        parent->parent = child;
    }
    else /* child == parent->right */
    {
        /* Link parent's other child to child */
        parent->left->parent  = child;
        child->left  = parent->left;

        /* Link former parent to child's right (making it its new right child) */
        child->right = parent;
        parent->parent = child;
    }

    /* Link child's childs to former parent */
    parent->left  = tmpL;
    parent->right = tmpR;
    if(tmpL != NULL)
        tmpL->parent = parent;
    if(tmpR != NULL)
        tmpR->parent = parent;
}

所以:

  1. 保存child 的孩子。
  2. 将子(新父)链接到旧父的父,管理parent 是根节点的情况。我们需要了解parent 是在他父母的左边还是右边。
  3. 交换parentchild根据原来的相互位置。如果我们弄错了,我们可能会破坏整个子树。
  4. 恢复child的孩子

我测试了上面的代码,它按预期工作。


PS:虽然与本题无关,但请考虑使用数组和循环进行初始化。下面的代码的行为类似于您的初始化。不是更优雅吗?

  int * i[15], j;

  CBTree * tree  = newCBTree(); //create tree

  for(j=0; j<15; j++)
  {
    i[j] = malloc(sizeof(int));
    *(i[j]) = j+1;

    CBTreeInsert(tree, (int*) i[j]);
  }

另一种解决方案

但是有一个更简单的解决方案可以实现您的特定目标:为什么在程序结束时只需要交换节点内容时交换整个子树?

所以你的交换函数变成了:

void CBTreeSwap(CBTree* tree, TNode* parent, TNode* child)
{
    assert(parent != NULL && child != NULL && (child == parent->left || child == parent->right));

    void *tmpData = parent->data;
    parent->data = child->data;
    child->data = tmpData;
}

【讨论】:

  • 感谢您的回答。我的老师告诉我,我的函数 CBTreeSwap() 很好,这就是我寻找其他地方的原因。我知道移动数据是一个简单的解决方案,但不幸的是这不是练习的重点。我会继续戳它。
  • @Crunchy 我确认问题出在CBTreeSwap()。我编辑了我的答案,还以您要求的形式提供了解决方案。我解释了每一步。如果对您有帮助,请随时接受答案。
  • 这太完美了!我现在对我做错了什么有了更好的理解。我将在我的期末项目中使用您的版本,并将您的答案链接到我的老师。对于初始化,我会更新帖子,但我不需要优雅,这只是我测试的一种粗暴方式,但还是谢谢你!
猜你喜欢
  • 1970-01-01
  • 2019-07-02
  • 1970-01-01
  • 1970-01-01
  • 2021-03-20
  • 2020-08-24
  • 2013-01-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多