Как определить общие диапазоны значений (включая открытые диапазоны) для проверки общего двоичного дерева поиска с ключами произвольного типа

#java #generics #data-structures #binary-search-tree #generic-programming

#java #общие #структуры данных #binary-search-tree #generic-программирование

Вопрос:

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

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

  public boolean checkBST(Node root) {
    int min = Integer.MIN_VALUE;
    int max = Integer.MAX_VALUE;
    return validateBST(root, min, max);
  }
  

Ссылка : https://en.wikipedia.org/wiki/Binary_search_tree#Verification

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

Это легко сделать с помощью оболочек числового примитивного типа, таких как Integer (пример выше) или Double , если вы фиксируете определенный тип элемента. Однако мне нужно обобщить этот подход, чтобы он работал с любым заданным T типом (это может быть Number или что-то совершенно другое).

Мы можем предположить, что T это Comparable<? extends T> или что соответствующий компаратор передается при построении дерева.

Как я могу это сделать?

Комментарии:

1. Публикация validateBST() . Вы пытаетесь проверить, что значение находится в пределах диапазона? Каковы допустимые типы этого значения? любое число?

2. «как минимальный и максимальный диапазоны имеют смысл для любого пользовательского типа. скажем., Employee, Имена» Вам решать, как сравнивать и определять, какой из них больше, и реализовывать compareTo в этих объектах. Например, сделать сопоставимого сотрудника . Если вы знаете, как сравнивать эти объекты, вы можете определить min max.

3. Я думаю, что теперь я получаю то, о чем вы просите, я подозреваю, что запрашивается общая версия Integer. MIN_VALUE и Integer. MAX_VALUE для любого заданного T не только int s, поэтому что-то вроде T.MIN_VALUE и T.MAX_VALUE . Я должен сказать, что ваш вопрос не очень понятен, возможно, вам следует подумать о том, чтобы перефразировать его. Я отправил ответ, который в меру своих возможностей устраняет такую проблему.

4. «Я подозреваю, что запрашивается общая версия Integer. MIN_VALUE и Integer. MAX_VALUE для любого заданного T » Я не думаю, что это практически необходимо. Вы могли бы выполнить итерацию по всему графику, найти минимальные, максимальные значения и использовать эти значения.

5. Это правда, что сложность не увеличивается, если дерево сбалансировано, максимальное или минимальное значение должно быть O (log n), но это не обязательно. Также вы могли бы отслеживать минимальные и максимальные значения по мере вставки значений. У вас есть свои варианты

Ответ №1:

Для сравнения объектов вам необходимо их реализовать Comparable поэтому определите тип вашего узла как подкласс Comparable :

 class Node<Q extends Comparable<Q>>{

    private final Q value;
    private Node left, right;   

    Node getLeft() {
        return left;
    }

    Node getRight() {
        return right;
    }

    Node(Q value){
        this.value = value;
    }

    Q getValue(){
        return value;
    }
}
  

Когда Q реализуется comparable, вы можете сравнить два объекта, например:

 Node<String> minNode = new Node<>("Z");
Node<String> maxNode = new Node<>("A");
Node<String> aNode = new Node<>("L");
System.out.println(aNode.getValue().compareTo(minNode.getValue())< 0 amp;amp;
                            aNode.getValue().compareTo(maxNode.getValue()) > 0 );
  

Имея возможность сравнивать, вы должны быть в состоянии определить boolean isBST(Node node, Node minNode, Node maxNode)

Редактировать: Если ваш вопрос касается «общей версии Integer.MIN_VALUE и Integer.MAX_VALUE для любого заданного T»:
Я не думаю, что это практически необходимо. Вы могли бы выполнить итерацию по всему графику, найти минимальные, максимальные значения и использовать эти значения.

Комментарии:

1. Я уже реализовал Comparable и могу использовать compareTo для любого типа объекта. Теперь здесь вы говорите «Z» и «A», что не является практическим ограничением для строки. Моя проблема НЕ в том, как выполнить сравнение, моя проблема в том, как вы определяете минимальный и максимальный диапазон для любого типа данных.

