Класс дерева в C ++ - PullRequest
1 голос
/ 29 марта 2020

Я хочу решить проблему с числами, сохраненными в древовидной структуре. enter image description here

Я хотел бы создать один класс с именем Tree и другой с именем Element (в данном случае это будет Integer, но это может быть что угодно) и сделать его наиболее подходящим способом основанный на стандартах C ++. Должна быть возможность добавить дочерние элементы к указанному элементу c в дереве и отследить родительский элемент каждого элемента.

#include <iostream>
#include <vector>

class Element
{
public:
    Element() = delete;
    explicit Element(int value, Element* parent = nullptr):
            value_(value), parent_(parent), children_() {}
    int getValue() const { return value_; }
    Element* getParent() { return parent_; }
    // it will throw if idx out of bounds
    Element* getChild(size_t idx) { return children_.at(idx).get(); }


    size_t numChildren() const { return children_.size(); }
    Element* insertChild(int value)
    {
        std::cout << "new elem: " << value << std::endl;
        children_.emplace_back(std::make_unique<Element>(value, this));
        return children_.back().get();
    }
    bool moveChildren2Parent()
    {
        if (isRoot()) return false;
        for (auto& c : children_)
        {
            // children change parent
            c->parent_ = parent_;
            parent_->children_.emplace_back(std::move(c));
        }
        children_.clear();
        return true;
    }
    bool removeChild(size_t idx)
    {
        if (children_.size() <= idx) return false;
        children_.erase(children_.begin()+idx);
        return true;
    }
    bool isRoot() { return parent_ == nullptr; }

private:
    int value_;
    Element* parent_;
    std::vector<std::unique_ptr<Element> > children_;
};

void checkChilds(Element* element) {
    for (int i = 0; i < element->numChildren(); i++)
    {
        if (element->getChild(i)->numChildren() == 1)
        {
            element->getChild(i)->moveChildren2Parent();
            element->removeChild(i);
            i--;
        } else if (element->getChild(i)->numChildren() > 1)
        {
            checkChilds(element->getChild(i));
        }
    }
}

int main()
{
    auto root = std::make_shared<Element>(0);
    Element* _ = root->insertChild(1)->insertChild(3)->insertChild(5);
    Element* last_child = root->insertChild(2)->insertChild(4)->insertChild(7);
    last_child->getParent()->insertChild(6);

    for (int i=0;i<root->numChildren();i++)
    {
        if (root->getChild(i)->numChildren()==1)
        {
            root->getChild(i)->moveChildren2Parent();
            root->removeChild(i);
            i--;
        }
        else if (root->getChild(i)->numChildren()>1)
        {
            checkChilds(root->getChild(i));
        }
    }
    return 0;
}

Моя цель - создать дерево и после этого, если у каждого элемента только один дочерний элемент удалить этот элемент, сохраняя листья. Мой код работает, но я хотел бы знать улучшения, чтобы сделать его лучше выглядящим на основе стандартов C ++.


EDIT

Благодаря ответу @pptaszni и после адаптации его к моим спецификациям c проблема под рукой, это результат. Я думаю, что моя реализация go по всем элементам, проверяющая, равно ли число дочерних элементов, равное 1, и, если это так, удаление написано не очень хорошо. Вы знаете, как я могу оптимизировать его (последний для l oop в main и function checkChilds)?

Ответы [ 3 ]

1 голос
/ 29 марта 2020

Чтобы ответить на ваш первый вопрос: не используйте оператор [] , потому что он вставляет созданный по умолчанию элемент, если он не существует на карте. Вместо этого вы можете использовать в .

Тогда о вашей архитектуре: она выглядит не очень хорошо, потому что, имея ребенка, не очевидно, как получить родителя, иметь родителя, это не так. Очевидно, как получить его, ваш ключ std::map должен быть уникальным, и вы также используете его как значение для вашего Elements. Я предлагаю применить по крайней мере следующее:

class Element
{
public:
  Element() = delete;
  explicit Element(int value, Element* parent = nullptr):
    value_(value), parent_(parent), left_child_(nullptr), right_child_(nullptr) {}
  int getValue() const { return value_; }
  Element* getParent() { return parent_; }
  Element* getLeftChild() { return left_child_.get(); }
  Element* getRightChild() { return right_child_.get(); }
  Element* insertLeftChild(int value)
  {
    if (left_child_)
    {
      std::cout << "already exists" << std::endl;
      return left_child_.get();  // already exists
    }
    std::cout << "new left elem: " << value << std::endl;
    left_child_ = std::make_unique<Element>(value, this);
    return left_child_.get();
  }
  bool isRoot() { return parent_ == nullptr; }

private:
  int value_;
  Element* parent_;
  std::unique_ptr<Element> left_child_;
  std::unique_ptr<Element> right_child_;
};

