diff --git a/src/com/walking/lesson42_tree/task1/Main.java b/src/com/walking/lesson42_tree/task1/Main.java index 082700c06..2a7b3851d 100644 --- a/src/com/walking/lesson42_tree/task1/Main.java +++ b/src/com/walking/lesson42_tree/task1/Main.java @@ -1,5 +1,12 @@ package com.walking.lesson42_tree.task1; +import com.walking.lesson42_tree.task1.model.Task; +import com.walking.lesson42_tree.task1.structure.BinarySearchTree; +import com.walking.lesson42_tree.task1.structure.BinarySearchTree_v2; + +import java.util.Comparator; +import java.util.List; + /** * Реализуйте бинарное дерево поиска. Учтите возможность использования дерева как для Comparable-сущностей, * так и для сортировки на основе компаратора. @@ -15,5 +22,91 @@ */ public class Main { public static void main(String[] args) { + Comparator taskNameComparator = new Comparator() { + @Override + public int compare(Task o1, Task o2) { + return o1.getName().compareTo(o2.getName()); + } + }; + + BinarySearchTree binarySearchTree = new BinarySearchTree<>(); + + List tasks = getMiddleList(); + + for (Task task : tasks) { + binarySearchTree.put(task); + } + + printTreeOverview(binarySearchTree); + + Task foundTask = binarySearchTree.get(tasks.get(0)); + System.out.println("Найдена задача: " + foundTask); + + Task removedTask = binarySearchTree.remove(tasks.get(0)); + System.out.println("Извлечена задача:" + removedTask); + + printTreeOverview(binarySearchTree); + + binarySearchTree.balance(); + + printTreeOverview(binarySearchTree); + + foundTask = binarySearchTree.get(tasks.get(0)); + System.out.println("Найдена задача: " + foundTask); + } + + public static List getAscendingList() { + Task task0 = new Task("05"); + Task task1 = new Task("10"); + Task task2 = new Task("15"); + Task task3 = new Task("19"); + Task task4 = new Task("20"); + Task task5 = new Task("21"); + Task task6 = new Task("25"); + Task task7 = new Task("30"); + Task task8 = new Task("35"); + + return List.of(task0, task1, task2, task3, task4, task5, task6, task7, task8); + } + + public static List getMiddleList() { + Task task0 = new Task("20"); + Task task1 = new Task("15"); + Task task2 = new Task("10"); + Task task3 = new Task("05"); + Task task4 = new Task("19"); + Task task5 = new Task("30"); + Task task6 = new Task("25"); + Task task7 = new Task("21"); + Task task8 = new Task("35"); + + return List.of(task0, task1, task2, task3, task4, task5, task6, task7, task8); + } + + public static void print(List list) { + for (Object object : list) { + System.out.print(object); + } + + System.out.println(); + } + + public static void printTreeOverview(BinarySearchTree tree) { + System.out.println(".".repeat(80)); + + System.out.println("Корень дерева: " + tree.getRoot()); + + System.out.println("Содержимое дерева в порядке возрастания ширины (BFS):"); + tree.breadthFirstSearchTraversal(); + System.out.println(); + + System.out.println("Содержимое дерева в порядке возрастания значений (DFS:inOrder):"); + List ascendingOrder = tree.getAscendingOrder(); + print(ascendingOrder); + + System.out.println("min = " + tree.getMin()); + System.out.println("max = " + tree.getMax()); + + System.out.println(".".repeat(80)); } -} +} \ No newline at end of file diff --git a/src/com/walking/lesson42_tree/task1/model/Task.java b/src/com/walking/lesson42_tree/task1/model/Task.java new file mode 100644 index 000000000..9a2e9b532 --- /dev/null +++ b/src/com/walking/lesson42_tree/task1/model/Task.java @@ -0,0 +1,44 @@ +package com.walking.lesson42_tree.task1.model; + +import java.util.Objects; + +public class Task implements Comparable { + private final String name; + + public Task(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "[%s]".formatted(name); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Task task = (Task) o; + + return Objects.equals(name, task.name); + } + + @Override + public int hashCode() { + return name != null ? name.hashCode() : 0; + } + + @Override + public int compareTo(Task o) { + return name.compareTo(o.getName()); + } +} \ No newline at end of file diff --git a/src/com/walking/lesson42_tree/task1/structure/BinarySearchTree.java b/src/com/walking/lesson42_tree/task1/structure/BinarySearchTree.java new file mode 100644 index 000000000..a4a60069b --- /dev/null +++ b/src/com/walking/lesson42_tree/task1/structure/BinarySearchTree.java @@ -0,0 +1,264 @@ +package com.walking.lesson42_tree.task1.structure; + +import java.util.*; + +public class BinarySearchTree { + private Node root; + private final Comparator comparator; + + public BinarySearchTree() { + comparator = null; + } + + public BinarySearchTree(Comparator comparator) { + this.comparator = comparator; + } + + public E getRoot() { + return root != null ? root.value : null; + } + + public E get(E key) { + Node found = get(key, root); + + return found != null ? found.value : null; + } + + public E getMin() { + Node minNode = getMinNode(root); + + return minNode != null ? minNode.value : null; + } + + public E getMax() { + Node maxNode = getMaxNode(root); + + return maxNode != null ? maxNode.value : null; + } + + public void put(E key) { + root = put(key, root); + } + + public E remove(E key) { + Node removed = removeNode(key, root, null); + + return removed != null ? removed.value : null; + } + + public List getAscendingOrder() { + List list = new ArrayList<>(); + + inOrderToList(root, list); + + return list; + } + + public void breadthFirstSearchTraversal() { + if (root == null) { + return; + } + + Queue> unvisitedNodes = new ArrayDeque<>(); + Node current; + + unvisitedNodes.offer(root); + + while (!unvisitedNodes.isEmpty()) { + current = unvisitedNodes.poll(); + + visitNode(current); + + if (current.hasLeftChild()) { + unvisitedNodes.offer(current.left); + } + + if (current.hasRightChild()) { + unvisitedNodes.offer(current.right); + } + } + } + + public void balance() { + if (root == null) { + return; + } + + List inorder = getInOrderList(); + List middles = new ArrayList<>(inorder.size()); + + middleExtraction(inorder, middles); + + BinarySearchTree balanced = new BinarySearchTree(this.comparator); + + for (E middle : middles) { + balanced.put(middle); + } + + this.root = balanced.root; + } + + private void visitNode(Node current) { + System.out.print(current.value); + } + + private Node get(E key, Node localRoot) { + if (localRoot == null) { + return null; + } + + int route = getRouteByKey(key, localRoot.value); + + return isSearchHit(route) ? + localRoot : + isLeftBranch(route) ? get(key, localRoot.left) : get(key, localRoot.right); + } + + private Node getMinNode(Node localRoot) { + return (localRoot != null && localRoot.hasLeftChild()) ? getMinNode(localRoot.left) : localRoot; + } + + private Node getMaxNode(Node localRoot) { + return (localRoot != null && localRoot.hasRightChild()) ? getMaxNode(localRoot.right) : localRoot; + } + + private Node put(E key, Node localRoot) { + if (localRoot == null) { + return new Node<>(key); + } + + int route = getRouteByKey(key, localRoot.value); + + if (isLeftBranch(route)) { + localRoot.left = put(key, localRoot.left); + } else { + localRoot.right = put(key, localRoot.right); + } + + return localRoot; + } + + private Node removeNode(E key, Node localRoot, Node parent) { + if (localRoot == null) { + return null; + } + + int route = getRouteByKey(key, localRoot.value); + + return isSearchHit(route) ? + removeFound(localRoot, parent) : + isLeftBranch(route) ? + removeNode(key, localRoot.left, localRoot) : + removeNode(key, localRoot.right, localRoot); + } + + private Node removeFound(Node found, Node parent) { + Node newborn = found.hasTwoChild() ? removeSuccessor(found, parent) : getFoundChild(found); + + linkParentWithNewborn(found, parent, newborn); + + return found; + } + + private Node removeSuccessor(Node found, Node parent) { + Node successor = getMaxNode(found.left); + + removeNode(successor.value, found, parent); + + successor.left = found.left; + successor.right = found.right; + + return successor; + } + + private Node getFoundChild(Node found) { + return found.hasLeftChild() ? found.left : found.right; + } + + private void linkParentWithNewborn(Node found, Node parent, Node newborn) { + if (found == root) { + root = newborn; + } else if (found == parent.left) { + parent.left = newborn; + } else { + parent.right = newborn; + } + } + + private void middleExtraction(List source, List middles) { + if (source.size() == 0) { + return; + } + + int middleIndex = source.size() / 2; + middles.add(source.get(middleIndex)); + + middleExtraction(source.subList(0, middleIndex), middles); + middleExtraction(source.subList(++middleIndex, source.size()), middles); + } + + private List getInOrderList() { + List list = new ArrayList<>(); + + inOrderToList(root, list); + + return list; + } + + private void inOrderToList(Node localRoot, List list) { + if (localRoot == null) { + return; + } + + inOrderToList(localRoot.left, list); + + list.add(localRoot.value); + + inOrderToList(localRoot.right, list); + } + + private int getRouteByKey(E keyValue, E currentValue) { + if (comparator != null) { + return comparator.compare(keyValue, currentValue); + } else { + Comparable comparable = (Comparable) keyValue; + return comparable.compareTo(currentValue); + } + } + + private boolean isLeftBranch(int result) { + return result <= 0; + } + + private boolean isSearchHit(int route) { + return route == 0; + } + + private static class Node { + private final E value; + private Node left; + private Node right; + + public Node(E value) { + this.value = value; + } + + public Node(E value, Node left, Node right) { + this.value = value; + this.left = left; + this.right = right; + } + + private boolean hasTwoChild() { + return right != null && left != null; + } + + private boolean hasLeftChild() { + return left != null; + } + + private boolean hasRightChild() { + return right != null; + } + } +} \ No newline at end of file diff --git a/src/com/walking/lesson42_tree/task1/structure/BinarySearchTree_v2.java b/src/com/walking/lesson42_tree/task1/structure/BinarySearchTree_v2.java new file mode 100644 index 000000000..03c5d76ad --- /dev/null +++ b/src/com/walking/lesson42_tree/task1/structure/BinarySearchTree_v2.java @@ -0,0 +1,264 @@ +package com.walking.lesson42_tree.task1.structure; + +import java.util.*; + +/** + * При удалении узлы помечаются как удаленные, но остаются в дереве. + *

+ * Итеративные алгоритмы поиска, добавления и удаления. + */ +public class BinarySearchTree_v2 { + private Node root; + private final Comparator comparator; + + public BinarySearchTree_v2() { + comparator = null; + } + + public BinarySearchTree_v2(Comparator comparator) { + this.comparator = comparator; + } + + public E getRoot() { + return root != null ? root.value : null; + } + + public E get(E key) { + Node found = get(key, root); + + return found != null ? found.value : null; + } + + public E getMin() { + Node found = getMinNode(root); + + return found != null ? found.value : null; + } + + public E getMax() { + Node found = getMaxNode(root); + + return found != null ? found.value : null; + } + + public E put(E key) { + if (root == null) { + root = new Node<>(key); + + return root.value; + } + + int route = getRouteByKey(key, root.value); + + if (isSearchHit(route) && root.isDeleted) { + root = new Node<>(key, root.left, root.right); + + return root.value; + } + + Node parent = root; + Node current = isLeftBranch(route) ? root.left : root.right; + Node newborn = new Node<>(key); + + while (current != null) { + route = getRouteByKey(key, current.value); + + if (isSearchHit(route) && current.isDeleted) { + newborn.left = current.left; + newborn.right = current.right; + current = null; + } else { + parent = current; + current = isLeftBranch(route) ? current.left : current.right; + } + } + + if (isLeftBranch(route)) { + parent.left = newborn; + } else { + parent.right = newborn; + } + + return newborn.value; + } + + public E remove(E key) { + Node removed = get(key, root); + + if (removed != null) { + removed.isDeleted = true; + } + + return removed != null ? removed.value : null; + } + + public List getAscendingOrder() { + List inOrder = new ArrayList<>(); + + inOrderToList(root, inOrder); + + return inOrder; + } + + public void breadthFirstSearchTraversal() { + if (root == null) { + return; + } + + Queue> unvisitedNodes = new ArrayDeque<>(); + Node current; + + unvisitedNodes.offer(root); + + while (!unvisitedNodes.isEmpty()) { + current = unvisitedNodes.poll(); + + visitNode(current); + + if (current.hasLeftChild()) { + unvisitedNodes.offer(current.left); + } + + if (current.hasRightChild()) { + unvisitedNodes.offer(current.right); + } + } + } + + public void balance() { + if (root == null) { + return; + } + + List inorder = getInOrderList(); + List middles = new ArrayList<>(inorder.size()); + + middleExtraction(inorder, middles); + + BinarySearchTree_v2 balanced = new BinarySearchTree_v2<>(this.comparator); + + for (E middle : middles) { + balanced.put(middle); + } + + this.root = balanced.root; + } + + private void visitNode(Node current) { + if (!current.isDeleted) { + System.out.print(current.value); + } + } + + private Node get(E key, Node localRoot) { + int route; + + while (localRoot != null) { + route = getRouteByKey(key, localRoot.value); + + if (isSearchHit(route) && !localRoot.isDeleted) { + return localRoot; + } + + localRoot = isLeftBranch(route) ? localRoot.left : localRoot.right; + } + + return null; + } + + private Node getMinNode(Node localRoot) { + if (localRoot == null) { + return null; + } + + Node found = getMinNode(localRoot.left); + + return found != null ? found : !localRoot.isDeleted ? localRoot : getMinNode(localRoot.right); + } + + private Node getMaxNode(Node localRoot) { + if (localRoot == null) { + return null; + } + + Node found = getMaxNode(localRoot.right); + + return found != null ? found : !localRoot.isDeleted ? localRoot : getMinNode(localRoot.left); + } + + private void middleExtraction(List source, List middles) { + if (source.size() == 0) { + return; + } + + int middleIndex = source.size() / 2; + middles.add(source.get(middleIndex)); + + middleExtraction(source.subList(0, middleIndex), middles); + middleExtraction(source.subList(++middleIndex, source.size()), middles); + } + + private List getInOrderList() { + List inOrder = new ArrayList<>(); + + inOrderToList(root, inOrder); + + return inOrder; + } + + private void inOrderToList(Node localRoot, List list) { + if (localRoot == null) { + return; + } + + inOrderToList(localRoot.left, list); + + if (!localRoot.isDeleted) { + list.add(localRoot.value); + } + + inOrderToList(localRoot.right, list); + } + + private int getRouteByKey(E keyValue, E currentValue) { + if (comparator != null) { + return comparator.compare(keyValue, currentValue); + } else { + Comparable comparable = (Comparable) keyValue; + return comparable.compareTo(currentValue); + } + } + + private boolean isSearchHit(int comparisonResult) { + return comparisonResult == 0; + } + + private boolean isLeftBranch(int comparisonResult) { + return comparisonResult <= 0; + } + + private static class Node { + private final E value; + private Node left; + private Node right; + private boolean isDeleted; + + public Node(E value) { + this.value = value; + } + + public Node(E value, Node left, Node right) { + this.value = value; + this.left = left; + this.right = right; + } + + private boolean hasLeftChild() { + return left != null; + } + + private boolean hasRightChild() { + return right != null; + } + } +}