修改的
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
我以为我会把它扔到那里,看看有人想出来。
答案 0 :(得分:4)
让我们调用我们要删除的节点current
。只有current
指向的节点,即current->previous
,current->next
,current->child
和current->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();
您需要做出一些设计决策:
第四,如果已设置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()
操作,作为我们希望插入行为的副产品自然而然地脱落,但可以单独使用