Как найти индекс элемента в TreeSet?

Я использую TreeSet<Integer> и просто хочу найти индекс числа в наборе. Есть ли хороший способ сделать это, который фактически использует сложность O (log (n)) двоичных деревьев?

(Если нет, что мне делать, и кто-нибудь знает, почему? Мне любопытно, почему такой класс будет включен в Java без чего-то вроде функции поиска.)


person jtbandes    schedule 27.10.2011    source источник
comment
По крайней мере, его можно найти в O(n) с итерацией ... меня не удивит, если будет альтернатива в Гуаве или в одной из библиотек апача.   -  person    schedule 27.10.2011
comment
Для целых чисел более эффективным может быть обычный массив int[]. (вы можете отсортировать его заранее, а затем использовать Arrays.binarySearch). И это более эффективно с точки зрения использования памяти во всех случаях, потому что нет бокса.   -  person Display Name    schedule 12.03.2016


Ответы (5)


Как указывает @Yrlec, set.headSet(element).size вернет 0, хотя в наборе нет этого элемента. Так что лучше проверить:

 return set.contains(element)? set.headSet(element).size(): -1;

Вот тестовый пример, показывающий проблему:

public static void main(String args[]){
    TreeSet<Integer> set = new TreeSet<>();
    set.add(4);
    set.add(2);
    set.add(3);
    set.add(1);

    System.out.println(set.headSet(1).size());//0
    System.out.println(set.headSet(2).size());//1
    System.out.println(set.headSet(3).size());//2
    System.out.println(set.headSet(4).size());//3
    System.out.println(set.headSet(-1).size());//0!!Caution,returns 0 though it does not exist!

}
person JaskeyLam    schedule 24.04.2015
comment
Это имеет O(n) сложность. - person Gedrox; 07.05.2018
comment
Я бы, наверное, сделал contains()только если headSet(...).size() == 0? - person Erk; 15.01.2021

Я некоторое время копался в TreeSet и его интерфейсах, и лучший способ, который я нашел, чтобы получить индекс элемента, это:

set.headSet(element).size()

headSet(element) возвращает под-TreeSet элементов меньше, чем его аргумент, поэтому размер этого набора будет индексом рассматриваемого элемента. Действительно странное решение.

person jtbandes    schedule 27.10.2011
comment
Примечание: это не работает, если элемент отсутствует в наборе. В этом случае он возвращает 0, что неверно (более подходящим было бы -1). - person Yrlec; 27.04.2012
comment
@Yrlec ... Было бы хорошо, если бы мы могли сначала проверить, содержит ли этот TreeSet элемент, который мы ищем. - person Vikram; 11.04.2013

https://github.com/geniot/indexed-tree-map

У меня такая же проблема. Поэтому я взял исходный код java.util.TreeMap и написал IndexedTreeMap. Он реализует мою собственную IndexedNavigableMap:

public interface IndexedNavigableMap<K, V> extends NavigableMap<K, V> {
   K exactKey(int index);
   Entry<K, V> exactEntry(int index);
   int keyIndex(K k);
}

Реализация основана на обновлении весов узлов в красно-черном дереве при его изменении. Вес — это количество дочерних узлов под данным узлом плюс один собственный. Например, когда дерево повернуто влево:

    private void rotateLeft(Entry<K, V> p) {
    if (p != null) {
        Entry<K, V> r = p.right;

        int delta = getWeight(r.left) - getWeight(p.right);
        p.right = r.left;
        p.updateWeight(delta);

        if (r.left != null) {
            r.left.parent = p;
        }

        r.parent = p.parent;


        if (p.parent == null) {
            root = r;
        } else if (p.parent.left == p) {
            delta = getWeight(r) - getWeight(p.parent.left);
            p.parent.left = r;
            p.parent.updateWeight(delta);
        } else {
            delta = getWeight(r) - getWeight(p.parent.right);
            p.parent.right = r;
            p.parent.updateWeight(delta);
        }

        delta = getWeight(p) - getWeight(r.left);
        r.left = p;
        r.updateWeight(delta);

        p.parent = r;
    }
  }

