Сортований список масивів у Java


85

Мене бентежить, що я не можу знайти швидку відповідь на це. По суті, я шукаю структуру даних у Java, яка реалізує java.util.Listінтерфейс, але яка зберігає своїх членів у відсортованому порядку. Я знаю, що ви можете використовувати звичайний ArrayListі використовувати Collections.sort()на ньому, але у мене є сценарій, коли я час від часу додаю та часто отримую членів зі свого списку, і я не хочу, щоб сортувати його кожного разу, коли я отримую учасника на випадок додано новий. Хто-небудь може вказати мені на таке, що існує в JDK або навіть у сторонніх бібліотеках?

РЕДАГУВАТИ : в структурі даних потрібно зберегти дублікати.

РЕЗЮМЕ ВІДПОВІДІ : Мені все це було дуже цікаво і я багато чому навчився. Aioobe, зокрема, заслуговує на увагу за його наполегливість у спробі досягти моїх вимог вище (в основному відсортована реалізація java.util.List, яка підтримує дублікати). Я прийняв його відповідь як найбільш точну для того, про що я просив, і найбільш спонукає до наслідків того, що я шукав, навіть якщо те, що я запитував, було не зовсім тим, що мені потрібно.

Проблема того, про що я просив, полягає в самому інтерфейсі List і концепції додаткових методів в інтерфейсі. Щоб процитувати javadoc:

Користувач цього інтерфейсу має точний контроль над тим, куди у списку вставляється кожен елемент.

Вставка у відсортований список не має точного контролю над точкою вставки. Потім вам слід подумати, як ви будете обробляти деякі з методів. Візьмемо addдля прикладу:

загальнодоступне логічне додавання (Об'єкт o)

 Appends the specified element to the end of this list (optional operation).

Тепер ви опинилися в незручній ситуації: 1) розірвання контракту та реалізація відсортованої версії add; 2) надання дозволу на addдодавання елемента в кінець списку, порушення вашого відсортованого порядку; 3) залишення add(як необов’язкове), UnsupportedOperationExceptionі реалізації іншого методу , який додає елементи в відсортованому порядку.

Варіант 3, мабуть, найкращий, але я вважаю несмачним наявність методу додавання, який ви не можете використовувати, та іншого методу sortedAdd, якого немає в інтерфейсі.

Інші супутні рішення (без певного порядку):

  • java.util.PriorityQueue, який, мабуть, найближчий до того, що мені потрібно, ніж те, про що я просив. Черга - не найточніше визначення колекції об’єктів у моєму випадку, але функціонально вона робить все, що мені потрібно.
  • net.sourceforge.nite.util.SortedList . Однак ця реалізація порушує контракт інтерфейсу List, реалізуючи сортування в add(Object obj)методі, і химерно не має методу ефекту для add(int index, Object obj). Загальний консенсус припускає, що throw new UnsupportedOperationException()в цьому сценарії кращим вибором може бути.
  • TreeMultiSet від Guava Набір реалізацій, що підтримує дублікати
  • ca.odell.glazedlists.SortedList Цей клас містить застереження у своєму javadoc:Warning: This class breaks the contract required by List

4
Якщо ви іноді вставляєте і часто читаєте, чому б просто не відсортувати його під час вставки?
serg

Відповіді:


62

Мінімалістичне рішення

Ось «мінімальне» рішення.

class SortedArrayList<T> extends ArrayList<T> {

    @SuppressWarnings("unchecked")
    public void insertSorted(T value) {
        add(value);
        Comparable<T> cmp = (Comparable<T>) value;
        for (int i = size()-1; i > 0 && cmp.compareTo(get(i-1)) < 0; i--)
            Collections.swap(this, i, i-1);
    }
}

Вставка виконується в лінійний час, але це все одно ви отримаєте, використовуючи ArrayList (усі елементи праворуч від вставленого елемента повинні бути зміщені в ту чи іншу сторону).

