Как реализовать структуру данных "дерево" на Java?
Есть ли стандартный класс библиотеки Java для представления дерева?
Мне необходимо создать структуру данных для дерева с следующими характеристиками:
- Поддерево в любом узле может иметь произвольное количество дочерних узлов.
- Каждый узел (кроме корневого) и его дочерние узлы будут иметь строковое значение.
- Мне нужно получить всех детей (некую структуру данных, например, список или массив строк) данного узла, а также его строковое значение (т.е. метод, который будет принимать узел в качестве входного параметра и возвращать все строковые значения дочерних узлов в качестве результата).
Существует ли уже готовая структура для этого или мне нужно реализовать свою собственную? Если да, буду признателен за предложения по реализации.
5 ответ(ов)
Вот пример базовой структуры дерева на Java:
public class Tree<T> {
private Node<T> root;
public Tree(T rootData) {
root = new Node<T>();
root.data = rootData;
root.children = new ArrayList<Node<T>>();
}
public static class Node<T> {
private T data;
private Node<T> parent;
private List<Node<T>> children;
}
}
Это базовая структура дерева, которую можно использовать для работы со строками или любыми другими объектами. Реализовать простые деревья для ваших нужд довольно просто.
Все, что вам нужно добавить, — это методы для добавления, удаления, обхода и конструкторы. Node
является основным строительным блоком дерева.
Ваш код представляет собой реализацию простой структуры дерева в Java с использованием обобщений. Дерево состоит из узлов, которые содержат данные, ссылку на родительский узел и список дочерних узлов.
Вот основные моменты вашей реализации:
Класс TreeNode:
- Параметризован типом
<T>
, что позволяет использовать любой тип данных для хранения в узлах дерева. - В конструкторе инициализируется поле
data
, а также создается пустой списокchildren
для дочерних узлов.
- Параметризован типом
Метод addChild(T child):
- Создает новый узел с переданными данными, устанавливает текущий узел в качестве родителя и добавляет его в список дочерних узлов.
- Возвращает созданный дочерний узел для возможного дальнейшего использования.
Пример использования:
- Создается корневой узел ("root") и добавляются дочерние узлы ("node0", "node1", "node2").
- Внутри узла "node2" добавляются еще узлы, включая узел с
null
в данных.
Бонус: Вы упомянули, что у вас есть полная реализация дерева, включая итератор и методы поиска, доступные на GitHub по ссылке.
Если у вас есть дополнительные вопросы относительно реализации или возможно улучшения, дайте знать!
Ваш код реализует структуру данных в виде дерева на языке Java. Давайте разберем ключевые моменты, которые могут быть полезны для понимания его работы:
Класс Tree: Это обобщенный класс, который принимает параметр типа
<T>
, представляющий тип объекта в дереве.Поля:
head
: главная (корневая) нода текущего дерева.leafs
: список дочерних деревьев (листьев).parent
: ссылка на родительское дерево.locate
: хэш-карта, позволяющая быстро находить дерево по его корневому элементу.
Конструктор: Конструктор класса принимает значение типа
T
и добавляет это значение в хэш-картуlocate
.Методы:
addLeaf(T root, T leaf)
: добавляет лист к дереву. Если родитель не найден, он создает новое поддерево.addLeaf(T leaf)
: создает новый лист и добавляет его в списокleafs
.setAsParent(T parentRoot)
: назначает текущий экземпляр дерева как дочерний для нового дерева, которое создается с указанным корнем.getHead()
,getTree(T element)
,getParent()
: стандартные методы для получения информации о дереве.getSuccessors(T root)
: возвращает коллекцию всех дочерних элементов указанного узла.getSubTrees()
: возвращает коллекцию поддеревьев.toString()
иprintTree(int increment)
: методы для текстового представления дерева с учетом отступов.
Статический метод:
getSuccessors(T of, Collection<Tree<T>> in)
ищет заданный элемент в коллекции деревьев и возвращает его потомков.
В целом, реализация выглядит достаточно аккуратно и понятна. Возможно, стоит рассмотреть некоторые улучшения, такие как обработка исключений при добавлении элементов или улучшение управления памятью при работе с большими деревьями.
Если у вас есть конкретные вопросы о данном коде или его реализации, не стесняйтесь их задавать!
Ваш код представляет собой реализацию класса Tree
, который содержит дерево, где каждая вершина может иметь несколько дочерних элементов (листьев) и ссылку на родительский элемент. Вы правы, что для полного функционала стоит добавить методы, позволяющие добавлять и удалять дочерние элементы. Вот пример того, как это можно сделать:
public class Tree {
private List<Tree> leaves = new LinkedList<>();
private Tree parent = null;
private String data;
public Tree(String data, Tree parent) {
this.data = data;
this.parent = parent;
}
// Метод для добавления дочернего элемента
public void addChild(Tree child) {
if (child != null) {
leaves.add(child);
}
}
// Метод для удаления дочернего элемента
public boolean removeChild(Tree child) {
return leaves.remove(child);
}
// Геттер для данных
public String getData() {
return data;
}
// Геттер для родительского элемента
public Tree getParent() {
return parent;
}
// Метод для получения дочерних элементов
public List<Tree> getLeaves() {
return new LinkedList<>(leaves); // Возвращаем копию списка
}
}
В этом обновленном классе:
addChild(Tree child)
: Метод добавляет дочерний элемент к текущему узлу.removeChild(Tree child)
: Метод удаляет дочерний элемент, если он существует.- Геттеры: Также добавлены геттеры для получения данных узла и его родителя, а также метод для получения списка дочерних элементов.
Эти методы позволят вам управлять иерархией дерева более удобно.
Чтобы начать, вам следует сначала определить, что такое дерево (в данной области), и это лучше всего сделать, определив интерфейс. Не все структуры деревьев модифицируемы, возможность добавлять и удалять узлы должна быть опциональной функцией, поэтому мы создадим дополнительный интерфейс для этого.
Нет необходимости создавать объекты узлов, которые хранят значения, на самом деле, я рассматриваю это как серьезный недостаток проектирования и лишнюю нагрузку в большинстве реализаций деревьев. Если вы посмотрите на Swing, вы увидите, что TreeModel
свободен от классов узлов (только DefaultTreeModel
использует TreeNode
), поскольку они на самом деле не нужны.
public interface Tree <N extends Serializable> extends Serializable {
List<N> getRoots ();
N getParent (N node);
List<N> getChildren (N node);
}
Для модифицируемой структуры дерева (которая позволяет добавлять и удалять узлы):
public interface MutableTree <N extends Serializable> extends Tree<N> {
boolean add (N parent, N node);
boolean remove (N node, boolean cascade);
}
Учитывая эти интерфейсы, код, который использует деревья, не должен сильно заботиться о том, как дерево реализовано. Это позволяет вам использовать обобщенные реализации, а также специализированные, при которых реализация дерева делегирует функции другой API.
Пример: структура дерева файлов
public class FileTree implements Tree<File> {
@Override
public List<File> getRoots() {
return Arrays.stream(File.listRoots()).collect(Collectors.toList());
}
@Override
public File getParent(File node) {
return node.getParentFile();
}
@Override
public List<File> getChildren(File node) {
if (node.isDirectory()) {
File[] children = node.listFiles();
if (children != null) {
return Arrays.stream(children).collect(Collectors.toList());
}
}
return Collections.emptyList();
}
}
Пример: обобщенная структура дерева (основанная на отношениях родитель/дитя):
public class MappedTreeStructure<N extends Serializable> implements MutableTree<N> {
public static void main(String[] args) {
MutableTree<String> tree = new MappedTreeStructure<>();
tree.add("A", "B");
tree.add("A", "C");
tree.add("C", "D");
tree.add("E", "A");
System.out.println(tree);
}
private final Map<N, N> nodeParent = new HashMap<>();
private final LinkedHashSet<N> nodeList = new LinkedHashSet<>();
private void checkNotNull(N node, String parameterName) {
if (node == null)
throw new IllegalArgumentException(parameterName + " must not be null");
}
@Override
public boolean add(N parent, N node) {
checkNotNull(parent, "parent");
checkNotNull(node, "node");
// проверка на циклы
N current = parent;
do {
if (node.equals(current)) {
throw new IllegalArgumentException(" node must not be the same or an ancestor of the parent");
}
} while ((current = getParent(current)) != null);
boolean added = nodeList.add(node);
nodeList.add(parent);
nodeParent.put(node, parent);
return added;
}
@Override
public boolean remove(N node, boolean cascade) {
checkNotNull(node, "node");
if (!nodeList.contains(node)) {
return false;
}
if (cascade) {
for (N child : getChildren(node)) {
remove(child, true);
}
} else {
for (N child : getChildren(node)) {
nodeParent.remove(child);
}
}
nodeList.remove(node);
return true;
}
@Override
public List<N> getRoots() {
return getChildren(null);
}
@Override
public N getParent(N node) {
checkNotNull(node, "node");
return nodeParent.get(node);
}
@Override
public List<N> getChildren(N node) {
List<N> children = new LinkedList<>();
for (N n : nodeList) {
N parent = nodeParent.get(n);
if (node == null && parent == null) {
children.add(n);
} else if (node != null && parent != null && parent.equals(node)) {
children.add(n);
}
}
return children;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
dumpNodeStructure(builder, null, "- ");
return builder.toString();
}
private void dumpNodeStructure(StringBuilder builder, N node, String prefix) {
if (node != null) {
builder.append(prefix);
builder.append(node.toString());
builder.append('\n');
prefix = " " + prefix;
}
for (N child : getChildren(node)) {
dumpNodeStructure(builder, child, prefix);
}
}
}
Эти примеры показывают, как можно реализовать структуры деревьев, обеспечивая при этом гибкость и возможность расширения.
Инициализация ArrayList в одну строчку
Почему нет ConcurrentHashSet, если есть ConcurrentHashMap?
Как объявить массив в одну строку?
Загрузка JDK Java на Linux через wget приводит к отображению страницы лицензии вместо установки
Создание репозитория Spring без сущности