Бинарное дерево java методы

Реализация двоичного дерева в Java

Взгляните на реализацию отсортированного двоичного дерева в Java.

1. введение

В этой статье мы рассмотрим реализацию двоичного дерева в Java.

Для этой статьи мы будем использовать отсортированное двоичное дерево , которое будет содержать int значения .

Дальнейшее чтение:

Как распечатать двоичную древовидную диаграмму

Реверсирование двоичного дерева в Java

Глубина первого поиска на Java

2. Двоичное дерево

Двоичное дерево-это рекурсивная структура данных, в которой каждый узел может иметь не более 2 дочерних элементов.

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

Вот краткое визуальное представление этого типа двоичного дерева:

Для реализации мы будем использовать вспомогательный класс Node , который будет хранить значения int и сохранять ссылку на каждый дочерний элемент:

Затем давайте добавим начальный узел нашего дерева, обычно называемый root:

3. Общие операции

Теперь давайте рассмотрим наиболее распространенные операции, которые мы можем выполнять с двоичным деревом.

3.1. Вставка Элементов

Первая операция, которую мы рассмотрим, – это вставка новых узлов.

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

  • если значение нового узла ниже, чем значение текущего узла, мы переходим к левому дочернему узлу
  • если значение нового узла больше, чем значение текущего узла, мы переходим к правому дочернему узлу
  • когда текущий узел равен null, мы достигли конечного узла, и мы можем вставить новый узел в эту позицию

Во-первых, мы создадим рекурсивный метод для вставки:

private Node addRecursive(Node current, int value) < if (current == null) < return new Node(value); >if (value < current.value) < current.left = addRecursive(current.left, value); >else if (value > current.value) < current.right = addRecursive(current.right, value); >else < // value already exists return current; >return current; >

Затем мы создадим открытый метод, который запускает рекурсию из корневого узла:

Теперь давайте посмотрим, как мы можем использовать этот метод для создания дерева из нашего примера:

private BinaryTree createBinaryTree()

3.2. Нахождение элемента

Теперь давайте добавим метод, чтобы проверить, содержит ли дерево определенное значение.

Как и прежде, сначала мы создадим рекурсивный метод, который пересекает дерево:

private boolean containsNodeRecursive(Node current, int value) < if (current == null) < return false; >if (value == current.value) < return true; >return value

Здесь мы ищем значение, сравнивая его со значением в текущем узле, а затем продолжаем в левом или правом дочернем узле в зависимости от этого.

Далее, давайте создадим открытый метод, который начинается с root :

public boolean containsNode(int value)

Теперь давайте создадим простой тест, чтобы убедиться, что дерево действительно содержит вставленные элементы:

@Test public void givenABinaryTree_WhenAddingElements_ThenTreeContainsThoseElements()

Все добавленные узлы должны содержаться в дереве.

3.3. Удаление элемента

Другой распространенной операцией является удаление узла из дерева.

Во-первых, мы должны найти узел для удаления таким же образом, как и раньше:

private Node deleteRecursive(Node current, int value) < if (current == null) < return null; >if (value == current.value) < // Node to delete found // . code to delete the node will go here >if (value < current.value) < current.left = deleteRecursive(current.left, value); return current; >current.right = deleteRecursive(current.right, value); return current; >

Как только мы найдем узел для удаления, есть 3 основных различных случая:

  • у узла нет потомков – это самый простой случай; нам просто нужно заменить этот узел на null в его родительском узле
  • у узла есть ровно один дочерний узел – в родительском узле мы заменяем этот узел его единственным дочерним узлом.
  • у узла есть два дочерних элемента – это самый сложный случай, потому что он требует реорганизации дерева

Давайте посмотрим, как мы можем реализовать первый случай, когда узел является конечным узлом:

if (current.left == null && current.right == null)

Теперь давайте продолжим случай, когда у узла есть один дочерний элемент:

if (current.right == null) < return current.left; >if (current.left == null)

Здесь мы возвращаем ненулевой дочерний элемент, чтобы он мог быть назначен родительскому узлу.

Наконец, мы должны рассмотреть случай, когда у узла есть два дочерних элемента.

Во-первых, нам нужно найти узел, который заменит удаленный узел. Мы будем использовать наименьший узел узла для удаления правого поддерева:

private int findSmallestValue(Node root)

Затем мы присваиваем узлу наименьшее значение для удаления, а затем удаляем его из правого поддерева:

int smallestValue = findSmallestValue(current.right); current.value = smallestValue; current.right = deleteRecursive(current.right, smallestValue); return current;

Наконец, давайте создадим общедоступный метод, который запускает удаление из root :

