Оригинал: Chapter 13 Binary search tree
Переводчик: Летающий дракон
Протокол: CC BY-NC-SA 4.0
Гордо использую Google Translate
В этой главе представлено решение предыдущего упражнения, а также проводится тестирование производительности древовидных структур данных. Я продемонстрировал проблему реализации и объяснил, как Java TreeMap
решает её.
В предыдущем упражнении я дал вам план MyTreeMap
и попросил заполнить недостающие методы. Теперь я покажу результаты, начиная с findNode
:
private Node findNode(Object target) {
// some implementations can handle null as a key, but not this one
if (target == null) {
throw new IllegalArgumentException();
}
// something to make the compiler happy
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) target;
// the actual search
Node node = root;
while (node != null) {
int cmp = k.compareTo(node.key);
if (cmp < 0)
node = node.left;
else if (cmp > 0)
node = node.right;
else
return node;
}
return null;
}
findNode
— это частный метод, используемый containsKey
и get
; он не является частью интерфейса Map
. Параметр target
— это ключ, который мы ищем. В предыдущем упражнении я объяснил первую часть этого метода:
null
не является допустимым значением ключа.compareTo
на target
, мы должны привести его к определённому типу Comparable
. Здесь используется «тип-заполнитель», который допускает как можно больше; то есть он работает для любого типа, реализующего Comparable
, и чей compareTo
принимает K
или суперкласс K
.Затем фактический поиск становится довольно простым. Мы инициализируем переменную цикла node
, которая ссылается на корневой узел. На каждой итерации мы сравниваем цель с node.key
. Если цель меньше текущего ключа, мы переходим к левому поддереву. Если она больше, мы переходим к правому поддереву. Если они равны, мы возвращаем текущий узел.
Если мы не находим цель и достигаем дна дерева, я предполагаю, что её нет в дереве, и возвращаю null
.
В предыдущей главе я объяснил, что время выполнения findNode
пропорционально высоте дерева, а не количеству узлов, потому что нам не нужно искать всё дерево. Однако для containsValue
мы должны искать значение, а не ключ; свойства BST не применимы к значениям, поэтому мы должны искать всё дерево.
Мой подход рекурсивен:
public boolean containsValue(Object target) {
return containsValueHelper(root, target);
}
private boolean containsValueHelper(Node node, Object target) {
if (node == null) {
return false;
}
if (equals(target, node.value)) {
return true;
}
if (containsValueHelper(node.left, target)) {
return true;
}
if (containsValueHelper(node.right, target)) {
return true;
}
return false;
}
containsValue
принимает целевое значение в качестве параметра и немедленно вызывает containsValueHelper
, передавая корневой узел дерева в качестве дополнительного параметра.
Вот как работает containsValueHelper
:
if
проверяет граничное условие рекурсии. Если node
равен null
, это означает, что мы уже достигли дна дерева и не нашли target
, поэтому мы должны вернуть false
. Обратите внимание, что это просто означает, что цель не была найдена на этом пути; она всё ещё может быть обнаружена на другом пути.true
. В противном случае мы должны продолжить.target
в левом поддереве. Если мы найдём его, мы можем сразу же вернуть true
, не ища правое поддерево. В противном случае продолжаем.true
. В противном случае, когда мы пройдём по всему дереву, мы вернём false
.Этот метод «посещает» каждый узел в дереве, поэтому его время выполнения пропорционально количеству узлов.
Метод put
немного сложнее, чем get
, поскольку он должен обрабатывать два случая: (1) если заданный ключ уже находится в дереве, заменить и вернуть старое значение; (2) в противном случае необходимо добавить новый узел в дерево в правильном месте.
В предыдущем упражнении я предоставил этот начальный код:
public V put(K key, V value) {
if (key == null) {
throw new IllegalArgumentException();
}
if (root == null) {
root = new Node(key, value);
size++;
return null;
}
return putHelper(root, key, value);
}
И попросил вас заполнить putHelper
. Вот мой ответ:
private V putHelper(Node node, K key, V value) {
Comparable<? super K> k = (Comparable<? super K>) key;
int cmp = k.compareTo(node.key);
if (cmp < 0) {
if (node.left == null) {
node.left = new Node(key, value);
size++;
return null;
} else {
return putHelper(node.left, key, value);
}
}
if (cmp > 0) {
if (node.right == null) {
node.right = new Node(key, value);
size++;
return null;
} else {
return putHelper(node.right, key, value);
}
}
V oldValue = node.value;
node.value = value;
return oldValue;
}
Первый параметр node
изначально является корнем дерева, но каждый раз, когда мы выполняем рекурсивный вызов, он указывает на другое поддерево. Как и в случае с get
, мы используем compareTo
, чтобы определить, по какому пути следовать по дереву. Если cmp < 0
, добавляемый ключ меньше node.key
, тогда мы идём влево. Есть два случая:
node.left
равно null
, мы достигли дна дерева без нахождения key
. В этом случае мы знаем, что key
отсутствует в дереве, и мы знаем, где его следует разместить. Поэтому мы создаём новый узел и добавляем его как левое поддерево node
.Если cmp > 0
, то добавляемый ключ больше node.key
, и мы идём вправо. Мы обрабатываем эти два случая так же, как и предыдущий раздел. Наконец, если cmp == 0
, мы нашли ключ в дереве, затем мы изменяем его и возвращаем старое значение.
Я использовал рекурсию для написания этого метода, чтобы сделать его более читаемым, но его можно было бы переписать напрямую с использованием итерации, которую вы, возможно, захотите попробовать в качестве упражнения.
Примечание: в тексте запроса присутствуют фрагменты кода на языке Java. Перевод этих фрагментов выполнен с помощью сервиса машинного перевода.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )