我是一名业余程序员,试图找到将unique_ptr
放在我的二进制树中的适当位置。最初,我将unique_ptr
用于左右两个子节点,但这是“意味着”每个节点“拥有”每个后续节点。我在previous post中被告知,该树应拥有其节点。这是我对这个问题的解决方案:所有树节点都存储在唯一指针的向量中,unique_ptr::get
用于提取按常规方式使用的原始指针(例如add)。
#include "pch.h"
#include <iostream>
#include <sstream>
#include <string>
#include <memory>
#include <vector>
#include <unordered_map>
class Node
{
public:
Node(int data = 0) : data_(data), left_child(nullptr),
right_child(nullptr) {}
int data_;
Node *left_child;
Node *right_child;
};
class Tree
{
public:
Tree(int data = 0) {
store.emplace_back(std::make_unique<Node>(data));
root = store.at(0).get();
}
void add(int data) {
Node *index = root;
while (true) {
if (data == index->data_) {
std::stringstream ss;
ss << data << " already exists\n";
throw std::invalid_argument(ss.str());
}
if (data < index->data_) {
if (index->left_child != nullptr) {
index = index->left_child;
continue;
}
std::unique_ptr<Node> temp = std::make_unique<Node>(data);
index->left_child = temp.get();
store.push_back(std::move(temp));
return;
}
if (index->right_child != nullptr) {
index = index->right_child;
continue;
}
std::unique_ptr<Node> temp = std::make_unique<Node>(data);
index->right_child = temp.get();
store.push_back(std::move(temp));
return;
}
}
private:
std::vector<std::unique_ptr<Node>> store;
Node* root;
};
删除节点似乎很麻烦。在树中找到值(快速),在std::vector
中找到指针(慢),从向量中删除条目,最后从父级修剪指针。我在正确的轨道上吗?如果没有,提示会受到欢迎。
答案 0 :(得分:1)
为子级用户使用std :: unique_ptr是一种快捷的解决方案,但它与问题的要求不符。由于代码复杂,而且涉及时间复杂性,因此将指针放入向量中不是一个好主意。
一种快捷方法是在树中编写一个函数,该函数将递归删除节点。缺点是如果树不平衡(就像子节点上的std::unique_ptr
一样),则可能导致堆栈溢出。有几种方法可以解决潜在的堆栈溢出问题:
高效的解决方案,没有堆栈溢出,也没有std::bad_alloc
异常的可能性。它是一种DFS算法,使用释放的树节点作为堆栈。 Node :: left条目将指向有效负载(要释放的子树),而Node :: right将在DFS堆栈(链接列表)中扮演next
的角色。
static Node * & nextNode(Node & node)
{ return node.right_child; }
static Node * & payload(Node & node)
{ return node.left_child; }
Tree::~Tree()
{
Node temp;
Node *stack = & temp;
payload(*stack) = root;
nextNode(*stack) = nullptr;
constexpr bool TRACE = false;
while (stack) {
Node * treeNode = payload(*stack);
Node * toPush1 = treeNode->left_child;
Node * toPush2 = treeNode->right_child;
if (toPush1) {
payload(*stack) = toPush1;
if (toPush2) {
payload(*treeNode) = toPush2;
nextNode(*treeNode) = stack;
stack = treeNode;
} else {
if (TRACE) std::cout << treeNode->data_ << " ";
delete treeNode;
}
}
else if (toPush2) {
payload(*stack) = toPush2;
if (TRACE) std::cout << treeNode->data_ << " ";
delete treeNode;
}
else { // nothing to push
Node *nextStack = nextNode(*stack);
if (TRACE) std::cout << treeNode->data_ << " ";
delete treeNode;
if (stack != &temp) {
if (TRACE) std::cout << stack->data_ << " ";
delete stack;
}
stack = nextStack;
}
}
// free the stack.
while (stack) {
Node *nextStack = nextNode(*stack);
if (stack != &temp) {
if (TRACE) std::cout << stack->data_ << " ";
delete stack;
}
stack = nextStack;
}
if (TRACE) std::cout << '\n';
}
这将使您既具有O(1)附加内存的内存效率,又具有O(N)时间复杂度的时间效率。
为完整起见,这是Tree类的其余部分:
class Tree
{
public:
Tree(int data = 0) {
root = new Node(data);
}
~Tree();
Copy ctor, assignment, move ctor, move assignment
void add(int data) {
Node *index = root;
while (true) {
if (data == index->data_) {
std::stringstream ss;
ss << data << " already exists\n";
throw std::invalid_argument(ss.str());
}
if (data < index->data_) {
if (index->left_child != nullptr) {
index = index->left_child;
continue;
}
std::unique_ptr<Node> temp = std::make_unique<Node>(data);
index->left_child = temp.release();
return;
}
if (index->right_child != nullptr) {
index = index->right_child;
continue;
}
std::unique_ptr<Node> temp = std::make_unique<Node>(data);
index->right_child = temp.release();
return;
}
}
private:
// owning the root and all descendants recursively
Node* root;
};
答案 1 :(得分:0)
当然,在vector
类中拥有所有已分配节点的Tree
是一个坏主意。如您所指出的那样,操纵它来查找和删除Node
很慢(即,线性地取决于您的树的大小),并且削弱了树本应具有的所有优势。
第一个建议是为std::unique_ptr<Node>
的{{1}}和left_child
成员使用right_child
。然后,您将不需要Node
来存储所有节点。
但是在您的特定实现中还不够:您无法对树进行平衡,因此在最坏的情况下其深度会线性增长,因此递归清理将失败(堆栈溢出)。您需要混合的迭代-递归清除。
但是您可以作为下一步。第一步-摆脱vector
。