2009-09-05 3 views
3

Кто-нибудь знает о реализации List, которая имеет постоянное время get (int index) (I.e. реализует RandomAccess), но не нужно копировать весь список, когда он растет, как ArrayList делает?ArrayList без накладных расходов на копирование?

Я думаю, что реализация может быть выполнена с точки зрения других списков, например.

public class ChunkedList<T> implements List<T>, RandomAccess { 
    private LinkedList<ArrayList<T>> chunks; 
    public T get(int index) { 
    return findCorrectChunk(index).get(computeChunkIndex(index)); 
    } 
} 
+1

Итак, у вас есть список, где вам нужно регулярно выращивать список и извлекать элементы из него по индексу? – aperkins

+9

AFAIK, ArrayList удваивает его емкость при превышении его текущей емкости. Таким образом, будут O (log (n)) копии, где n - конечная емкость List. Это означает, что количество фактических времен, когда нужно скопировать весь список, очень мало. Вероятно, вы начнете исчерпывать память задолго до того, как накладные расходы станут значительными. OTOH, если вы должны остановить копии и заранее знать верхнюю границу числа элементов, которые должны удерживать список, вы можете просто передать максимальный размер в качестве аргумента в конструктор ArrayList. – MAK

+2

есть практическая причина, в которой вы нуждаетесь? имеет контрольный показатель/профиль, показывающий, что арраист будет вашим узким местом? – basszero

ответ

2

Если бы существовала такая структура, все использовали бы ее вместо массивов.

Однако я считаю более близкую структуру, о которой мне рассказывали в университетской лекции. Он имеет постоянное время доступа и время добавления/удаления элемента в произвольную позицию в основном O (sqrt (N)), и только когда N пересекает квадрат целочисленного значения, он принимает O (N). Амортизированное время - O (sqrt (N)). Вот идея.

N элементов в этой структуре хранятся в непрерывном массиве, который разделен на квадраты sqrt (N) смежных элементов sqrt (N) (возможно, последний фрагмент содержит меньше элементов). Каждый кусок - это ring buffer, для которого позиция первого элемента хранится в отдельном массиве sqrt (N). Чтобы получить доступ к элементу, вы должны определить, в каком фрагменте он находится (принимает одно деление), и выполнить правильный сдвиг в кольцевом буфере (сумма и модуль). Это постоянное время для доступа.

Чтобы добавить элемент до i-го положения, определите кусок k, элемент будет в конечном итоге, а затем отметьте все последние элементы в каждом фрагменте в k .. sqrt(N)-1. Сдвиньте отмеченный элемент в предыдущем фрагменте в свободный слот в последнем фрагменте, который будет главой кольцевого буфера (доступ к дополнительному массиву для определения где именно). Затем в положение перемещенного элемента из предыдущего фрагмента перемещайте отмеченный элемент из пред-предыдущего фрагмента. Повторите это, и вы получите свободный слот в середине массива, чтобы разместить элемент, который вы собираетесь добавить.

Магия заключается в том, что вы должны только увеличивать значения в дополнительном массиве на единицу (принимает время O (sqrt (N)), что делает структуру последовательной для доступа снова. Магия sqrt (N) также находится здесь: вы должны работать с каждым из X кусков и по каждому из элементов N/X вспомогательного массива. min (X + N/X) достигается для X = sqrt (N).

Если нет места в последней порции, чтобы добавить еще один элемент (т.е. SQRT (N), используемые до сих пор слишком мал), переупакуйте массив с SQRT (N) увеличивается на единицу. Это занимает время O (N). Амортизированное время по-прежнему равно O (sqrt (N)) за элемент.

Поэтому добавление элемента в произвольное место массива принимает O (sqrt (N)). Удаление происходит в одно и то же время. Время доступа принимает O (1).

Это идея.Я не знаю, как это называется, и профессор не знал ни того, что сам изобрел его. Любые ссылки будут оценены. И OP мог его реализовать, но, я уверен, кто-то уже имеет.

+0

Это, безусловно, интересно, но вам нужно будет знать sqrt (N) раньше времени, хотя вы, вероятно, могли бы сделать хорошее предположение, а затем придерживаться его. – daveb

+0

Нет, на самом деле нет. И я упомянул об этом: вы просто используете текущий 'sqrt (N)' и перестраиваете всю структуру, когда изменяется целочисленная часть 'sqrt (N)'. Переупаковка принимает O (N), как вставка в обычный массив.Но это не должно происходить часто, и средняя стоимость (если массив только растет) - это O (sqrt (N)) в любом случае. –

+1

Я что-то упустил? Это решение имеет время вставки O (sqrt (N)), а время восстановления O (N) и перестройка происходит не более, чем sqrt (N) раз. Является ли это лучше, чем ArrayList, где у нас есть время вставки O (1), O (N) время восстановления и перестройка происходит не более, чем log (N) раз. – Buhb

0

Ну, на самом деле это не идеальное решение, но для этого вы можете использовать TreeMap. Ваш ChunkedList будет warapper вокруг этого. Ваши ключи в TreeMap будут иметь тип Integer или Long и будут содержать индексы списка. Время доступа и вставки будет o (log (n)) (не константа, но намного лучше, чем n). И внутренне TreeMap работает аналогично LinkedList, т. Е. Узлы просто связаны со ссылками.

EDIT: Что-то вроде этого:

public class ChunkedList<T> implements List<T>, RandomAccess { 

    private TreeMap<Integer, T> data = new TreeMap<Integer, T>(); 

    public T get(int index) { 
     return data.get(index); 
    } 

    public boolean add(T o) { 
     data.put(data.size() + 1, o); 
     return true; 
    } 

     // Other operations 

} 

Конечно, другие операции будут немного более сложным и займет больше времени, чем в ArrayList.

1

Вы можете, конечно, написать реализацию списка как массив массивов. Существует много вариантов точного алгоритма. Производительность теоретически постоянна (игнорируя эффекты кеша и т. Д.).

На практике в большинстве ситуаций не так много смысла. Существуют реализации веревок (строки, сформированные как массив сегментов), однако они относительно редки. Копия на самом деле не такая дорогостоящая и добавляет, что она амортизируется во многих операциях, чтобы исчезнуть.

(кстати, в данном примере вопроса коде LinkedList неуместно, так как это почти всегда бывает.)

0

Вы смотрели только намекая максимальный размер конструктора ArrayList?

0

Посмотрите на списки случайного доступа. Вы можете получить O (1) вставку с обоих концов и O (log (n)) доступ к элементам.

В конце концов, какая-то структура дерева-иса должна дать наилучшее время поиска/вставки.

0

Это звучит как преждевременная оптимизация. Профилировали ли вы функцию add() и показали, что она медленная? Поскольку ArrayList удваивает размер базового массива каждый раз, когда у него заканчивается пространство, поэтому вам не нужно копировать список каждый раз, когда вы добавляете его.

Возможно, вы пытаетесь решить проблему, которая не существует.

0

Невозможно написать такую ​​структуру данных. Самое близкое, что вы можете получить, - это предварительный размер массива ArrayList до максимального размера, предполагая, что вы знаете max. Интересно, что такие алгоритмы, как Collections.sort(), будут выполнять хуже по адресу ChunkedList, если его обозначение RandomAccess.

Смежные вопросы