C++ C++;基于链表的树结构。在列表之间复制节点
编辑 分类:目的不是从原始列表中删除节点。但要创建与原始节点相同的节点(数据和子节点),并将其插入到新列表中。换句话说,“移动”并不意味着从原始文件中“移除” 恩迪特 要求:C++ C++;基于链表的树结构。在列表之间复制节点,c++,stl,linked-list,C++,Stl,Linked List,编辑 分类:目的不是从原始列表中删除节点。但要创建与原始节点相同的节点(数据和子节点),并将其插入到新列表中。换句话说,“移动”并不意味着从原始文件中“移除” 恩迪特 要求: 列表中的每个节点都必须包含对其上一个同级节点的引用 列表中的每个节点必须包含对其下一个同级节点的引用 每个节点可以有一个子节点列表 每个子节点必须有对其父节点的引用 基本上我们有一个任意深度和长度的树结构。比如: -root(NULL) --Node1 ----ChildNode1 ------ChildOfChild
- 列表中的每个节点都必须包含对其上一个同级节点的引用
- 列表中的每个节点必须包含对其下一个同级节点的引用
- 每个节点可以有一个子节点列表
- 每个子节点必须有对其父节点的引用
-root(NULL)
--Node1
----ChildNode1
------ChildOfChild
--------AnotherChild
----ChildNode2
--Node2
----ChildNode1
------ChildOfChild
----ChildNode2
------ChildOfChild
--Node3
----ChildNode1
----ChildNode2
Node* myNode = ...
Node* someChildInListTwo = findPlaceToInsert(listTwo);
listTwo->insertAfter(myNode->clone(), someChildInListTwo);
给定任何单个节点,您需要能够遍历其兄弟节点。子节点,或从树向上到根节点
节点的最终外观如下所示:
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;
}
这就是设置,现在回答问题。如何将一个节点(及其子节点等)移动到另一个列表,而不使前一个列表悬空
例如,如果Node*myNode存在于ListOne中,我想将其附加到ListOne中
使用指针,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
我想我应该把这个扔出去,看看大家有什么想法。让我们调用要删除的节点
current
。只有current
指向的节点,即current->previous
,current->next
,current->child
,以及current->parent
指向节点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
诚然,这仍然有点混乱,但是如果你将链表功能分解成小功能(看起来你已经在做了)并使用好的注释,那么我不认为这真的没有那么糟糕。让我们调用将要删除的节点
current
。只有current
指向的节点,即current->previous
,current->next
,current->child
,以及current->parent
指向节点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
诚然,这仍然有点混乱,但是如果你将链表功能分解成小功能(看起来你已经在做了)并使用好的注释,那么它真的没有那么糟糕,我不认为。首先,我同意,如果你复制节点,而不是从原始树中删除它,该操作应称为复制,而不是移动 其次,我建议您将实际执行复制的操作与执行插入的操作分开。这将使它更加灵活,例如,如果您需要将来自其他源的节点插入到目标中,或者您希望复制节点以用于其他目的 第三,您没有指定节点是否是多态的。如果是,我将实现如下方法进行复制:
virtual Node* clone();
在这里,您需要做出几个设计决策:
- 进行克隆时是否要保留下一个、上一个和父级?我认为把它们归零是有道理的
- 在进行克隆时,是否也要对子对象进行深度复制?根据用例的不同,您可能希望执行其中一项或另一项操作,或者同时考虑这两项
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 */ }
};
从一棵树复制到另一棵树的代码看起来就像:
-root(NULL)
--Node1
----ChildNode1
------ChildOfChild
--------AnotherChild
----ChildNode2
--Node2
----ChildNode1
------ChildOfChild
----ChildNode2
------ChildOfChild
--Node3
----ChildNode1
----ChildNode2
Node* myNode = ...
Node* someChildInListTwo = findPlaceToInsert(listTwo);
listTwo->insertAfter(myNode->clone(), someChildInListTwo);
在本例中,我们已将一个有点复杂的操作转换为一组离散操作,您可以在多种情况下使用这些操作:
- 一种
操作,允许您复制任意节点,无论它们是否已添加到树中clone()
- 一个
操作,如果您愿意,它将自动为您执行移动,并正确处理添加到子列表前面的操作insert()
- 一个
remove()