Вставка чогось непорівнянного результату в ClassCastException. (Це також підхід, який застосовується PriorityQueueтакож: Пріоритетна черга, яка покладається на природне впорядкування, також не дозволяє вставляти непорівнянні об'єкти (це може призвести до ClassCastException). )

Перевизначення List.add

Зверніть увагу, що перевизначення List.add(або, List.addAllщо стосується цього), щоб вставити елементи впорядковано, буде прямим порушенням специфікації інтерфейсу . Що ви могли б зробити, це перевизначити цей метод, щоб кинути UnsupportedOperationException.

З документів List.add:

boolean add(E e)
    Додає зазначений елемент до кінця цього списку (необов’язкова операція).

Однакові міркування стосуються обох версій add, обох версій addAllта set. (Усі вони є необов’язковими операціями відповідно до інтерфейсу списку.)


Деякі тести

SortedArrayList<String> test = new SortedArrayList<String>();

test.insertSorted("ddd");    System.out.println(test);
test.insertSorted("aaa");    System.out.println(test);
test.insertSorted("ccc");    System.out.println(test);
test.insertSorted("bbb");    System.out.println(test);
test.insertSorted("eee");    System.out.println(test);

.... відбитки:

[ddd]
[aaa, ddd]
[aaa, ccc, ddd]
[aaa, bbb, ccc, ddd]
[aaa, bbb, ccc, ddd, eee]

Хороший початок, але виклик add, або addall додасть учасників у несортованому порядку.
Chris Knight

Так. Будь-що, крім додавання їх до списку, було б прямим порушенням інтерфейсу List. Дивіться мою оновлену відповідь.
aioobe

@aioobe Гарна думка. Але хіба непідтримувана операція методу інтерфейсу не є запахом коду? Правильним способом може бути не розширення ArrayList, а реалізація List, але навіть тоді, можливо, List просто не був призначений для цієї мети. З Javadoc для списку: The user of this interface has precise control over where in the list each element is insertedщо не найкращий опис для вставки елементів в сортуванні, і вам все одно доведеться мати справу з add(int index, Object obj)методом інтерфейсу. Ці проблеми, ймовірно, пояснюють, чому List не було впроваджено упорядковано.
Chris Knight

Ну, операція необов’язкова з певної причини. Я не був би здивований, якщо б я отримав .addUnsupportedExceptionOperation під час роботи в SortedArrayList. Так, однакові міркування стосуються обох версій add, обох версій addAll та set. (Усі вони є необов’язковими операціями відповідно до інтерфейсу списку.)
aioobe

Ах, я не розумів, що це необов’язкові операції. Сюжет потовщується ...;)
Кріс Найт,

10

Використовуйте java.util.PriorityQueue.


7
це не Список, тобто відсутність довільного доступу.
Тіло

1
Це купа пріоритетів на основі черги, а не реалізує List.
zengr

3
Звичайно, зі списком, який підтримує порядок сортування, індекси змінюються постійно, тому довільний доступ, мабуть, і не потрібен.
Тіло

5
@Qwerky, зауважте, що точна відповідь не завжди є найкращою відповіддю, або відповідь, якою насправді є ОП.
aioobe

3
черга пріоритетів не надає відсортований порядок на ітерації.
marcorossi

6

Погляньте на SortedList

Цей клас реалізує відсортований список. Він побудований за допомогою компаратора, який може порівнювати два об’єкти та відповідно сортувати об’єкти. Коли ви додаєте об’єкт до списку, він вставляється у правильному місці. Об'єкти, які за рівнем порівняння рівні, будуть у списку в тому порядку, в якому вони були додані до цього списку. Додайте лише об’єкти, які компаратор може порівняти.


Коли в списку вже є об’єкти, рівні за даними компаратора, новий об’єкт буде вставлений відразу після цих інших об’єктів.


5
Це виглядає добре, але також виглядає помилково: жодної версії addAll не замінено, тому після виклику їх список буде відсортовано.
Том Андерсон,

3
А метод додавання "не має ефекту". Він повинен скоріше викинути UnsupportedOperationException, якщо його не можна використовувати.
Тіло

@Tom Anderson @Thilo, погодьтеся з вами обома.
Jigar Joshi

1
Цікаво, але я досить обережний, щоб хтось у майбутньому використовував addAll()і думав, що всі ці елементи будуть відсортовані. Погодьтесь і з UnsupportedOperationException.
Chris Knight

1
Яка часова складність доповнення до цього списку?
shrini1000


5

Підхід Aioobe - це шлях. Я хотів би запропонувати наступне покращення щодо його рішення.

class SortedList<T> extends ArrayList<T> {

    public void insertSorted(T value) {
        int insertPoint = insertPoint(value);
        add(insertPoint, value);
    }

    /**
     * @return The insert point for a new value. If the value is found the insert point can be any
     * of the possible positions that keeps the collection sorted (.33 or 3.3 or 33.).
     */
    private int insertPoint(T key) {
        int low = 0;
        int high = size() - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = (Comparable<T>) get(mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else {
                return mid; // key found
            }
        }

        return low;  // key not found
    }
}