updateWeight просто обновляет веса до корня:

   void updateWeight(int delta) {
        weight += delta;
        Entry<K, V> p = parent;
        while (p != null) {
            p.weight += delta;
            p = p.parent;
        }
    }

И когда нам нужно найти элемент по индексу, вот реализация, использующая веса:

public K exactKey(int index) {
    if (index < 0 || index > size() - 1) {
        throw new ArrayIndexOutOfBoundsException();
    }
    return getExactKey(root, index);
}

private K getExactKey(Entry<K, V> e, int index) {
    if (e.left == null && index == 0) {
        return e.key;
    }
    if (e.left == null && e.right == null) {
        return e.key;
    }
    if (e.left != null && e.left.weight > index) {
        return getExactKey(e.left, index);
    }
    if (e.left != null && e.left.weight == index) {
        return e.key;
    }
    return getExactKey(e.right, index - (e.left == null ? 0 : e.left.weight) - 1);
}

Также очень удобно найти индекс ключа:

    public int keyIndex(K key) {
    if (key == null) {
        throw new NullPointerException();
    }
    Entry<K, V> e = getEntry(key);
    if (e == null) {
        throw new NullPointerException();
    }
    if (e == root) {
        return getWeight(e) - getWeight(e.right) - 1;//index to return
    }
    int index = 0;
    int cmp;
    if (e.left != null) {
        index += getWeight(e.left);
    }
    Entry<K, V> p = e.parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        while (p != null) {
            cmp = cpr.compare(key, p.key);
            if (cmp > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    } else {
        Comparable<? super K> k = (Comparable<? super K>) key;
        while (p != null) {
            if (k.compareTo(p.key) > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    }
    return index;
}

Вы можете найти результат этой работы по адресу https://github.com/geniot/indexed-tree-map

person Vitaly Sazanovich    schedule 08.02.2013
comment
Ты Бог! - person swooby; 17.03.2016
comment
Спасибо большое, Виталий! Только одна проблема: IndexedTreeSet.subLis аварийно завершает работу с java.lang.IllegalArgumentException: Map должен реализовать IndexedTreeMap в com.dictiography.collections.IndexedTreeSet.‹init›(IndexedTreeSet.java:98) в com.dictiography.collections.IndexedTreeSet.subSet(IndexedTreeSet.java :318) в com.dictiography.collections.IndexedTreeSet.subSet(IndexedTreeSet.java:354) - person isabsent; 01.12.2018
comment
Другая полезная библиотека iamsoft.com/products /util/javadoc///com/jshift/util/коллекции/ - person isabsent; 05.12.2018
comment
Используйте github.com/geniot/indexed-tree-map/issues, чтобы сообщать о любых проблемах. - person Vitaly Sazanovich; 27.06.2021

Класс TreeSet в Java не имеет возможности найти индекс числа в наборе. Для этого вам нужно будет предоставить свою собственную реализацию - это красно-черное дерево под капотом, и его можно расширить для поддержки операции индекса. Взгляните на процедуру OS-RANK в главе «Увеличение структур данных» «Введение в алгоритмы», это именно то, о чем вы просите.

person Óscar López    schedule 27.10.2011
comment
TreeSet может сделать это, но неэффективно. - person mpartel; 30.07.2017

здесь покажите мою функцию:

//ФУНКЦИЯ ДЛЯ ЗАДАНИЯ СТРОКОВОЙ ПОЗИЦИИ В ДЕРЕВО

private static void get_posistion(TreeSet abre, String codig) {
    Iterator iterator;
    iterator = abre.iterator();
    int cont = 0;
    String prova = "";

    while (iterator.hasNext()) {                      
        prova = iterator.next().toString();
        if (codig.equals(prova)) {
            System.out.println(cont);
        } else {
            cont++;
        }
    }
}
person Geff    schedule 16.11.2015