2. что, если BST строк содержит «@testValue» , очевидно, что ограниченное определение не будет работать.

3. Я не уверен, что понимаю. String реализует Comparable , чтобы вы могли сравнивать любые две строки.

4. minNode = новый узел<строка> («A»); как насчет строки «a» или чего-нибудь меньшего, чем это. Если мой BST имеет следующие входные данные, которые должны быть собраны в виде дерева BST. «__a», «@b», «1abc» и т.д.

5. для пользовательского класса, такого как ‘Address’ , я могу compareTo для определенного примитивного типа или строки и минимального диапазона, который должен быть определен для этого. Я пытаюсь сделать этот класс BST универсальным классом — должен ли я кодировать конкретный метод для возврата минимального и максимального диапазона, зависит от типа.

Ответ №2:

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

К сожалению, в общем случае произвольный T не обязательно имеет инстанцируемый минимум и максимум. На самом деле int и Integer делаю только потому, что из-за ограничения представления они не могут быть сколь угодно большими или маленькими.

Например, для String можно утверждать, что тогда пустая строка («») является минимальной, безусловно, меньше или равна любой другой, String используя ее естественный порядок, но какова была бы максимальная строка. Вероятно, это привело бы к бесконечно долгому повторению максимального символа Юникода, поэтому вы не можете создать такой объект.

Однако это не должно мешать вам определять диапазоны, которые включали бы любое значение или которые были бы открыты на одном конце (т. Е. у них есть min, но не max или наоборот).

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

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

 class BST<T extends Comparable<T>> {
   // ...
   public boolean checkBST(Node<T> root) {
      return validateBST(root, null, null);
   }
   // ...
   boolean validateBST(Node<T> node, T min, T max) {
         if (node == null) {
            // nothing to do here.
            return true;
         }
         final T value = node.getValue();
         if (min != null amp;amp; min.compareTo(value) >= 0) {
             return false;
         } else if (max != null amp;amp; max.compareTo(value) <= 0) {
             return false;
         } else {
             return validateBST(node.getLeft(), min, value) amp;amp;
                    validateBST(node.getRight(), value, max);
         }
   }
   // ...
}
  

Теперь некоторым людям может не понравиться видимое использование null для этого. В таком случае вам может потребоваться определить Range<T> класс, который его инкапсулирует:

 public class Range<T extends Comparable<T>> {
    private T min;
    private T max;

    private Range(T min, T max) {
       this.min = min;
       this.max = max;
    }

    public static <T> of(T min, T max) {
       Objects.requiresNonNull(min);
       Objects.requiresNonNull(max);
       return new Range(min, max);
    }

    public static <T> from(T min) {
       Objects.requiresNonNull(min);
       return new Range(min, null);
    }

    public static <T> to(T max) {
       Objects.requiresNonNull(max);
       return new Range(null, max);
    }

    public static <T> all() {
       return new Range(null, null);
    }

    public Range<T> subRangeTo(T max) {
       Objects.requiresNonNull(max);
       return new Range<>(this.min, max);
    }

    public Range<T> subRangeFrom(T min) {
       Objects.requiresNonNull(min);
       return new Range<>(min, this.max);
    }

    public boolean encloses(T value) {
       Objects.requiresNonNull(value);
       return (min == null || min.compareTo(value) < 0) 
            amp;amp;  (max == null || max.compareTo(value) > 0);
    }
}

  

Тогда код в validate более тривиален:

    // ...
   public boolean checkBST(Node<T> root) {
     return validateBST(root, Range.all());
   }
   // ...
   boolean validateBST(Node<T> node, Range<T> range) {
         if (node == null) {
            return true;
         }
         if (!range.encloses(node.getValue)) {
            return false;
         } else {
            return validateBST(node.getLeft(), Range.subRangeTo(value)) 
                   amp;amp; validateBST(node.getRight(), Range.subRangeFrom(value));
         }
   }
   // ...

  

Обратите внимание, что диапазоны в любом решении не включают предельные значения.

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

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