Как реализовать структуру данных "дерево" на 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);
}
}
}
Эти примеры показывают, как можно реализовать структуры деревьев, обеспечивая при этом гибкость и возможность расширения.
Какую структуру данных выбрать: TreeMap или HashMap? (Java)
Что значит 'synchronized'?
Почему нет ConcurrentHashSet, если есть ConcurrentHashMap?
Как объявить массив в одну строку?
Какие проблемы следует учитывать при переопределении equals и hashCode в Java?