Рішення aioobe стає дуже повільним при використанні великих списків. Використання того факту, що список сортується, дозволяє нам знайти точку вставки нових значень за допомогою двійкового пошуку.

Я б також використовував композицію над спадщиною, щось на зразок

SortedList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

4

Списки, як правило, зберігають порядок додавання елементів. Вам точно потрібен список , чи сортуваний набір (наприклад TreeSet<E>) буде для вас добре? В основному, чи потрібно зберігати дублікати?


2
Дякую, Джон, але мені потрібно зберегти дублікати
Кріс Найт,

2

Це може бути занадто важкою вагою для вас, але GlazedLists має SortedList, який ідеально підходить для використання в якості моделі таблиці або JList


1

Ви можете підклас ArrayList і викликати Collections.sort (this) після додавання будь-якого елемента - вам потрібно буде замінити дві версії add і дві addAll, щоб зробити це.

Продуктивність була б не такою хорошою, як розумніша реалізація, яка вставляла б елементи в потрібне місце, але це могло б зробити роботу. Якщо додавання до списку є рідкісним, вартість, амортизована за всі операції зі списку, повинна бути низькою.


1

Просто створіть такий клас, як цей:

public class SortedList<T> extends ArrayList<T> {

private final Comparator<? super T> comparator;

public SortedList() {
    super();
    this.comparator = null;
}

public SortedList(Comparator<T> comparator) {
    super();
    this.comparator = comparator;
}

@Override
public boolean add(T item) {
    int index = comparator == null ? Collections.binarySearch((List<? extends Comparable<? super T>>)this, item) :
            Collections.binarySearch(this, item, comparator);
    if (index < 0) {
        index = index * -1 - 2;
    }
    super.add(index+1, item);
    return true;
}

@Override
public void add(int index, T item) {
    throw new UnsupportedOperationException("'add' with an index is not supported in SortedArrayList");
}

@Override
public boolean addAll(Collection<? extends T> items) {
    boolean allAdded = true;
    for (T item : items) {
        allAdded = allAdded && add(item);
    }
    return allAdded;
}

@Override
public boolean addAll(int index, Collection<? extends T> items) {
    throw new UnsupportedOperationException("'addAll' with an index is not supported in SortedArrayList");
}

}

Ви можете протестувати це так:

    List<Integer> list = new SortedArrayList<>((Integer i1, Integer i2) -> i1.compareTo(i2));
    for (Integer i : Arrays.asList(4, 7, 3, 8, 9, 25, 20, 23, 52, 3)) {
        list.add(i);
    }
    System.out.println(list);

0

Я думаю, що вибір між SortedSets / Lists та "звичайними" сортуваними колекціями залежить від того, чи потрібно вам сортувати лише для цілей презентації, чи майже в кожному місці під час виконання. Використання відсортованої колекції може бути набагато дорожчим, оскільки сортування проводиться кожного разу, коли ви вставляєте елемент.

Якщо ви не можете вибрати колекцію в JDK, ви можете поглянути на колекції Apache Commons


0

Оскільки запропоновані в даний час реалізації, які реалізують відсортований список, розбиваючи API колекції, мають власну реалізацію дерева або щось подібне, мені було цікаво, як буде реалізуватися реалізація на основі TreeMap. (Особливо, оскільки TreeSet теж базується на TreeMap)

Якщо когось це теж цікавить, він може почуватись вільно:

TreeList

Це частина основної бібліотеки , ви можете додати її через залежність Maven, звичайно. (Ліцензія Apache)

В даний час реалізація, схоже, досить добре порівнюється на тому ж рівні, що і сортування гуави SortedMultiSet та TreeList бібліотеки Apache Commons.

Але я був би радий, якби не тільки я протестував реалізацію, щоб переконатися, що я не пропустив чогось важливого.

З найкращими побажаннями!


0

У мене була та сама проблема. Тож я взяв вихідний код 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;
    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;
}

Результати цієї роботи ви можете знайти за адресою http://code.google.com/p/indexed-tree-map/

TreeSet / TreeMap (а також їхні індексовані аналоги з проекту індексованого дерева-карти) не дозволяють повторювані ключі, ви можете використовувати 1 ключ для масиву значень. Якщо вам потрібен SortedSet з дублікатами, використовуйте TreeMap зі значеннями як масиви. Я б це зробив.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.