public void delete(int value)

Теперь давайте проверим, что удаление работает должным образом:

@Test public void givenABinaryTree_WhenDeletingElements_ThenTreeDoesNotContainThoseElements()

4. Обход дерева

В этом разделе мы рассмотрим различные способы обхода дерева, подробно описывая поиск по глубине и по ширине.

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

4.1. Поиск по Глубине

Поиск по глубине-это тип обхода, который максимально углубляется в каждого ребенка, прежде чем исследовать следующего брата или сестру.

Существует несколько способов выполнить поиск по глубине: по заказу, по предварительному заказу и после заказа.

Обход по порядку состоит из первого посещения левого поддерева, затем корневого узла и, наконец, правого поддерева:

public void traverseInOrder(Node node) < if (node != null) < traverseInOrder(node.left); System.out.print(" " + node.value); traverseInOrder(node.right); >>

Если мы вызовем этот метод, вывод консоли покажет обход по порядку:

При обходе по предварительному заказу сначала посещается корневой узел, затем левое поддерево и, наконец, правое поддерево:

public void traversePreOrder(Node node) < if (node != null) < System.out.print(" " + node.value); traversePreOrder(node.left); traversePreOrder(node.right); >>

И давайте проверим обход предварительного заказа в выводе консоли:

Обход после заказа посещает левое поддерево, правое поддерево и корневой узел в конце:

public void traversePostOrder(Node node) < if (node != null) < traversePostOrder(node.left); traversePostOrder(node.right); System.out.print(" " + node.value); >>

4.2. Поиск по Ширине

Это еще один распространенный тип обхода, который посещает все узлы уровня, прежде чем перейти на следующий уровень .

Этот вид обхода также называется порядком уровней и посещает все уровни дерева, начиная с корня и слева направо.

Для реализации мы будем использовать Очередь для хранения узлов с каждого уровня по порядку. Мы извлекем каждый узел из списка, напечатаем его значения, а затем добавим его дочерние элементы в очередь:

public void traverseLevelOrder() < if (root == null) < return; >Queue nodes = new LinkedList<>(); nodes.add(root); while (!nodes.isEmpty()) < Node node = nodes.remove(); System.out.print(" " + node.value); if (node.left != null) < nodes.add(node.left); >if (node.right != null) < nodes.add(node.right); >> >

В этом случае порядок узлов будет:

5. Заключение

В этой статье мы рассмотрели, как реализовать отсортированное двоичное дерево в Java и его наиболее распространенные операции.

Полный исходный код примеров доступен на GitHub .

Читайте ещё по теме:

Источник

Реализация бинарного дерева в Java

В этой статье мы рассмотрим реализацию двоичного дерева на Java.

Для этой статьиwe’ll use a sorted binary tree that will contain int values.

2. Бинарное дерево

Двоичное дерево — это рекурсивная структура данных, в которой каждый узел может иметь не более 2 дочерних элементов.

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

Вот краткое визуальное представление этого типа двоичного дерева:

image

Для реализации мы будем использовать вспомогательный классNode, который будет хранить значенияint и ссылаться на каждого потомка:

Затем давайте добавим начальный узел нашего дерева, обычно называемыйroot:

3. Общие операции

Теперь давайте посмотрим, какие операции мы можем выполнять с двоичным деревом.

3.1. Вставка элементов

Первая операция, которую мы рассмотрим, — это вставка новых узлов.

Во-первых,we have to find the place where we want to add a new node in order to keep the tree sorted. Мы будем следовать этим правилам, начиная с корневого узла:

  • если значение нового узла ниже, чем значение текущего узла, мы переходим к левому дочернему элементу
  • если значение нового узла больше, чем значение текущего узла, мы переходим к правому дочернему элементу
  • когда текущий узел равенnull,, мы достигли листового узла и можем вставить новый узел в эту позицию

Сначала мы создадим рекурсивный метод для вставки:

private Node addRecursive(Node current, int value) < if (current == null) < return new Node(value); >if (value < current.value) < current.left = addRecursive(current.left, value); >else if (value > current.value) < current.right = addRecursive(current.right, value); >else < // value already exists return current; >return current; >

Затем мы создадим общедоступный метод, который запускает рекурсию с узлаroot:

Теперь давайте посмотрим, как мы можем использовать этот метод для создания дерева из нашего примера:

private BinaryTree createBinaryTree()

3.2. Поиск элемента

Давайте теперь добавим метод, чтобы проверить, содержит ли дерево определенное значение.

Как и раньше, сначала мы создадим рекурсивный метод для обхода дерева:

private boolean containsNodeRecursive(Node current, int value) < if (current == null) < return false; >if (value == current.value) < return true; >return value

