基于C ++链表的树结构。在列表之间复制节点

时间:2010-05-26 22:58:26

标签: c++ stl linked-list

修改

Clafification:目的不是从原始列表中删除节点。但是要创建一个与原始节点相同的节点(数据和子节点)并将其插入新列表中。换句话说,“移动”并不意味着从原件中“移除”。

EndEdit中

要求:

  • 列表中的每个节点都必须包含对其先前兄弟的引用
  • 列表中的每个节点都必须包含对其下一个兄弟的引用
  • 每个节点可能有一个子节点列表
  • 每个子节点必须具有对其父节点的引用

基本上我们拥有的任意深度和长度的树结构。类似的东西:

-root(NULL)
--Node1
----ChildNode1
------ChildOfChild
--------AnotherChild
----ChildNode2
--Node2
----ChildNode1
------ChildOfChild
----ChildNode2
------ChildOfChild
--Node3
----ChildNode1
----ChildNode2

给定任何单个节点,您需要能够遍历其兄弟节点。孩子,或树上的根节点。

节点最终看起来像这样:

class Node
{
  Node* previoius;
  Node* next;
  Node* child;
  Node* parent;
}

我有一个容器类来存储这些并提供STL迭代器。它执行典型的链表访问器。所以insertAfter看起来像:

void insertAfter(Node* after, Node* newNode)
{
  Node* next = after->next;
  after->next = newNode;
  newNode->previous = after;
  next->previous = newNode;
  newNode->next = next;
  newNode->parent = after->parent;
}

这是设置,现在是问题。如何将节点(及其子节点等)移动到另一个列表而不留下先前的列表悬空?

例如,如果ListOne中存在Node * myNode,我想将其附加到listTwo。

使用指针,listOne在其列表中留下了一个洞,因为下一个和前一个指针都被更改了。一种解决方案是传递附加节点的值。所以我们的insertAfter方法将成为:

void insertAfter(Node * after,Node newNode);

这似乎是一种尴尬的语法。另一个选择是在内部进行复制,所以你需要:

void insertAfter(Node* after, const Node* newNode)
{
  Node *new_node = new Node(*newNode);
  Node* next = after->next;
  after->next = new_node;
  new_node->previous = after;
  next->previous = new_node;
  new_node->next = next;
  new_node->parent = after->parent;
}

最后,您可以创建一个moveNode方法来移动和阻止原始插入或附加已经分配了兄弟姐妹和父母的节点。

// default pointer value is 0 in constructor and a operator bool(..) 
// is defined for the Node

bool isInList(const Node* node) const
{
  return (node->previous || node->next || node->parent);
}
// then in insertAfter and friends
if(isInList(newNode)
  // throw some error and bail

我以为我会把它扔到那里,看看有人想出来。

2 个答案:

答案 0 :(得分:4)

让我们调用我们要删除的节点current。只有current指向的节点,即current->previouscurrent->nextcurrent->childcurrent->parent才会引用节点current。例如,current->previous->next == current如果当前节点的前一个节点是非空的。因此,我们可以轻松地从列表中删除当前节点,如下所示:

垂直链接
节点保留其子节点,但需要从其父节点中删除它。这样做是这样的:

Let parent = current->parent
if parent is non-null:
    if parent->child == current:
         parent->child = current->next
current->parent = null

横向链接
以下代码在水平(下一个/上一个)方向取消链接当前节点:

Let prev = current->previous
Let next = current->next
if prev is non-null:
    prev->next = next
if next is non-null:
    next->previous = prev
current->previous = null
current->next = null

当然,这仍然有点混乱,但是如果你将链表功能分解成小功能(看起来你已经在做)并使用好的评论,那么它真的不是那么糟糕,我不认为

答案 1 :(得分:0)

首先,我同意,如果您正在复制节点而不是从原始树中删除它,那么该操作应该被称为副本,而不是移动!

其次,我建议您将实际执行复制的操作与插入操作分开。这将使其更加灵活,例如,您需要将其他源中的节点插入目标,或者您希望将节点复制用于其他目的。

第三,您没有指定Node是否是多态的。如果是,我会实现类似以下的方法来进行复制:

virtual Node* clone();

您需要做出一些设计决策:

  • 在进行克隆时,是否要保留next,prev和parent?我认为将它们归零是有道理的。
  • 当你做克隆时,你想要对孩子们进行深层复制吗?根据用例,您可能想要执行其中一个,或者同时允许这两个。

第四,如果已设置parent / prev / next指针,则假设插入失败。您可以这样做,但是在这种情况下,您可以从更强大的插入操作中获益,该插入操作只是从现有树中删除节点。两者都表现得一致。

第五,如果您要修复该节点的指针,则不能传递const节点。

让我们假设您决定将链接列表指针清零,但决定深层复制子项。那么您的代码可能如下所示:

class Node {
public:
    Node() : prev(NULL), next(NULL), parent(NULL), child(NULL) { }
    virtual Node* clone() {
        Node* newN = new Node();
        newN->cloneChildren(*this);
        return newN;
    }
    Node* lastChild() const { /* left as exercise */ }
    void insert(Node* node_) { insertAfter(node_, lastChild()); }
    void insertAfter(Node* node_, Node* prevSibling_) {
        ASSERT(node_);
        if (! prevSibling_) { // assume we want to push to front of child list
           prevSibling_ = child; // will be NULL if we have no children
        }
        ASSERT(! prevSibling_ || prevSibling_->parent == this);
        if (node_->parent) {
            // assume you want to move the child in this case
            node_->parent->remove(node_);
        }
        node_->parent = this;
        node_->prev = prevSibling_;
        if (prevSibling_) {
            node_->next = prevSibling_->next;
            prevSibling_->next = node_;
        } else {
            /* the new child is the only child - left as exercise */
        }
    }
    void remove(Node* child_) { /* left as exercise */ }
protected:
    virtual void cloneChildren(const Node& rhs) { /* left as exercise */ }
};

从一棵树复制到另一棵树的代码看起来像是:

Node* myNode = ...
Node* someChildInListTwo = findPlaceToInsert(listTwo);
listTwo->insertAfter(myNode->clone(), someChildInListTwo);

在这种情况下,我们将您的一个有点纠结的操作变成了一组可以在各种情况下使用的离散操作:

  • 一个clone()操作,允许您复制任意节点,无论它们是否已添加到树中
  • insert()操作,如果您愿意,将自动为您执行操作,并正确处理添加到子列表的前面
  • 一个remove()操作,作为我们希望插入行为的副产品自然而然地脱落,但可以单独使用
  • 如果您不想从原始树中删除节点,则复制+插入节点的简单机制