int main()
{
  auto root = std::make_shared<Element>(1);
  Element* last_child = root->insertLeftChild(2)->insertLeftChild(3)->insertLeftChild(4);
  std::cout << last_child->getValue() << std::endl;
  std::cout << last_child->getParent()->getValue() << std::endl;
  std::cout << last_child->getParent()->getParent()->getValue() << std::endl;
  std::cout << last_child->getParent()->getParent()->getParent()->getValue() << std::endl;
  std::cout << last_child->getParent()->getParent()->getParent()->isRoot() << std::endl;
  return 0;
}

Теперь у вас есть доступ к родителям и дочерним элементам из каждого элемента, и вы можете начать строить свое дерево. Затем возникают дополнительные проблемы, такие как Element оператор сравнения (при необходимости), только 2 дочерних элемента на узел или, возможно, больше, аннулирование указателей при каждой модификации дерева и т. Д. c. Это большой топи c в целом.

======= РЕДАКТИРОВАТЬ =========

Чтобы ответить на озабоченность ОП по поводу нескольких детей и приведите пример удаления узла с сохранением листьев (потомков):

class Element
{
public:
  Element() = delete;
  explicit Element(int value, Element* parent = nullptr):
    value_(value), parent_(parent), children_() {}
  int getValue() const { return value_; }
  Element* getParent() { return parent_; }
  // it will throw if idx out of bounds
  Element* getChild(size_t idx) { return children_.at(idx).get(); }
  size_t numChildren() const { return children_.size(); }
  Element* insertChild(int value)
  {
    std::cout << "new elem: " << value << std::endl;
    children_.emplace_back(std::make_unique<Element>(value, this));
    return children_.back().get();
  }
  bool moveChildren2Parent()
  {
    if (isRoot()) return false;
    for (auto& c : children_)
    {
      // children change parent
      c->parent_ = parent_;
      parent_->children_.emplace_back(std::move(c));
    }
    children_.clear();
    return true;
  }
  bool removeChild(size_t idx)
  {
    if (children_.size() <= idx) return false;
    children_.erase(children_.begin()+idx);
    return true;
  }
  bool isRoot() { return parent_ == nullptr; }

private:
  int value_;
  Element* parent_;
  std::vector<std::unique_ptr<Element> > children_;
};

int main()
{
  auto root = std::make_shared<Element>(1);
  Element* last_child = root->insertChild(2)->insertChild(3)->insertChild(4);
  last_child->getParent()->insertChild(5);
  std::cout << "numChildren: " << last_child->getParent()->numChildren() << std::endl;
  last_child->getParent()->moveChildren2Parent();
  std::cout << "numChildren: " << last_child->getParent()->numChildren() << std::endl;
  last_child->getParent()->removeChild(0);  // element with value 3 removed, it's children already transferred
  std::cout << last_child->getValue() << std::endl;
  std::cout << last_child->getParent()->getValue() << std::endl;
  std::cout << last_child->getParent()->getParent()->getValue() << std::endl;
  std::cout << last_child->getParent()->getParent()->isRoot() << std::endl;
  return 0;
}

Это лишь одна из многих возможностей, выбор конкретной реализации всегда зависит от системных требований.

1 голос
/ 29 марта 2020

есть ли конкретная c причина, по которой вам нужны два разных класса для элемента и дерева? Я предлагаю иметь только один класс, в котором есть один элемент данных, в котором будет храниться значение узла, и два указателя для указания на два разных дочерних объекта.

Ниже приведено предложение, основанное на том, что я понял из вашего вопроса.

class Node
{
    int value;
    Node* left_child = nullptr; 
    Node* right_child = nullptr;
    //methods for modifying tree.
};
0 голосов
/ 29 марта 2020

У вас там несколько проблем с вашим кодом. Самое яркое, что я могу видеть, это то, что вы свободно конвертируете между int парами и элементами. Классы предоставляют способ строгой типизации нашего кода, если int, int означает представление Element, сделайте его Element!

Причина, по которой вы должны объявить конструктор по умолчанию, заключается в потому что вы уже объявили конструктор. Компилятор сгенерирует для вас только значение по умолчанию, если вы уже объявили его.

Чтобы компилятор сгенерировал его автоматически, используйте default. Например:

Element() = default;

Чтобы исправить слабые проблемы с печатью, вы должны использовать Element для m_parent и при добавлении элемента в Tree, если вам нужно что-то вроде установки значений Затем родитель делает это с элементом, который вы передаете, чтобы добавить. Вот так

...
private:
Element m_parent;

...
void addElement(const Element& parent, int value)
    {
        Element elem = Element(value, parent);
        m_elements.insert(std::pair<int,Element>(value,elem));
    }
...

Возможно, вы также захотите использовать контейнер, отличный от std::map. Если вы хотите иметь понятие «элементы», я бы использовал std::set, сохранил значение элемента или ключ в качестве члена и перегрузил оператор < (чтобы разрешить сортировку) для возврата операции над значениями / ключами. std::set позволит вам искать в дереве (используя лямбду с find_if) значения ключей или , а также все остальное (например, сопоставление родителей).

Это использование сохранения родительский элемент в качестве другого Element также позволяет вам выполнить итерацию по дереву, если вы тоже этого хотите (независимо от вашего контейнера).

...