Здесь мы ищем значение, сравнивая его со значением в текущем узле, а затем продолжаем в левом или правом дочернем элементе в зависимости от этого.

Затем давайте создадим общедоступный метод, который начинается сroot:

public boolean containsNode(int value)

Теперь давайте создадим простой тест, чтобы убедиться, что дерево действительно содержит вставленные элементы:

@Test public void givenABinaryTree_WhenAddingElements_ThenTreeContainsThoseElements()

Все добавленные узлы должны содержаться в дереве.

3.3. Удаление элемента

Другой распространенной операцией является удаление узла из дерева.

Во-первых, мы должны найти узел для удаления таким же образом, как мы делали раньше:

private Node deleteRecursive(Node current, int value) < if (current == null) < return null; >if (value == current.value) < // Node to delete found // . code to delete the node will go here >if (value < current.value) < current.left = deleteRecursive(current.left, value); return current; >current.right = deleteRecursive(current.right, value); return current; >

Как только мы найдем узел для удаления, есть 3 основных случая:

  • a node has no children – это простейший случай; нам просто нужно заменить этот узел наnull в его родительском узле
  • a node has exactly one child – в родительском узле, мы заменяем этот узел его единственным дочерним узлом.
  • a node has two children — это самый сложный случай, потому что он требует реорганизации дерева

Давайте посмотрим, как мы можем реализовать первый случай, когда узел является листовым:

if (current.left == null && current.right == null)

Теперь давайте продолжим случай, когда у узла есть один дочерний элемент:

if (current.right == null) < return current.left; >if (current.left == null)

Здесь мы возвращаем дочерний элементnon-null, чтобы его можно было назначить родительскому узлу.

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

Во-первых, нам нужно найти узел, который заменит удаленный узел. Мы будем использовать самый маленький узел из правого поддерева удаляемого узла:

private int findSmallestValue(Node root)

Затем мы присваиваем наименьшее значение удаляемому узлу, и после этого мы удалим его из правого поддерева:

int smallestValue = findSmallestValue(current.right); current.value = smallestValue; current.right = deleteRecursive(current.right, smallestValue); return current;

Наконец, давайте создадим общедоступный метод, который начнет удаление изroot:

public void delete(int value)

Теперь давайте проверим, что удаление работает должным образом:

@Test public void givenABinaryTree_WhenDeletingElements_ThenTreeDoesNotContainThoseElements()

4. Путешествие по дереву

В этом разделе мы увидим различные способы обхода дерева и подробно рассмотрим поиск в глубину и в ширину.

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

4.1. Поиск в глубину

Поиск в глубину — это тип обхода, который максимально углубляется в каждого ребенка, прежде чем исследовать следующего брата или сестру.

Есть несколько способов выполнить поиск в глубину: по порядку, предварительному заказу и после заказа.

Обход по порядку состоит из первого посещения левого поддерева, затем корневого узла и, наконец, правого поддерева:

public void traverseInOrder(Node node) < if (node != null) < traverseInOrder(node.left); System.out.print(" " + node.value); traverseInOrder(node.right); >>

Если мы вызовем этот метод, вывод консоли покажет порядок обхода:

Обход предварительного заказа посещает сначала корневой узел, затем левое поддерево и, наконец, правое поддерево:

public void traversePreOrder(Node node) < if (node != null) < System.out.print(" " + node.value); traversePreOrder(node.left); traversePreOrder(node.right); >>

И давайте проверим обход предварительного заказа в выводе консоли:

Обход после заказа посещает левое поддерево, правое поддерево и корневой узел в конце:

public void traversePostOrder(Node node) < if (node != null) < traversePostOrder(node.left); traversePostOrder(node.right); System.out.print(" " + node.value); >>

4.2. Поиск в ширину

Это еще один распространенный тип обходаvisits all the nodes of a level before going to the next level.

Этот вид обхода также называется уровнем порядка и охватывает все уровни дерева, начиная с корня и слева направо.

Для реализации мы будем использоватьQueue для упорядочивания узлов каждого уровня. Мы извлечем каждый узел из списка, распечатаем его значения, а затем добавим его дочерние элементы в очередь:

public void traverseLevelOrder() < if (root == null) < return; >Queue nodes = new LinkedList<>(); nodes.add(root); while (!nodes.isEmpty()) < Node node = nodes.remove(); System.out.print(" " + node.value); if (node.left != null) < nodes.add(node.left); >if (node.right!= null) < nodes.add(node.right); >> >

В этом случае порядок узлов будет:

Источник

Читайте также:  Example Internal Links
Оцените статью