在节点中使用原始指针,在树中使用unique_ptr

时间:2019-03-19 05:24:02

标签: c++ pointers tree c++14 unique-ptr

我是一名业余程序员,试图找到将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中找到指针(慢),从向量中删除条目,最后从父级修剪指针。我在正确的轨道上吗?如果没有,提示会受到欢迎。

2 个答案:

答案 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